sync.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package raid
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. )
  10. type SyncState struct {
  11. DeviceName string //e.g, md0
  12. ResyncPercent float64 //e.g, 0.5
  13. CompletedBlocks int64
  14. TotalBlocks int64
  15. ExpectedTime string //e.g, 1h23m
  16. Speed string //e.g, 1234K/s
  17. }
  18. // GetSyncStateByPath retrieves the synchronization state of a RAID device by its device path.
  19. // devpath can be either a full path (e.g., /dev/md0) or just the device name (e.g., md0).
  20. func (m *Manager) GetSyncStateByPath(devpath string) (*SyncState, error) {
  21. devpath = strings.TrimPrefix(devpath, "/dev/")
  22. syncStates, err := m.GetSyncStates()
  23. if err != nil {
  24. return nil, err
  25. }
  26. for _, syncState := range syncStates {
  27. if syncState.DeviceName == devpath {
  28. return &syncState, nil
  29. }
  30. }
  31. return nil, errors.New("device not found")
  32. }
  33. // GetSyncStates retrieves the synchronization states of RAID arrays from /proc/mdstat.
  34. func (m *Manager) GetSyncStates() ([]SyncState, error) {
  35. file, err := os.Open("/proc/mdstat")
  36. if err != nil {
  37. return nil, err
  38. }
  39. defer file.Close()
  40. var syncStates []SyncState
  41. var lastDeviceName string = ""
  42. scanner := bufio.NewScanner(file)
  43. for scanner.Scan() {
  44. line := scanner.Text()
  45. if strings.Contains(line, "resync =") || strings.Contains(line, "recovery =") {
  46. parts := strings.Fields(line)
  47. var syncState SyncState
  48. for i, part := range parts {
  49. if part == "resync" || part == "recovery" {
  50. // Extract percentage
  51. if i+2 < len(parts) && strings.HasSuffix(parts[i+2], "%") {
  52. fmt.Sscanf(parts[i+2], "%f%%", &syncState.ResyncPercent)
  53. }
  54. // Extract completed and total blocks
  55. if i+3 < len(parts) && strings.HasPrefix(parts[i+3], "(") && strings.Contains(parts[i+3], "/") {
  56. var completed, total int64
  57. fmt.Sscanf(parts[i+3], "(%d/%d)", &completed, &total)
  58. syncState.CompletedBlocks = completed
  59. syncState.TotalBlocks = total
  60. }
  61. // Extract expected time
  62. if i+4 < len(parts) && strings.HasPrefix(parts[i+4], "finish=") {
  63. syncState.ExpectedTime = strings.TrimPrefix(parts[i+4], "finish=")
  64. }
  65. // Extract speed
  66. if i+5 < len(parts) && strings.HasPrefix(parts[i+5], "speed=") {
  67. syncState.Speed = strings.TrimPrefix(parts[i+5], "speed=")
  68. }
  69. }
  70. }
  71. syncState.DeviceName = lastDeviceName
  72. if syncState.DeviceName == "" {
  73. return nil, errors.New("device name not found")
  74. }
  75. // Add the sync state to the list
  76. syncStates = append(syncStates, syncState)
  77. } else if strings.HasPrefix(line, "md") {
  78. // Extract device name
  79. parts := strings.Fields(line)
  80. if len(parts) > 0 {
  81. lastDeviceName = strings.TrimPrefix(parts[0], "/dev/")
  82. }
  83. }
  84. }
  85. if err := scanner.Err(); err != nil {
  86. return nil, err
  87. }
  88. return syncStates, nil
  89. }
  90. // SetSyncPendingToReadWrite sets the RAID device to read-write mode.
  91. // After a RAID array is created, it may be in a "sync-pending" state.
  92. // This function changes the state to "read-write".
  93. func (m *Manager) SetSyncPendingToReadWrite(devname string) error {
  94. // Ensure devname does not already have the /dev/ prefix
  95. devname = strings.TrimPrefix(devname, "/dev/")
  96. // Check if the current user has root privileges by checking the UID
  97. hasSudo := os.Geteuid() == 0
  98. //Check if the device exists
  99. if _, err := os.Stat(fmt.Sprintf("/dev/%s", devname)); os.IsNotExist(err) {
  100. return fmt.Errorf("device %s does not exist", devname)
  101. }
  102. // Construct the command
  103. var cmd *exec.Cmd
  104. if hasSudo {
  105. cmd = exec.Command("sudo", "mdadm", "--readwrite", fmt.Sprintf("/dev/%s", devname))
  106. } else {
  107. cmd = exec.Command("mdadm", "--readwrite", fmt.Sprintf("/dev/%s", devname))
  108. }
  109. if output, err := cmd.CombinedOutput(); err != nil {
  110. return fmt.Errorf("failed to set device %s to readwrite: %v, output: %s", devname, err, string(output))
  111. }
  112. return nil
  113. }