raidutils.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package raid
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. "imuslab.com/arozos/mod/disk/diskfs"
  10. )
  11. // Get the next avaible RAID array name
  12. func GetNextAvailableMDDevice() (string, error) {
  13. for i := 0; i < 100; i++ {
  14. mdDevice := fmt.Sprintf("/dev/md%d", i)
  15. if _, err := os.Stat(mdDevice); os.IsNotExist(err) {
  16. return mdDevice, nil
  17. }
  18. }
  19. return "", fmt.Errorf("no available /dev/mdX devices found")
  20. }
  21. // Check if the given device is safe to remove from the array without losing data
  22. func (m *Manager) IsSafeToRemove(mdDev string, sdXDev string) bool {
  23. targetRAIDVol, err := m.GetRAIDDeviceByDevicePath(mdDev)
  24. if err != nil {
  25. return false
  26. }
  27. //Trim off the /dev/ part if exists
  28. sdXDev = filepath.Base(sdXDev)
  29. //Check how many members left if this is removed
  30. remainingMemebers := 0
  31. for _, member := range targetRAIDVol.Members {
  32. if member.Name != sdXDev {
  33. remainingMemebers++
  34. }
  35. }
  36. //Check if removal of sdX will cause data loss
  37. if strings.EqualFold(targetRAIDVol.Level, "raid0") {
  38. return false
  39. } else if strings.EqualFold(targetRAIDVol.Level, "raid1") {
  40. //In raid1, you need at least 1 disk to hold data
  41. return remainingMemebers >= 1
  42. } else if strings.EqualFold(targetRAIDVol.Level, "raid5") {
  43. //In raid 5, at least 2 disk is needed before data loss
  44. return remainingMemebers >= 2
  45. } else if strings.EqualFold(targetRAIDVol.Level, "raid6") {
  46. //In raid 6, you need 6 disks with max loss = 2 disks
  47. return remainingMemebers >= 2
  48. }
  49. return true
  50. }
  51. // Check if the given disk (sdX) is currently used in any volume
  52. func (m *Manager) DiskIsUsedInAnotherRAIDVol(sdXDev string) (bool, error) {
  53. raidPools, err := m.GetRAIDDevicesFromProcMDStat()
  54. if err != nil {
  55. return false, errors.New("unable to access RAID controller state")
  56. }
  57. for _, md := range raidPools {
  58. for _, member := range md.Members {
  59. if member.Name == filepath.Base(sdXDev) {
  60. return true, nil
  61. }
  62. }
  63. }
  64. return false, nil
  65. }
  66. // Check if the given disk (sdX) is root drive (the disk that install the OS, aka /)
  67. func (m *Manager) DiskIsRoot(sdXDev string) (bool, error) {
  68. bdMeta, err := diskfs.GetBlockDeviceMeta(sdXDev)
  69. if err != nil {
  70. return false, err
  71. }
  72. for _, partition := range bdMeta.Children {
  73. if partition.Mountpoint == "/" {
  74. //Root
  75. return true, nil
  76. }
  77. }
  78. return false, nil
  79. }
  80. // ClearSuperblock clears the superblock of the specified disk so it can be used safely
  81. func (m *Manager) ClearSuperblock(devicePath string) error {
  82. isMounted, err := diskfs.DeviceIsMounted(devicePath)
  83. if err != nil {
  84. return errors.New("unable to validate if the device is unmounted: " + err.Error())
  85. }
  86. if isMounted {
  87. return errors.New("target device is mounted. Make sure it is unmounted before clearing")
  88. }
  89. //Make sure there are /dev/ in front of the device path
  90. if !strings.HasPrefix(devicePath, "/dev/") {
  91. devicePath = filepath.Join("/dev/", devicePath)
  92. }
  93. cmd := exec.Command("sudo", "mdadm", "--zero-superblock", devicePath)
  94. err = cmd.Run()
  95. if err != nil {
  96. return fmt.Errorf("error clearing superblock: %v", err)
  97. }
  98. return nil
  99. }
  100. // Use to restart any not-removed RAID device
  101. func (m *Manager) RestartRAIDService() error {
  102. cmd := exec.Command("sudo", "mdadm", "--assemble", "--scan")
  103. // Run the command
  104. output, err := cmd.CombinedOutput()
  105. if err != nil {
  106. if string(output) == "" {
  107. //Nothing updated in config.
  108. return nil
  109. }
  110. return fmt.Errorf("error restarting RAID device: %s", strings.TrimSpace(string(output)))
  111. }
  112. return nil
  113. }
  114. // Stop RAID device with given path
  115. func (m *Manager) StopRAIDDevice(devicePath string) error {
  116. cmd := exec.Command("sudo", "mdadm", "--stop", devicePath)
  117. // Run the command
  118. err := cmd.Run()
  119. if err != nil {
  120. return fmt.Errorf("error stopping RAID device: %v", err)
  121. }
  122. return nil
  123. }
  124. // RemoveRAIDDevice removes the specified RAID device member (disk).
  125. func (m *Manager) RemoveRAIDMember(devicePath string) error {
  126. // Construct the mdadm command to remove the RAID device
  127. cmd := exec.Command("sudo", "mdadm", "--remove", devicePath)
  128. // Run the command
  129. output, err := cmd.CombinedOutput()
  130. if err != nil {
  131. // If there was an error, return the combined output and the error message
  132. return fmt.Errorf("error removing RAID device: %s", strings.TrimSpace(string(output)))
  133. }
  134. return nil
  135. }
  136. // IsValidRAIDLevel checks if the given RAID level is valid.
  137. func IsValidRAIDLevel(level string) bool {
  138. // List of valid RAID levels
  139. validLevels := []string{"raid1", "raid0", "raid6", "raid5", "raid4", "raid10"}
  140. // Convert the RAID level to lowercase and remove any surrounding whitespace
  141. level = strings.TrimSpace(strings.ToLower(level))
  142. // Check if the level exists in the list of valid levels
  143. for _, validLevel := range validLevels {
  144. if level == validLevel {
  145. return true
  146. }
  147. }
  148. // Return false if the level is not found in the list of valid levels
  149. return false
  150. }
  151. // Get RAID device info from device path
  152. func (m *Manager) GetRAIDDeviceByDevicePath(devicePath string) (*RAIDDevice, error) {
  153. //Strip the /dev/ part if it was accidentally passed in
  154. devicePath = filepath.Base(devicePath)
  155. //Get all the raid devices
  156. rdevs, err := m.GetRAIDDevicesFromProcMDStat()
  157. if err != nil {
  158. return nil, err
  159. }
  160. //Check for match
  161. for _, rdev := range rdevs {
  162. if rdev.Name == devicePath {
  163. return &rdev, nil
  164. }
  165. }
  166. return nil, errors.New("target RAID device not found")
  167. }
  168. // Check if a RAID device exists, e.g. md0
  169. func (m *Manager) RAIDDeviceExists(devicePath string) bool {
  170. _, err := m.GetRAIDDeviceByDevicePath(devicePath)
  171. return err == nil
  172. }