disk_windows.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //go:build windows
  2. // +build windows
  3. package disk
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "syscall"
  9. "unsafe"
  10. "github.com/shirou/gopsutil/v3/internal/common"
  11. "golang.org/x/sys/windows"
  12. "golang.org/x/sys/windows/registry"
  13. )
  14. var (
  15. procGetDiskFreeSpaceExW = common.Modkernel32.NewProc("GetDiskFreeSpaceExW")
  16. procGetLogicalDriveStringsW = common.Modkernel32.NewProc("GetLogicalDriveStringsW")
  17. procGetDriveType = common.Modkernel32.NewProc("GetDriveTypeW")
  18. procGetVolumeInformation = common.Modkernel32.NewProc("GetVolumeInformationW")
  19. )
  20. var (
  21. fileFileCompression = int64(16) // 0x00000010
  22. fileReadOnlyVolume = int64(524288) // 0x00080000
  23. )
  24. // diskPerformance is an equivalent representation of DISK_PERFORMANCE in the Windows API.
  25. // https://docs.microsoft.com/fr-fr/windows/win32/api/winioctl/ns-winioctl-disk_performance
  26. type diskPerformance struct {
  27. BytesRead int64
  28. BytesWritten int64
  29. ReadTime int64
  30. WriteTime int64
  31. IdleTime int64
  32. ReadCount uint32
  33. WriteCount uint32
  34. QueueDepth uint32
  35. SplitCount uint32
  36. QueryTime int64
  37. StorageDeviceNumber uint32
  38. StorageManagerName [8]uint16
  39. alignmentPadding uint32 // necessary for 32bit support, see https://github.com/elastic/beats/pull/16553
  40. }
  41. func init() {
  42. // enable disk performance counters on Windows Server editions (needs to run as admin)
  43. key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\PartMgr`, registry.SET_VALUE)
  44. if err == nil {
  45. key.SetDWordValue("EnableCounterForIoctl", 1)
  46. }
  47. }
  48. func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
  49. lpFreeBytesAvailable := int64(0)
  50. lpTotalNumberOfBytes := int64(0)
  51. lpTotalNumberOfFreeBytes := int64(0)
  52. diskret, _, err := procGetDiskFreeSpaceExW.Call(
  53. uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(path))),
  54. uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
  55. uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
  56. uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))
  57. if diskret == 0 {
  58. return nil, err
  59. }
  60. ret := &UsageStat{
  61. Path: path,
  62. Total: uint64(lpTotalNumberOfBytes),
  63. Free: uint64(lpTotalNumberOfFreeBytes),
  64. Used: uint64(lpTotalNumberOfBytes) - uint64(lpTotalNumberOfFreeBytes),
  65. UsedPercent: (float64(lpTotalNumberOfBytes) - float64(lpTotalNumberOfFreeBytes)) / float64(lpTotalNumberOfBytes) * 100,
  66. // InodesTotal: 0,
  67. // InodesFree: 0,
  68. // InodesUsed: 0,
  69. // InodesUsedPercent: 0,
  70. }
  71. return ret, nil
  72. }
  73. func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
  74. warnings := common.Warnings{
  75. Verbose: true,
  76. }
  77. var ret []PartitionStat
  78. lpBuffer := make([]byte, 254)
  79. diskret, _, err := procGetLogicalDriveStringsW.Call(
  80. uintptr(len(lpBuffer)),
  81. uintptr(unsafe.Pointer(&lpBuffer[0])))
  82. if diskret == 0 {
  83. return ret, err
  84. }
  85. for _, v := range lpBuffer {
  86. if v >= 65 && v <= 90 {
  87. path := string(v) + ":"
  88. typepath, _ := windows.UTF16PtrFromString(path)
  89. typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath)))
  90. if typeret == 0 {
  91. err := windows.GetLastError()
  92. warnings.Add(err)
  93. continue
  94. }
  95. // 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 4: DRIVE_REMOTE 5: DRIVE_CDROM
  96. if typeret == 2 || typeret == 3 || typeret == 4 || typeret == 5 {
  97. lpVolumeNameBuffer := make([]byte, 256)
  98. lpVolumeSerialNumber := int64(0)
  99. lpMaximumComponentLength := int64(0)
  100. lpFileSystemFlags := int64(0)
  101. lpFileSystemNameBuffer := make([]byte, 256)
  102. volpath, _ := windows.UTF16PtrFromString(string(v) + ":/")
  103. driveret, _, err := procGetVolumeInformation.Call(
  104. uintptr(unsafe.Pointer(volpath)),
  105. uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])),
  106. uintptr(len(lpVolumeNameBuffer)),
  107. uintptr(unsafe.Pointer(&lpVolumeSerialNumber)),
  108. uintptr(unsafe.Pointer(&lpMaximumComponentLength)),
  109. uintptr(unsafe.Pointer(&lpFileSystemFlags)),
  110. uintptr(unsafe.Pointer(&lpFileSystemNameBuffer[0])),
  111. uintptr(len(lpFileSystemNameBuffer)))
  112. if driveret == 0 {
  113. if typeret == 5 || typeret == 2 {
  114. continue // device is not ready will happen if there is no disk in the drive
  115. }
  116. warnings.Add(err)
  117. continue
  118. }
  119. opts := []string{"rw"}
  120. if lpFileSystemFlags&fileReadOnlyVolume != 0 {
  121. opts = []string{"ro"}
  122. }
  123. if lpFileSystemFlags&fileFileCompression != 0 {
  124. opts = append(opts, "compress")
  125. }
  126. d := PartitionStat{
  127. Mountpoint: path,
  128. Device: path,
  129. Fstype: string(bytes.Replace(lpFileSystemNameBuffer, []byte("\x00"), []byte(""), -1)),
  130. Opts: opts,
  131. }
  132. ret = append(ret, d)
  133. }
  134. }
  135. }
  136. return ret, warnings.Reference()
  137. }
  138. func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
  139. // https://github.com/giampaolo/psutil/blob/544e9daa4f66a9f80d7bf6c7886d693ee42f0a13/psutil/arch/windows/disk.c#L83
  140. drivemap := make(map[string]IOCountersStat, 0)
  141. var diskPerformance diskPerformance
  142. lpBuffer := make([]uint16, 254)
  143. lpBufferLen, err := windows.GetLogicalDriveStrings(uint32(len(lpBuffer)), &lpBuffer[0])
  144. if err != nil {
  145. return drivemap, err
  146. }
  147. for _, v := range lpBuffer[:lpBufferLen] {
  148. if 'A' <= v && v <= 'Z' {
  149. path := string(rune(v)) + ":"
  150. typepath, _ := windows.UTF16PtrFromString(path)
  151. typeret := windows.GetDriveType(typepath)
  152. if typeret == 0 {
  153. return drivemap, windows.GetLastError()
  154. }
  155. if typeret != windows.DRIVE_FIXED {
  156. continue
  157. }
  158. szDevice := fmt.Sprintf(`\\.\%s`, path)
  159. const IOCTL_DISK_PERFORMANCE = 0x70020
  160. h, err := windows.CreateFile(syscall.StringToUTF16Ptr(szDevice), 0, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, 0, 0)
  161. if err != nil {
  162. if err == windows.ERROR_FILE_NOT_FOUND {
  163. continue
  164. }
  165. return drivemap, err
  166. }
  167. defer windows.CloseHandle(h)
  168. var diskPerformanceSize uint32
  169. err = windows.DeviceIoControl(h, IOCTL_DISK_PERFORMANCE, nil, 0, (*byte)(unsafe.Pointer(&diskPerformance)), uint32(unsafe.Sizeof(diskPerformance)), &diskPerformanceSize, nil)
  170. if err != nil {
  171. return drivemap, err
  172. }
  173. drivemap[path] = IOCountersStat{
  174. ReadBytes: uint64(diskPerformance.BytesRead),
  175. WriteBytes: uint64(diskPerformance.BytesWritten),
  176. ReadCount: uint64(diskPerformance.ReadCount),
  177. WriteCount: uint64(diskPerformance.WriteCount),
  178. ReadTime: uint64(diskPerformance.ReadTime / 10000 / 1000), // convert to ms: https://github.com/giampaolo/psutil/issues/1012
  179. WriteTime: uint64(diskPerformance.WriteTime / 10000 / 1000),
  180. Name: path,
  181. }
  182. }
  183. }
  184. return drivemap, nil
  185. }
  186. func SerialNumberWithContext(ctx context.Context, name string) (string, error) {
  187. return "", common.ErrNotImplementedError
  188. }
  189. func LabelWithContext(ctx context.Context, name string) (string, error) {
  190. return "", common.ErrNotImplementedError
  191. }