smart.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package smart
  2. /*
  3. DISK SMART Service Listener
  4. Original author: alanyeung
  5. Rewritten by tobychui in Oct 2020 for system arch upgrade
  6. This module is not the core part of aroz online system.
  7. If you want to remove disk smart handler (e.g. running in VM?)
  8. remove the corrisponding code in disk.go
  9. */
  10. import (
  11. "encoding/json"
  12. "log"
  13. "net/http"
  14. "os"
  15. "strconv"
  16. "strings"
  17. //"os/exec"
  18. "errors"
  19. "runtime"
  20. "imuslab.com/arozos/mod/utils"
  21. //"time"
  22. )
  23. type SMARTListener struct {
  24. SystemSmartExecutable string
  25. DriveList DevicesList `json:"driveList"`
  26. }
  27. // DiskSmartInit Desktop script initiation
  28. func NewSmartListener() (*SMARTListener, error) {
  29. smartExec := getBinary()
  30. log.Println("Starting SMART mointoring")
  31. if smartExec == "" {
  32. return &SMARTListener{}, errors.New("not supported platform")
  33. }
  34. if !(utils.FileExists(smartExec)) {
  35. return &SMARTListener{}, errors.New("smartctl not found")
  36. }
  37. //Updated 5 June 2023: Try to chmod it if it is on linux so that
  38. //broken permissions still works in sudo mode
  39. if runtime.GOOS == "linux" {
  40. os.Chmod(smartExec, 0777)
  41. }
  42. driveList := scanAvailableDevices(smartExec)
  43. readSMARTDevices(smartExec, &driveList)
  44. fillHealthyStatus(&driveList)
  45. fillCapacity(&driveList)
  46. return &SMARTListener{
  47. SystemSmartExecutable: smartExec,
  48. DriveList: driveList,
  49. }, nil
  50. }
  51. // this function used for fetch available devices by using smartctl
  52. func scanAvailableDevices(smartExec string) DevicesList {
  53. rawInfo := execCommand(smartExec, "--scan", "--json=c")
  54. devicesList := new(DevicesList)
  55. json.Unmarshal([]byte(rawInfo), &devicesList)
  56. //used to remove csmi devices (Intel RAID Devices)
  57. numOfRemoved := 0
  58. for i, device := range devicesList.Devices {
  59. if strings.Contains(device.Name, "/dev/csmi") {
  60. devicesList.Devices = append(devicesList.Devices[:i-numOfRemoved], devicesList.Devices[i+1-numOfRemoved:]...)
  61. numOfRemoved++
  62. }
  63. }
  64. return *devicesList
  65. }
  66. // this function used for merge SMART Information into devicesList
  67. func readSMARTDevices(smartExec string, devicesList *DevicesList) {
  68. for i, device := range devicesList.Devices {
  69. rawInfo := execCommand(smartExec, device.Name, "--info", "--all", "--json=c")
  70. deviceSMART := new(DeviceSMART)
  71. json.Unmarshal([]byte(rawInfo), &deviceSMART)
  72. devicesList.Devices[i].Smart = *deviceSMART
  73. }
  74. }
  75. // used for fill the healthy status to the array
  76. func fillHealthyStatus(devicesList *DevicesList) {
  77. devicesList.Healthy = "Normal"
  78. for i, device := range devicesList.Devices {
  79. for j, smartTableElement := range device.Smart.AtaSmartAttributes.Table {
  80. devicesList.Devices[i].Smart.Healthy = "Normal"
  81. devicesList.Devices[i].Smart.AtaSmartAttributes.Table[j].Healthy = "Normal"
  82. if smartTableElement.WhenFailed == "FAILING_NOW" {
  83. devicesList.Devices[i].Smart.AtaSmartAttributes.Table[j].Healthy = "Failing"
  84. devicesList.Devices[i].Smart.Healthy = "Failing"
  85. devicesList.Healthy = "Failing"
  86. break
  87. }
  88. if smartTableElement.WhenFailed == "In_the_past" {
  89. devicesList.Devices[i].Smart.AtaSmartAttributes.Table[j].Healthy = "Attention"
  90. devicesList.Devices[i].Smart.Healthy = "Attention"
  91. devicesList.Healthy = "Attention"
  92. break
  93. }
  94. }
  95. }
  96. }
  97. // fill the capacity if windows
  98. func fillCapacity(devicesList *DevicesList) {
  99. if runtime.GOOS == "windows" {
  100. DiskNames := wmicGetinfo("diskdrive", "Model")
  101. DiskSizes := wmicGetinfo("diskdrive", "Size")
  102. for i, device := range devicesList.Devices {
  103. for j := range DiskNames {
  104. //since Intel driver will alter drive name to "XXXX SCSI Disk Device"
  105. //so remove the string to increase the match probability
  106. DiskNames[j] = strings.ReplaceAll(DiskNames[j], " SCSI Disk Device", "")
  107. //if the name match && capacity == 0
  108. if device.Smart.ModelName == DiskNames[j] && devicesList.Devices[i].Smart.UserCapacity.Bytes == 0 {
  109. capacity, _ := strconv.ParseInt(DiskSizes[j], 10, 64)
  110. devicesList.Devices[i].Smart.UserCapacity.Bytes = capacity
  111. }
  112. }
  113. }
  114. }
  115. }
  116. func (s *SMARTListener) GetSMART(w http.ResponseWriter, r *http.Request) {
  117. jsonText, _ := json.Marshal(s.DriveList)
  118. utils.SendJSONResponse(w, string(jsonText))
  119. }
  120. func getBinary() string {
  121. if runtime.GOOS == "windows" {
  122. return ".\\system\\disk\\smart\\win\\smartctl.exe"
  123. } else if runtime.GOOS == "linux" {
  124. if runtime.GOARCH == "arm" {
  125. return "./system/disk/smart/linux/smartctl_armv6"
  126. }
  127. if runtime.GOARCH == "arm64" {
  128. return "./system/disk/smart/linux/smartctl_arm64"
  129. }
  130. if runtime.GOARCH == "386" || runtime.GOARCH == "amd64" {
  131. return "./system/disk/smart/linux/smartctl_i386"
  132. }
  133. }
  134. return ""
  135. }