mdadmConf.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package raid
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "time"
  10. "imuslab.com/bokofs/bokofsd/mod/disktool/diskfs"
  11. "imuslab.com/bokofs/bokofsd/mod/utils"
  12. )
  13. /*
  14. mdadmConf.go
  15. This package handles the config modification and update for
  16. the mdadm module
  17. */
  18. // Force mdadm to stop all RAID and load fresh from config file
  19. // on some Linux distro this is required as mdadm start too early
  20. func (m *Manager) FlushReload() error {
  21. //Get a list of currently running RAID devices
  22. raidDevices, err := m.GetRAIDDevicesFromProcMDStat()
  23. if err != nil {
  24. return err
  25. }
  26. //Stop all of the running RAID devices
  27. for _, rd := range raidDevices {
  28. err = m.FlushReloadDev(&rd)
  29. if err != nil {
  30. log.Println("[RAID] Unable to stop " + rd.Name + ": " + err.Error())
  31. continue
  32. }
  33. }
  34. time.Sleep(300 * time.Millisecond)
  35. //Assemble mdadm array again
  36. err = m.RestartRAIDService()
  37. if err != nil {
  38. return err
  39. }
  40. return nil
  41. }
  42. // FlushReloadDev stop a single RAID device and remove it from mdadm config
  43. func (m *Manager) FlushReloadDev(targetDev *RAIDDevice) error {
  44. //Check if it is mounted. If yes, skip this
  45. devMounted, err := diskfs.DeviceIsMounted("/dev/" + targetDev.Name)
  46. if devMounted || err != nil {
  47. return errors.New("device is in use")
  48. }
  49. cmdMdadm := exec.Command("sudo", "mdadm", "--stop", "/dev/"+targetDev.Name)
  50. // Run the command and capture its output
  51. _, err = cmdMdadm.Output()
  52. if err != nil {
  53. return err
  54. }
  55. return nil
  56. }
  57. // removeDevicesEntry remove device hardcode from mdadm config file
  58. func removeDevicesEntry(configLine string) string {
  59. // Split the config line by space character
  60. tokens := strings.Fields(configLine)
  61. // Iterate through the tokens to find and remove the devices=* part
  62. for i, token := range tokens {
  63. if strings.HasPrefix(token, "devices=") {
  64. // Remove the devices=* part from the slice
  65. tokens = append(tokens[:i], tokens[i+1:]...)
  66. break
  67. }
  68. }
  69. // Join the tokens back into a single string
  70. updatedConfigLine := strings.Join(tokens, " ")
  71. return updatedConfigLine
  72. }
  73. // Updates the mdadm configuration file with the details of RAID arrays
  74. // so the RAID drive will still be seen after a reboot (hopefully)
  75. // this will automatically add / remove config base on current runtime setup
  76. func (m *Manager) UpdateMDADMConfig() error {
  77. cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
  78. // Run the command and capture its output
  79. output, err := cmdMdadm.Output()
  80. if err != nil {
  81. return fmt.Errorf("error running mdadm command: %v", err)
  82. }
  83. //Load the config from system
  84. currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
  85. if err != nil {
  86. return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
  87. }
  88. currentConf := string(currentConfigBytes)
  89. //Check if the current config already contains the setting
  90. newConfigLines := []string{}
  91. uuidsInNewConfig := []string{}
  92. arrayConfigs := strings.TrimSpace(string(output))
  93. lines := strings.Split(arrayConfigs, "ARRAY")
  94. for _, line := range lines {
  95. //For each line, you should have something like this
  96. //ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
  97. // devices=/dev/sdb,/dev/sdc
  98. //Building structure for RAID Config Record
  99. line = strings.ReplaceAll(line, "\n", " ")
  100. fields := strings.Fields(line)
  101. if len(fields) < 5 {
  102. continue
  103. }
  104. poolUUID := strings.TrimPrefix(fields[3], "UUID=")
  105. uuidsInNewConfig = append(uuidsInNewConfig, poolUUID)
  106. //Check if this uuid already in the config file
  107. if strings.Contains(currentConf, poolUUID) {
  108. continue
  109. }
  110. //This config not exists in the settings. Add it to append lines
  111. log.Println("[RAID] Adding " + fields[0] + " (UUID=" + poolUUID + ") into mdadm config")
  112. settingLine := "ARRAY " + strings.Join(fields, " ")
  113. //Remove the device specific names
  114. settingLine = removeDevicesEntry(settingLine)
  115. newConfigLines = append(newConfigLines, settingLine)
  116. }
  117. originalConfigLines := strings.Split(strings.TrimSpace(currentConf), "\n")
  118. poolUUIDToBeRemoved := []string{}
  119. for _, line := range originalConfigLines {
  120. lineFields := strings.Fields(line)
  121. for _, thisField := range lineFields {
  122. if strings.HasPrefix(thisField, "UUID=") {
  123. //This is the UUID of this array. Check if it still exists in new storage config
  124. thisPoolUUID := strings.TrimPrefix(thisField, "UUID=")
  125. existsInNewConfig := utils.StringInArray(uuidsInNewConfig, thisPoolUUID)
  126. if !existsInNewConfig {
  127. //Label this UUID to be removed
  128. poolUUIDToBeRemoved = append(poolUUIDToBeRemoved, thisPoolUUID)
  129. }
  130. //Skip scanning the remaining fields of this RAID pool
  131. break
  132. }
  133. }
  134. }
  135. if len(poolUUIDToBeRemoved) > 0 {
  136. //Remove the old UUIDs from config
  137. for _, volumeUUID := range poolUUIDToBeRemoved {
  138. err = m.RemoveVolumeFromMDADMConfig(volumeUUID)
  139. if err != nil {
  140. log.Println("[RAID] Error when trying to remove old RAID volume from config: " + err.Error())
  141. return err
  142. } else {
  143. log.Println("[RAID] RAID volume " + volumeUUID + " removed from config file")
  144. }
  145. }
  146. }
  147. if len(newConfigLines) == 0 {
  148. //Nothing to write
  149. log.Println("[RAID] Nothing to write. Skipping mdadm config update.")
  150. return nil
  151. }
  152. // Construct the bash command to append the line to mdadm.conf using echo and tee
  153. for _, configLine := range newConfigLines {
  154. cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))
  155. // Run the command
  156. err := cmd.Run()
  157. if err != nil {
  158. return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
  159. }
  160. }
  161. return nil
  162. }
  163. // Removes a RAID volume from the mdadm configuration file given its volume UUID.
  164. // Note that this only remove a single line of config. If your line consists of multiple lines
  165. // you might need to remove it manually
  166. func (m *Manager) RemoveVolumeFromMDADMConfig(volumeUUID string) error {
  167. // Construct the sed command to remove the line containing the volume UUID from mdadm.conf
  168. sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)
  169. // Execute the sed command
  170. cmd := exec.Command("bash", "-c", sedCommand)
  171. err := cmd.Run()
  172. if err != nil {
  173. return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
  174. }
  175. return nil
  176. }