github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/disk/disk_windows.go (about)

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