diskfs.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package diskfs
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "log"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "regexp"
  13. "strings"
  14. "imuslab.com/bokofs/bokofsd/mod/utils"
  15. )
  16. /*
  17. diskfs.go
  18. This module handle file system creation and formatting
  19. */
  20. // Storage Device meta was generated by lsblk
  21. // Partitions like sdX0
  22. type PartitionMeta struct {
  23. Name string `json:"name"`
  24. MajMin string `json:"maj:min"`
  25. Rm bool `json:"rm"`
  26. Size int64 `json:"size"`
  27. Ro bool `json:"ro"`
  28. Type string `json:"type"`
  29. Mountpoint string `json:"mountpoint"`
  30. }
  31. // Block device, usually disk or rom, like sdX
  32. type BlockDeviceMeta struct {
  33. Name string `json:"name"`
  34. MajMin string `json:"maj:min"`
  35. Rm bool `json:"rm"`
  36. Size int64 `json:"size"`
  37. Ro bool `json:"ro"`
  38. Type string `json:"type"`
  39. Mountpoint string `json:"mountpoint"`
  40. Children []PartitionMeta `json:"children,omitempty"`
  41. }
  42. // A collection of information for lsblk output
  43. type StorageDevicesMeta struct {
  44. Blockdevices []BlockDeviceMeta `json:"blockdevices"`
  45. }
  46. // Check if the file format driver is installed on this host
  47. // if a format is supported, mkfs.(format) should be symlinked under /sbin
  48. func FormatPackageInstalled(fsType string) bool {
  49. return utils.FileExists("/sbin/mkfs." + fsType)
  50. }
  51. // Create file system, support ntfs, ext4 and fat32 only
  52. func FormatStorageDevice(fsType string, devicePath string) error {
  53. // Check if the filesystem type is supported
  54. switch fsType {
  55. case "ext4":
  56. // Format the device with the specified filesystem type
  57. cmd := exec.Command("sudo", "mkfs."+fsType, devicePath)
  58. output, err := cmd.CombinedOutput()
  59. if err != nil {
  60. return errors.New("unable to format device: " + string(output))
  61. }
  62. return nil
  63. case "vfat", "fat", "fat32":
  64. //Check if mkfs.fat exists
  65. if !FormatPackageInstalled("vfat") {
  66. return errors.New("unable to format device as fat (vfat). dosfstools not installed?")
  67. }
  68. // Format the device with the specified filesystem type
  69. cmd := exec.Command("sudo", "mkfs.vfat", devicePath)
  70. output, err := cmd.CombinedOutput()
  71. if err != nil {
  72. return errors.New("unable to format device: " + string(output))
  73. }
  74. return nil
  75. case "ntfs":
  76. //Check if ntfs-3g exists
  77. if !FormatPackageInstalled("ntfs") {
  78. return errors.New("unable to format device as ntfs: ntfs-3g not installed?")
  79. }
  80. //Format the drive
  81. cmd := exec.Command("sudo", "mkfs.ntfs", devicePath)
  82. output, err := cmd.CombinedOutput()
  83. if err != nil {
  84. return errors.New("unable to format device: " + string(output))
  85. }
  86. return nil
  87. default:
  88. return fmt.Errorf("unsupported filesystem type: %s", fsType)
  89. }
  90. }
  91. // List all the storage device in the system, set minSize to 0 for no filter
  92. func ListAllStorageDevices() (*StorageDevicesMeta, error) {
  93. cmd := exec.Command("sudo", "lsblk", "-b", "--json")
  94. output, err := cmd.CombinedOutput()
  95. if err != nil {
  96. return nil, fmt.Errorf("lsblk error: %v", err)
  97. }
  98. var devices StorageDevicesMeta
  99. err = json.Unmarshal([]byte(output), &devices)
  100. return &devices, err
  101. }
  102. // Get block device (e.g. /dev/sdX) info
  103. func GetBlockDeviceMeta(devicePath string) (*BlockDeviceMeta, error) {
  104. //Trim the /dev/ part of the device path
  105. deviceName := strings.TrimPrefix(devicePath, "/dev/")
  106. if len(deviceName) == 0 {
  107. return nil, errors.New("invalid device path given")
  108. }
  109. re := regexp.MustCompile(`\d+`)
  110. if re.MatchString(deviceName) {
  111. //This is a partition
  112. return nil, errors.New("given device path is a partition not a block device")
  113. }
  114. storageMeta, err := ListAllStorageDevices()
  115. if err != nil {
  116. return nil, err
  117. }
  118. for _, blockdevice := range storageMeta.Blockdevices {
  119. if blockdevice.Name == deviceName {
  120. return &blockdevice, nil
  121. }
  122. }
  123. return nil, errors.New("target block device not found")
  124. }
  125. // Get the disk UUID by current device path (e.g. /dev/sda)
  126. func GetDiskUUID(devicePath string) (string, error) {
  127. cmd := exec.Command("sudo", "blkid", "-s", "UUID", "-o", "value", devicePath)
  128. var out bytes.Buffer
  129. cmd.Stdout = &out
  130. err := cmd.Run()
  131. if err != nil {
  132. return "", err
  133. }
  134. uuid := strings.TrimSpace(out.String())
  135. return uuid, nil
  136. }
  137. // Get partition information (e.g. /dev/sdX1)
  138. func GetPartitionMeta(devicePath string) (*PartitionMeta, error) {
  139. //Trim the /dev/ part of the device path
  140. deviceName := strings.TrimPrefix(devicePath, "/dev/")
  141. if len(deviceName) == 0 {
  142. return nil, errors.New("invalid device path given")
  143. }
  144. re := regexp.MustCompile(`\d+`)
  145. if !re.MatchString(deviceName) {
  146. //This is a partition
  147. return nil, errors.New("given device path is a block device not a partition")
  148. }
  149. storageMeta, err := ListAllStorageDevices()
  150. if err != nil {
  151. return nil, err
  152. }
  153. for _, blockdevice := range storageMeta.Blockdevices {
  154. if strings.Contains(deviceName, blockdevice.Name) {
  155. //Matching block device. Check for if there are a matching child
  156. for _, childPartition := range blockdevice.Children {
  157. if childPartition.Name == deviceName {
  158. return &childPartition, nil
  159. }
  160. }
  161. }
  162. }
  163. return nil, errors.New("target partition not found")
  164. }
  165. // Check if a device is mounted given the path name, like /dev/sdc
  166. func DeviceIsMounted(devicePath string) (bool, error) {
  167. // Open the mountinfo file
  168. file, err := os.Open("/proc/mounts")
  169. if err != nil {
  170. return false, fmt.Errorf("error opening /proc/mounts: %v", err)
  171. }
  172. defer file.Close()
  173. if !strings.HasPrefix(devicePath, "/dev/") {
  174. devicePath = filepath.Join("/dev/", devicePath)
  175. }
  176. // Scan the mountinfo file line by line
  177. scanner := bufio.NewScanner(file)
  178. for scanner.Scan() {
  179. line := scanner.Text()
  180. fields := strings.Fields(line)
  181. if strings.EqualFold(strings.TrimSpace(fields[0]), devicePath) {
  182. // Device is mounted
  183. return true, nil
  184. }
  185. }
  186. // Device is not mounted
  187. return false, nil
  188. }
  189. // UnmountDevice unmounts the specified device.
  190. // Remember to use full path (e.g. /dev/md0) in the devicePath
  191. func UnmountDevice(devicePath string) error {
  192. // Construct the bash command to unmount the device
  193. cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount -l %s", devicePath))
  194. // Run the command
  195. output, err := cmd.CombinedOutput()
  196. if err != nil {
  197. log.Println("[RAID] Unable to unmount device: " + string(output))
  198. return fmt.Errorf("error unmounting device: %v", err)
  199. }
  200. return nil
  201. }
  202. // Force Version of Unmount (dangerous)
  203. // Remember to use full path (e.g. /dev/md0) in the devicePath
  204. func ForceUnmountDevice(devicePath string) error {
  205. // Construct the bash command to unmount the device
  206. cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount -l %s", devicePath))
  207. // Run the command
  208. err := cmd.Run()
  209. if err != nil {
  210. return fmt.Errorf("error unmounting device: %v", err)
  211. }
  212. return nil
  213. }
  214. // DANGER: Wipe the whole disk given the disk path
  215. func WipeDisk(diskPath string) error {
  216. // Unmount the disk
  217. isMounted, _ := DeviceIsMounted(diskPath)
  218. if isMounted {
  219. umountCmd := exec.Command("sudo", "umount", diskPath)
  220. if err := umountCmd.Run(); err != nil {
  221. return fmt.Errorf("error unmounting disk %s: %v", diskPath, err)
  222. }
  223. }
  224. // Wipe all filesystem signatures on the entire disk
  225. wipeCmd := exec.Command("sudo", "wipefs", "--all", "--force", diskPath)
  226. if err := wipeCmd.Run(); err != nil {
  227. return fmt.Errorf("error wiping filesystem signatures on %s: %v", diskPath, err)
  228. }
  229. return nil
  230. }