// +build windows
package disk
import (
"bytes"
"context"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/windows"
)
var (
procGetDiskFreeSpaceExW = common . Modkernel32 . NewProc ( "GetDiskFreeSpaceExW" )
procGetLogicalDriveStringsW = common . Modkernel32 . NewProc ( "GetLogicalDriveStringsW" )
procGetDriveType = common . Modkernel32 . NewProc ( "GetDriveTypeW" )
provGetVolumeInformation = common . Modkernel32 . NewProc ( "GetVolumeInformationW" )
)
var (
FileFileCompression = int64 ( 16 ) // 0x00000010
FileReadOnlyVolume = int64 ( 524288 ) // 0x00080000
)
type Win32_PerfFormattedData struct {
Name string
AvgDiskBytesPerRead uint64
AvgDiskBytesPerWrite uint64
AvgDiskReadQueueLength uint64
AvgDiskWriteQueueLength uint64
AvgDisksecPerRead uint64
AvgDisksecPerWrite uint64
}
type win32_DiskDrive struct {
DeviceID string
SerialNumber string
}
type win32_DiskPartition struct {
DeviceID string
}
const WaitMSec = 500
func Usage ( path string ) ( * UsageStat , error ) {
return UsageWithContext ( context . Background ( ) , path )
}
func UsageWithContext ( ctx context . Context , path string ) ( * UsageStat , error ) {
ret := & UsageStat { }
lpFreeBytesAvailable := int64 ( 0 )
lpTotalNumberOfBytes := int64 ( 0 )
lpTotalNumberOfFreeBytes := int64 ( 0 )
diskret , _ , err := procGetDiskFreeSpaceExW . Call (
uintptr ( unsafe . Pointer ( windows . StringToUTF16Ptr ( path ) ) ) ,
uintptr ( unsafe . Pointer ( & lpFreeBytesAvailable ) ) ,
uintptr ( unsafe . Pointer ( & lpTotalNumberOfBytes ) ) ,
uintptr ( unsafe . Pointer ( & lpTotalNumberOfFreeBytes ) ) )
if diskret == 0 {
return nil , err
}
ret = & UsageStat {
Path : path ,
Total : uint64 ( lpTotalNumberOfBytes ) ,
Free : uint64 ( lpTotalNumberOfFreeBytes ) ,
Used : uint64 ( lpTotalNumberOfBytes ) - uint64 ( lpTotalNumberOfFreeBytes ) ,
UsedPercent : ( float64 ( lpTotalNumberOfBytes ) - float64 ( lpTotalNumberOfFreeBytes ) ) / float64 ( lpTotalNumberOfBytes ) * 100 ,
// InodesTotal: 0,
// InodesFree: 0,
// InodesUsed: 0,
// InodesUsedPercent: 0,
}
return ret , nil
}
func Partitions ( all bool ) ( [ ] PartitionStat , error ) {
return PartitionsWithContext ( context . Background ( ) , all )
}
func PartitionsWithContext ( ctx context . Context , all bool ) ( [ ] PartitionStat , error ) {
var ret [ ] PartitionStat
lpBuffer := make ( [ ] byte , 254 )
diskret , _ , err := procGetLogicalDriveStringsW . Call (
uintptr ( len ( lpBuffer ) ) ,
uintptr ( unsafe . Pointer ( & lpBuffer [ 0 ] ) ) )
if diskret == 0 {
return ret , err
}
for _ , v := range lpBuffer {
if v >= 65 && v <= 90 {
path := string ( v ) + ":"
if path == "A:" || path == "B:" { // skip floppy drives
continue
}
typepath , _ := windows . UTF16PtrFromString ( path )
typeret , _ , _ := procGetDriveType . Call ( uintptr ( unsafe . Pointer ( typepath ) ) )
if typeret == 0 {
return ret , windows . GetLastError ( )
}
// 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 4: DRIVE_REMOTE 5: DRIVE_CDROM
if typeret == 2 || typeret == 3 || typeret == 4 || typeret == 5 {
lpVolumeNameBuffer := make ( [ ] byte , 256 )
lpVolumeSerialNumber := int64 ( 0 )
lpMaximumComponentLength := int64 ( 0 )
lpFileSystemFlags := int64 ( 0 )
lpFileSystemNameBuffer := make ( [ ] byte , 256 )
volpath , _ := windows . UTF16PtrFromString ( string ( v ) + ":/" )
driveret , _ , err := provGetVolumeInformation . Call (
uintptr ( unsafe . Pointer ( volpath ) ) ,
uintptr ( unsafe . Pointer ( & lpVolumeNameBuffer [ 0 ] ) ) ,
uintptr ( len ( lpVolumeNameBuffer ) ) ,
uintptr ( unsafe . Pointer ( & lpVolumeSerialNumber ) ) ,
uintptr ( unsafe . Pointer ( & lpMaximumComponentLength ) ) ,
uintptr ( unsafe . Pointer ( & lpFileSystemFlags ) ) ,
uintptr ( unsafe . Pointer ( & lpFileSystemNameBuffer [ 0 ] ) ) ,
uintptr ( len ( lpFileSystemNameBuffer ) ) )
if driveret == 0 {
if typeret == 5 || typeret == 2 {
continue //device is not ready will happen if there is no disk in the drive
}
return ret , err
}
opts := "rw"
if lpFileSystemFlags & FileReadOnlyVolume != 0 {
opts = "ro"
}
if lpFileSystemFlags & FileFileCompression != 0 {
opts += ".compress"
}
d := PartitionStat {
Mountpoint : path ,
Device : path ,
Fstype : string ( bytes . Replace ( lpFileSystemNameBuffer , [ ] byte ( "\x00" ) , [ ] byte ( "" ) , - 1 ) ) ,
Opts : opts ,
}
ret = append ( ret , d )
}
}
}
return ret , nil
}
func IOCounters ( names ... string ) ( map [ string ] IOCountersStat , error ) {
return IOCountersWithContext ( context . Background ( ) , names ... )
}
func IOCountersWithContext ( ctx context . Context , names ... string ) ( map [ string ] IOCountersStat , error ) {
ret := make ( map [ string ] IOCountersStat , 0 )
var dst [ ] Win32_PerfFormattedData
err := common . WMIQueryWithContext ( ctx , "SELECT * FROM Win32_PerfFormattedData_PerfDisk_LogicalDisk" , & dst )
if err != nil {
return ret , err
}
for _ , d := range dst {
if len ( d . Name ) > 3 { // not get _Total or Harddrive
continue
}
if len ( names ) > 0 && ! common . StringsHas ( names , d . Name ) {
continue
}
tmpIO := IOCountersStat {
Name : d . Name ,
ReadCount : uint64 ( d . AvgDiskReadQueueLength ) ,
WriteCount : d . AvgDiskWriteQueueLength ,
ReadBytes : uint64 ( d . AvgDiskBytesPerRead ) ,
WriteBytes : uint64 ( d . AvgDiskBytesPerWrite ) ,
ReadTime : d . AvgDisksecPerRead ,
WriteTime : d . AvgDisksecPerWrite ,
}
tmpIO . SerialNumber = GetDiskSerialNumber ( d . Name )
ret [ d . Name ] = tmpIO
}
return ret , nil
}
// return disk serial number(not volume serial number) of given device or empty string on error. Name of device is drive letter, eg. C:
func GetDiskSerialNumber ( name string ) string {
return GetDiskSerialNumberWithContext ( context . Background ( ) , name )
}
func GetDiskSerialNumberWithContext ( ctx context . Context , name string ) string {
var diskPart [ ] win32_DiskPartition
var diskDrive [ ] win32_DiskDrive
err := common . WMIQueryWithContext ( ctx , "Associators of {Win32_LogicalDisk.DeviceID='" + name + "'} where AssocClass=Win32_LogicalDiskToPartition" , & diskPart )
if err != nil || len ( diskPart ) <= 0 {
return ""
}
err = common . WMIQueryWithContext ( ctx , "Associators of {Win32_DiskPartition.DeviceID='" + diskPart [ 0 ] . DeviceID + "'} where AssocClass=Win32_DiskDriveToDiskPartition" , & diskDrive )
if err != nil || len ( diskDrive ) <= 0 {
return ""
}
return diskDrive [ 0 ] . SerialNumber
}