package diskfs import ( "bufio" "bytes" "encoding/json" "errors" "fmt" "log" "os" "os/exec" "path/filepath" "regexp" "strings" "imuslab.com/bokofs/bokofsd/mod/utils" ) /* diskfs.go This module handle file system creation and formatting */ // Storage Device meta was generated by lsblk // Partitions like sdX0 type PartitionMeta struct { Name string `json:"name"` MajMin string `json:"maj:min"` Rm bool `json:"rm"` Size int64 `json:"size"` Ro bool `json:"ro"` Type string `json:"type"` Mountpoint string `json:"mountpoint"` } // Block device, usually disk or rom, like sdX type BlockDeviceMeta struct { Name string `json:"name"` MajMin string `json:"maj:min"` Rm bool `json:"rm"` Size int64 `json:"size"` Ro bool `json:"ro"` Type string `json:"type"` Mountpoint string `json:"mountpoint"` Children []PartitionMeta `json:"children,omitempty"` } // A collection of information for lsblk output type StorageDevicesMeta struct { Blockdevices []BlockDeviceMeta `json:"blockdevices"` } // Check if the file format driver is installed on this host // if a format is supported, mkfs.(format) should be symlinked under /sbin func FormatPackageInstalled(fsType string) bool { return utils.FileExists("/sbin/mkfs." + fsType) } // Create file system, support ntfs, ext4 and fat32 only func FormatStorageDevice(fsType string, devicePath string) error { // Check if the filesystem type is supported switch fsType { case "ext4": // Format the device with the specified filesystem type cmd := exec.Command("sudo", "mkfs."+fsType, devicePath) output, err := cmd.CombinedOutput() if err != nil { return errors.New("unable to format device: " + string(output)) } return nil case "vfat", "fat", "fat32": //Check if mkfs.fat exists if !FormatPackageInstalled("vfat") { return errors.New("unable to format device as fat (vfat). dosfstools not installed?") } // Format the device with the specified filesystem type cmd := exec.Command("sudo", "mkfs.vfat", devicePath) output, err := cmd.CombinedOutput() if err != nil { return errors.New("unable to format device: " + string(output)) } return nil case "ntfs": //Check if ntfs-3g exists if !FormatPackageInstalled("ntfs") { return errors.New("unable to format device as ntfs: ntfs-3g not installed?") } //Format the drive cmd := exec.Command("sudo", "mkfs.ntfs", devicePath) output, err := cmd.CombinedOutput() if err != nil { return errors.New("unable to format device: " + string(output)) } return nil default: return fmt.Errorf("unsupported filesystem type: %s", fsType) } } // List all the storage device in the system, set minSize to 0 for no filter func ListAllStorageDevices() (*StorageDevicesMeta, error) { cmd := exec.Command("sudo", "lsblk", "-b", "--json") output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("lsblk error: %v", err) } var devices StorageDevicesMeta err = json.Unmarshal([]byte(output), &devices) return &devices, err } // Get block device (e.g. /dev/sdX) info func GetBlockDeviceMeta(devicePath string) (*BlockDeviceMeta, error) { //Trim the /dev/ part of the device path deviceName := strings.TrimPrefix(devicePath, "/dev/") if len(deviceName) == 0 { return nil, errors.New("invalid device path given") } re := regexp.MustCompile(`\d+`) if re.MatchString(deviceName) { //This is a partition return nil, errors.New("given device path is a partition not a block device") } storageMeta, err := ListAllStorageDevices() if err != nil { return nil, err } for _, blockdevice := range storageMeta.Blockdevices { if blockdevice.Name == deviceName { return &blockdevice, nil } } return nil, errors.New("target block device not found") } // Get the disk UUID by current device path (e.g. /dev/sda) func GetDiskUUID(devicePath string) (string, error) { cmd := exec.Command("sudo", "blkid", "-s", "UUID", "-o", "value", devicePath) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return "", err } uuid := strings.TrimSpace(out.String()) return uuid, nil } // Get partition information (e.g. /dev/sdX1) func GetPartitionMeta(devicePath string) (*PartitionMeta, error) { //Trim the /dev/ part of the device path deviceName := strings.TrimPrefix(devicePath, "/dev/") if len(deviceName) == 0 { return nil, errors.New("invalid device path given") } re := regexp.MustCompile(`\d+`) if !re.MatchString(deviceName) { //This is a partition return nil, errors.New("given device path is a block device not a partition") } storageMeta, err := ListAllStorageDevices() if err != nil { return nil, err } for _, blockdevice := range storageMeta.Blockdevices { if strings.Contains(deviceName, blockdevice.Name) { //Matching block device. Check for if there are a matching child for _, childPartition := range blockdevice.Children { if childPartition.Name == deviceName { return &childPartition, nil } } } } return nil, errors.New("target partition not found") } // Check if a device is mounted given the path name, like /dev/sdc func DeviceIsMounted(devicePath string) (bool, error) { // Open the mountinfo file file, err := os.Open("/proc/mounts") if err != nil { return false, fmt.Errorf("error opening /proc/mounts: %v", err) } defer file.Close() if !strings.HasPrefix(devicePath, "/dev/") { devicePath = filepath.Join("/dev/", devicePath) } // Scan the mountinfo file line by line scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) if strings.EqualFold(strings.TrimSpace(fields[0]), devicePath) { // Device is mounted return true, nil } } // Device is not mounted return false, nil } // UnmountDevice unmounts the specified device. // Remember to use full path (e.g. /dev/md0) in the devicePath func UnmountDevice(devicePath string) error { // Construct the bash command to unmount the device cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount -l %s", devicePath)) // Run the command output, err := cmd.CombinedOutput() if err != nil { log.Println("[RAID] Unable to unmount device: " + string(output)) return fmt.Errorf("error unmounting device: %v", err) } return nil } // Force Version of Unmount (dangerous) // Remember to use full path (e.g. /dev/md0) in the devicePath func ForceUnmountDevice(devicePath string) error { // Construct the bash command to unmount the device cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount -l %s", devicePath)) // Run the command err := cmd.Run() if err != nil { return fmt.Errorf("error unmounting device: %v", err) } return nil } // DANGER: Wipe the whole disk given the disk path func WipeDisk(diskPath string) error { // Unmount the disk isMounted, _ := DeviceIsMounted(diskPath) if isMounted { umountCmd := exec.Command("sudo", "umount", diskPath) if err := umountCmd.Run(); err != nil { return fmt.Errorf("error unmounting disk %s: %v", diskPath, err) } } // Wipe all filesystem signatures on the entire disk wipeCmd := exec.Command("sudo", "wipefs", "--all", "--force", diskPath) if err := wipeCmd.Run(); err != nil { return fmt.Errorf("error wiping filesystem signatures on %s: %v", diskPath, err) } return nil }