mdadm.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package raid
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "os"
  7. "os/exec"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "imuslab.com/arozos/mod/utils"
  12. )
  13. /*
  14. mdadm manager
  15. This script handles the interaction with mdadm
  16. */
  17. // Storage Device meta was generated by lsblk
  18. type StorageDeviceMeta struct {
  19. Name string
  20. Size int64
  21. RO bool
  22. DevType string
  23. MountPoint string
  24. }
  25. // RAIDDevice represents information about a RAID device.
  26. type RAIDMember struct {
  27. Name string
  28. Seq int
  29. }
  30. type RAIDDevice struct {
  31. Name string
  32. Status string
  33. Level string
  34. Members []*RAIDMember
  35. }
  36. // List all the storage device in the system, set minSize to 0 for no filter
  37. func (m *Manager) ListAllStorageDevices(minSize int64) ([]*StorageDeviceMeta, error) {
  38. cmd := exec.Command("sudo", "lsblk", "-b")
  39. output, err := cmd.CombinedOutput()
  40. if err != nil {
  41. return nil, fmt.Errorf("lsblk error: %v", err)
  42. }
  43. // Split the output into lines
  44. lines := strings.Split(string(output), "\n")
  45. var devices []*StorageDeviceMeta
  46. // Parse each line to extract device information
  47. for _, line := range lines[1:] { // Skip the header line
  48. fields := strings.Fields(line)
  49. if len(fields) < 7 {
  50. continue
  51. }
  52. size, err := strconv.ParseInt(fields[3], 10, 64)
  53. if err != nil {
  54. return nil, fmt.Errorf("error parsing device size: %v", err)
  55. }
  56. ro := fields[4] == "1"
  57. device := &StorageDeviceMeta{
  58. Name: fields[0],
  59. Size: size,
  60. RO: ro,
  61. DevType: fields[5],
  62. MountPoint: fields[6],
  63. }
  64. // Filter devices based on minimum size
  65. if size >= minSize {
  66. devices = append(devices, device)
  67. }
  68. }
  69. return devices, nil
  70. }
  71. // Return the uuid of the disk by its path name (e.g. /dev/sda)
  72. func (m *Manager) GetDiskUUIDByPath(devicePath string) (string, error) {
  73. cmd := exec.Command("sudo", "blkid", devicePath)
  74. output, err := cmd.CombinedOutput()
  75. if err != nil {
  76. return "", fmt.Errorf("blkid error: %v", err)
  77. }
  78. // Parse the output to extract the UUID
  79. fields := strings.Fields(string(output))
  80. for _, field := range fields {
  81. if strings.HasPrefix(field, "UUID=") {
  82. uuid := strings.TrimPrefix(field, "UUID=\"")
  83. uuid = strings.TrimSuffix(uuid, "\"")
  84. return uuid, nil
  85. }
  86. }
  87. return "", fmt.Errorf("UUID not found for device %s", devicePath)
  88. }
  89. // CreateRAIDDevice creates a RAID device using the mdadm command.
  90. func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel int, raidDeviceIds []string, spareDeviceIds []string) error {
  91. //Calculate the size of the raid devices
  92. raidDev := len(raidDeviceIds)
  93. spareDevice := len(spareDeviceIds)
  94. //Validate if raid level
  95. if !IsValidRAIDLevel("raid" + strconv.Itoa(raidLevel)) {
  96. return fmt.Errorf("invalid or unsupported raid level given: raid" + strconv.Itoa(raidLevel))
  97. }
  98. //Validate the number of disk is enough for the raid
  99. if raidLevel == 0 && raidDev < 2 {
  100. return fmt.Errorf("not enough disks for raid0")
  101. } else if raidLevel == 1 && raidDev < 2 {
  102. return fmt.Errorf("not enough disks for raid1")
  103. } else if raidLevel == 5 && raidDev < 3 {
  104. return fmt.Errorf("not enough disk for raid5")
  105. } else if raidLevel == 6 && raidDev < 4 {
  106. return fmt.Errorf("not enough disk for raid6")
  107. }
  108. //Append /dev to the name if missing
  109. if !strings.HasPrefix(devName, "/dev/") {
  110. devName = "/dev/" + devName
  111. }
  112. if utils.FileExists(devName) {
  113. //RAID device already exists
  114. return errors.New(devName + " already been used")
  115. }
  116. // Concatenate RAID and spare device arrays
  117. allDeviceIds := append(raidDeviceIds, spareDeviceIds...)
  118. // Build the mdadm command
  119. cmd := exec.Command("bash", "-c", fmt.Sprintf("yes | sudo mdadm --create %s --name %s --level=%d --raid-devices=%d --spare-devices=%d %s",
  120. devName, raidName, raidLevel, raidDev, spareDevice, strings.Join(allDeviceIds, " ")))
  121. cmd.Stdout = os.Stdout
  122. cmd.Stderr = os.Stderr
  123. err := cmd.Run()
  124. if err != nil {
  125. return fmt.Errorf("error running mdadm command: %v", err)
  126. }
  127. return nil
  128. }
  129. // GetRAIDDevicesFromProcMDStat retrieves information about RAID devices from /proc/mdstat.
  130. // if your RAID array is in auto-read-only mode, it is (usually) brand new
  131. func (m *Manager) GetRAIDDevicesFromProcMDStat() ([]RAIDDevice, error) {
  132. // Execute the cat command to read /proc/mdstat
  133. cmd := exec.Command("cat", "/proc/mdstat")
  134. // Run the command and capture its output
  135. output, err := cmd.Output()
  136. if err != nil {
  137. return nil, fmt.Errorf("error running cat command: %v", err)
  138. }
  139. // Convert the output to a string and split it into lines
  140. lines := strings.Split(string(output), "\n")
  141. // Initialize an empty slice to store RAID devices
  142. raidDevices := make([]RAIDDevice, 0)
  143. // Iterate over the lines, skipping the first line (Personalities)
  144. // Lines usually looks like this
  145. // md0 : active raid1 sdc[1] sdb[0]
  146. for _, line := range lines[1:] {
  147. // Skip empty lines
  148. if line == "" {
  149. continue
  150. }
  151. // Split the line by colon (:)
  152. parts := strings.SplitN(line, " : ", 2)
  153. if len(parts) != 2 {
  154. continue
  155. }
  156. // Extract device name and status
  157. deviceName := parts[0]
  158. // Split the members string by space to get individual member devices
  159. info := strings.Fields(parts[1])
  160. if len(info) < 4 {
  161. //Malform output
  162. continue
  163. }
  164. deviceStatus := info[0]
  165. //Raid level usually appears at position 1 - 2, check both
  166. raidLevel := ""
  167. if strings.HasPrefix(info[1], "raid") {
  168. raidLevel = info[1]
  169. } else if strings.HasPrefix(info[2], "raid") {
  170. raidLevel = info[2]
  171. }
  172. //Get the members (disks) of the array
  173. members := []*RAIDMember{}
  174. for _, disk := range info[2:] {
  175. if !strings.HasPrefix(disk, "sd") {
  176. //Probably not a storage device
  177. continue
  178. }
  179. //In sda[0] format, we need to split out the number from the disk seq
  180. tmp := strings.Split(disk, "[")
  181. if len(tmp) != 2 {
  182. continue
  183. }
  184. //Convert the sequence to id
  185. seqInt, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(tmp[1]), "]"))
  186. if err != nil {
  187. //Not an integer?
  188. log.Println("[RAID] Unable to parse " + disk + " sequence ID")
  189. continue
  190. }
  191. member := RAIDMember{
  192. Name: strings.TrimSpace(tmp[0]),
  193. Seq: seqInt,
  194. }
  195. members = append(members, &member)
  196. }
  197. //Sort the member disks
  198. sort.Slice(members[:], func(i, j int) bool {
  199. return members[i].Seq < members[j].Seq
  200. })
  201. // Create a RAIDDevice struct and append it to the slice
  202. raidDevice := RAIDDevice{
  203. Name: deviceName,
  204. Status: deviceStatus,
  205. Level: raidLevel,
  206. Members: members,
  207. }
  208. raidDevices = append(raidDevices, raidDevice)
  209. }
  210. return raidDevices, nil
  211. }