diff --git a/CHANGELOG.md b/CHANGELOG.md index cece624..7a2b4b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added missing runtime import for FreeBSD. #104 - Handle nil command line in Windows processes. #110 +- List filesystems on Windows that have an access path but not an assigned letter. #112 ### Changed diff --git a/examples/df/df.go b/examples/df/df.go index 4411bd0..a366f27 100644 --- a/examples/df/df.go +++ b/examples/df/df.go @@ -13,7 +13,11 @@ const output_format = "%-15s %4s %4s %5s %4s %-15s\n" func main() { fslist := gosigar.FileSystemList{} - fslist.Get() + err := fslist.Get() + if err != nil { + fmt.Printf("Failed to get list of filesystems: %v", err) + os.Exit(-1) + } fmt.Fprintf(os.Stdout, output_format, "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on") diff --git a/sigar_windows.go b/sigar_windows.go index ebd7c33..6da3080 100644 --- a/sigar_windows.go +++ b/sigar_windows.go @@ -155,9 +155,9 @@ func (self *CpuList) Get() error { } func (self *FileSystemList) Get() error { - drives, err := windows.GetLogicalDriveStrings() + drives, err := windows.GetAccessPaths() if err != nil { - return errors.Wrap(err, "GetLogicalDriveStrings failed") + return errors.Wrap(err, "GetAccessPaths failed") } for _, drive := range drives { diff --git a/sys/windows/syscall_windows.go b/sys/windows/syscall_windows.go index 88df0fe..36be45b 100644 --- a/sys/windows/syscall_windows.go +++ b/sys/windows/syscall_windows.go @@ -151,25 +151,81 @@ func GetLogicalDriveStrings() ([]string, error) { return nil, errors.Wrap(err, "GetLogicalDriveStringsW failed") } - // Split the uint16 slice at null-terminators. - var startIdx int - var drivesUTF16 [][]uint16 - for i, value := range buffer { - if value == 0 { - drivesUTF16 = append(drivesUTF16, buffer[startIdx:i]) - startIdx = i + 1 + return UTF16SliceToStringSlice(buffer), nil +} + +// GetAccessPaths returns the list of access paths for volumes in the system. +func GetAccessPaths() ([]string, error) { + volumes, err := GetVolumes() + if err != nil { + return nil, errors.Wrap(err, "GetVolumes failed") + } + + var paths []string + for _, volumeName := range volumes { + volumePaths, err := GetVolumePathsForVolume(volumeName) + if err != nil { + return nil, errors.Wrapf(err, "failed to get list of access paths for volume '%s'", volumeName) + } + if len(volumePaths) == 0 { + continue } + + // Get only the first path + paths = append(paths, volumePaths[0]) } - // Convert the utf16 slices to strings. - drives := make([]string, 0, len(drivesUTF16)) - for _, driveUTF16 := range drivesUTF16 { - if len(driveUTF16) > 0 { - drives = append(drives, syscall.UTF16ToString(driveUTF16)) + return paths, nil +} + +// GetVolumes returs the list of volumes in the system. +// https://docs.microsoft.com/es-es/windows/desktop/api/fileapi/nf-fileapi-findfirstvolumew +func GetVolumes() ([]string, error) { + buffer := make([]uint16, MAX_PATH+1) + + var volumes []string + + h, err := _FindFirstVolume(&buffer[0], uint32(len(buffer))) + if err != nil { + return nil, errors.Wrap(err, "FindFirstVolumeW failed") + } + defer _FindVolumeClose(h) + + for { + volumes = append(volumes, syscall.UTF16ToString(buffer)) + + err = _FindNextVolume(h, &buffer[0], uint32(len(buffer))) + if err != nil { + if errors.Cause(err) == syscall.ERROR_NO_MORE_FILES { + break + } + return nil, errors.Wrap(err, "FindNextVolumeW failed") } } - return drives, nil + return volumes, nil +} + +// GetVolumePathsForVolume returns the list of volume paths for a volume. +// https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-getvolumepathnamesforvolumenamew +func GetVolumePathsForVolume(volumeName string) ([]string, error) { + var length uint32 + err := _GetVolumePathNamesForVolumeName(volumeName, nil, 0, &length) + if errors.Cause(err) != syscall.ERROR_MORE_DATA { + return nil, errors.Wrap(err, "GetVolumePathNamesForVolumeNameW failed to get needed buffer length") + } + if length == 0 { + // Not mounted, no paths, that's ok + return nil, nil + } + + buffer := make([]uint16, length*(MAX_PATH+1)) + err = _GetVolumePathNamesForVolumeName(volumeName, &buffer[0], length, &length) + if err != nil { + return nil, errors.Wrap(err, "GetVolumePathNamesForVolumeNameW failed") + } + + return UTF16SliceToStringSlice(buffer), nil } // GlobalMemoryStatusEx retrieves information about the system's current usage @@ -361,10 +417,34 @@ func Process32Next(handle syscall.Handle) (ProcessEntry32, error) { return processEntry32, nil } +// UTF16SliceToStringSlice converts slice of uint16 containing a list of UTF16 +// strings to a slice of strings. +func UTF16SliceToStringSlice(buffer []uint16) []string { + // Split the uint16 slice at null-terminators. + var startIdx int + var stringsUTF16 [][]uint16 + for i, value := range buffer { + if value == 0 { + stringsUTF16 = append(stringsUTF16, buffer[startIdx:i]) + startIdx = i + 1 + } + } + + // Convert the utf16 slices to strings. + result := make([]string, 0, len(stringsUTF16)) + for _, stringUTF16 := range stringsUTF16 { + if len(stringUTF16) > 0 { + result = append(result, syscall.UTF16ToString(stringUTF16)) + } + } + + return result +} + // Use "GOOS=windows go generate -v -x ." to generate the source. // Add -trace to enable debug prints around syscalls. -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -systemdll=false -output zsyscall_windows.go syscall_windows.go // Windows API calls //sys _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) = kernel32.GlobalMemoryStatusEx @@ -383,3 +463,7 @@ func Process32Next(handle syscall.Handle) (ProcessEntry32, error) { //sys _LookupPrivilegeName(systemName string, luid *int64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW //sys _LookupPrivilegeValue(systemName string, name string, luid *int64) (err error) = advapi32.LookupPrivilegeValueW //sys _AdjustTokenPrivileges(token syscall.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges +//sys _FindFirstVolume(volumeName *uint16, size uint32) (handle syscall.Handle, err error) = kernel32.FindFirstVolumeW +//sys _FindNextVolume(handle syscall.Handle, volumeName *uint16, size uint32) (err error) = kernel32.FindNextVolumeW +//sys _FindVolumeClose(handle syscall.Handle) (err error) = kernel32.FindVolumeClose +//sys _GetVolumePathNamesForVolumeName(volumeName string, buffer *uint16, bufferSize uint32, length *uint32) (err error) = kernel32.GetVolumePathNamesForVolumeNameW diff --git a/sys/windows/zsyscall_windows.go b/sys/windows/zsyscall_windows.go index 53fae4e..8da079a 100644 --- a/sys/windows/zsyscall_windows.go +++ b/sys/windows/zsyscall_windows.go @@ -2,40 +2,71 @@ package windows -import "unsafe" -import "syscall" +import ( + "syscall" + "unsafe" +) var _ unsafe.Pointer +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + var ( modkernel32 = syscall.NewLazyDLL("kernel32.dll") modpsapi = syscall.NewLazyDLL("psapi.dll") modntdll = syscall.NewLazyDLL("ntdll.dll") modadvapi32 = syscall.NewLazyDLL("advapi32.dll") - procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") - procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW") - procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") - procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW") - procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") - procGetDriveTypeW = modkernel32.NewProc("GetDriveTypeW") - procEnumProcesses = modpsapi.NewProc("EnumProcesses") - procGetDiskFreeSpaceExW = modkernel32.NewProc("GetDiskFreeSpaceExW") - procProcess32FirstW = modkernel32.NewProc("Process32FirstW") - procProcess32NextW = modkernel32.NewProc("Process32NextW") - procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") - procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation") - procNtQueryInformationProcess = modntdll.NewProc("NtQueryInformationProcess") - procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") - procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") - procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") + procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") + procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW") + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW") + procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") + procGetDriveTypeW = modkernel32.NewProc("GetDriveTypeW") + procEnumProcesses = modpsapi.NewProc("EnumProcesses") + procGetDiskFreeSpaceExW = modkernel32.NewProc("GetDiskFreeSpaceExW") + procProcess32FirstW = modkernel32.NewProc("Process32FirstW") + procProcess32NextW = modkernel32.NewProc("Process32NextW") + procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") + procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation") + procNtQueryInformationProcess = modntdll.NewProc("NtQueryInformationProcess") + procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") + procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") + procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") + procFindFirstVolumeW = modkernel32.NewProc("FindFirstVolumeW") + procFindNextVolumeW = modkernel32.NewProc("FindNextVolumeW") + procFindVolumeClose = modkernel32.NewProc("FindVolumeClose") + procGetVolumePathNamesForVolumeNameW = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW") ) func _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) { r1, _, e1 := syscall.Syscall(procGlobalMemoryStatusEx.Addr(), 1, uintptr(unsafe.Pointer(buffer)), 0, 0) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -48,7 +79,7 @@ func _GetLogicalDriveStringsW(bufferLength uint32, buffer *uint16) (length uint3 length = uint32(r0) if length == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -60,7 +91,7 @@ func _GetProcessMemoryInfo(handle syscall.Handle, psmemCounters *ProcessMemoryCo r1, _, e1 := syscall.Syscall(procGetProcessMemoryInfo.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(psmemCounters)), uintptr(cb)) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -73,7 +104,7 @@ func _GetProcessImageFileName(handle syscall.Handle, outImageFileName *uint16, s length = uint32(r0) if length == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -85,7 +116,7 @@ func _GetSystemTimes(idleTime *syscall.Filetime, kernelTime *syscall.Filetime, u r1, _, e1 := syscall.Syscall(procGetSystemTimes.Addr(), 3, uintptr(unsafe.Pointer(idleTime)), uintptr(unsafe.Pointer(kernelTime)), uintptr(unsafe.Pointer(userTime))) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -98,7 +129,7 @@ func _GetDriveType(rootPathName *uint16) (dt DriveType, err error) { dt = DriveType(r0) if dt == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -110,7 +141,7 @@ func _EnumProcesses(processIds *uint32, sizeBytes uint32, bytesReturned *uint32) r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(processIds)), uintptr(sizeBytes), uintptr(unsafe.Pointer(bytesReturned))) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -122,7 +153,7 @@ func _GetDiskFreeSpaceEx(directoryName *uint16, freeBytesAvailable *uint64, tota r1, _, e1 := syscall.Syscall6(procGetDiskFreeSpaceExW.Addr(), 4, uintptr(unsafe.Pointer(directoryName)), uintptr(unsafe.Pointer(freeBytesAvailable)), uintptr(unsafe.Pointer(totalNumberOfBytes)), uintptr(unsafe.Pointer(totalNumberOfFreeBytes)), 0, 0) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -134,7 +165,7 @@ func _Process32First(handle syscall.Handle, processEntry32 *ProcessEntry32) (err r1, _, e1 := syscall.Syscall(procProcess32FirstW.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(processEntry32)), 0) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -146,7 +177,7 @@ func _Process32Next(handle syscall.Handle, processEntry32 *ProcessEntry32) (err r1, _, e1 := syscall.Syscall(procProcess32NextW.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(processEntry32)), 0) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -159,7 +190,7 @@ func _CreateToolhelp32Snapshot(flags uint32, processID uint32) (handle syscall.H handle = syscall.Handle(r0) if handle == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -172,7 +203,7 @@ func _NtQuerySystemInformation(systemInformationClass uint32, systemInformation ntstatus = uint32(r0) if ntstatus == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -185,7 +216,7 @@ func _NtQueryInformationProcess(processHandle syscall.Handle, processInformation ntstatus = uint32(r0) if ntstatus == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -206,7 +237,7 @@ func __LookupPrivilegeName(systemName *uint16, luid *int64, buffer *uint16, size r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -232,7 +263,7 @@ func __LookupPrivilegeValue(systemName *uint16, name *uint16, luid *int64) (err r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) if r1 == 0 { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) } else { err = syscall.EINVAL } @@ -251,7 +282,65 @@ func _AdjustTokenPrivileges(token syscall.Token, releaseAll bool, input *byte, o success = r0 != 0 if true { if e1 != 0 { - err = error(e1) + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _FindFirstVolume(volumeName *uint16, size uint32) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall(procFindFirstVolumeW.Addr(), 2, uintptr(unsafe.Pointer(volumeName)), uintptr(size), 0) + handle = syscall.Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _FindNextVolume(handle syscall.Handle, volumeName *uint16, size uint32) (err error) { + r1, _, e1 := syscall.Syscall(procFindNextVolumeW.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(volumeName)), uintptr(size)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _FindVolumeClose(handle syscall.Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFindVolumeClose.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _GetVolumePathNamesForVolumeName(volumeName string, buffer *uint16, bufferSize uint32, length *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(volumeName) + if err != nil { + return + } + return __GetVolumePathNamesForVolumeName(_p0, buffer, bufferSize, length) +} + +func __GetVolumePathNamesForVolumeName(volumeName *uint16, buffer *uint16, bufferSize uint32, length *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetVolumePathNamesForVolumeNameW.Addr(), 4, uintptr(unsafe.Pointer(volumeName)), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(unsafe.Pointer(length)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) } else { err = syscall.EINVAL }