@ -9,10 +9,10 @@ import (
"runtime"
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/StackExchange/wmi"
"github.com/shirou/gopsutil/internal/common"
process "github.com/shirou/gopsutil/process"
"golang.org/x/sys/windows"
@ -20,15 +20,24 @@ import (
var (
procGetSystemTimeAsFileTime = common . Modkernel32 . NewProc ( "GetSystemTimeAsFileTime" )
osInfo * Win32_OperatingSystem
procGetTickCount32 = common . Modkernel32 . NewProc ( "GetTickCount" )
procGetTickCount64 = common . Modkernel32 . NewProc ( "GetTickCount64" )
procRtlGetVersion = common . ModNt . NewProc ( "RtlGetVersion" )
)
type Win32_OperatingSystem struct {
Version string
Caption string
ProductType uint32
BuildNumber string
LastBootUpTime time . Time
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
type osVersionInfoExW struct {
dwOSVersionInfoSize uint32
dwMajorVersion uint32
dwMinorVersion uint32
dwBuildNumber uint32
dwPlatformId uint32
szCSDVersion [ 128 ] uint16
wServicePackMajor uint16
wServicePackMinor uint16
wSuiteMask uint16
wProductType uint8
wReserved uint8
}
func Info ( ) ( * InfoStat , error ) {
@ -84,6 +93,8 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
}
func getMachineGuid ( ) ( string , error ) {
// there has been reports of issues on 32bit using golang.org/x/sys/windows/registry, see https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612
// for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue
var h windows . Handle
err := windows . RegOpenKeyEx ( windows . HKEY_LOCAL_MACHINE , windows . StringToUTF16Ptr ( ` SOFTWARE\Microsoft\Cryptography ` ) , 0 , windows . KEY_READ | windows . KEY_WOW64_64KEY , & h )
if err != nil {
@ -111,40 +122,24 @@ func getMachineGuid() (string, error) {
return hostID , nil
}
func GetOSInfo ( ) ( Win32_OperatingSystem , error ) {
return GetOSInfoWithContext ( context . Background ( ) )
}
func GetOSInfoWithContext ( ctx context . Context ) ( Win32_OperatingSystem , error ) {
var dst [ ] Win32_OperatingSystem
q := wmi . CreateQuery ( & dst , "" )
err := common . WMIQueryWithContext ( ctx , q , & dst )
if err != nil {
return Win32_OperatingSystem { } , err
}
osInfo = & dst [ 0 ]
return dst [ 0 ] , nil
}
func Uptime ( ) ( uint64 , error ) {
return UptimeWithContext ( context . Background ( ) )
}
func UptimeWithContext ( ctx context . Context ) ( uint64 , error ) {
if osInfo == nil {
_ , err := GetOSInfoWithContext ( ctx )
if err != nil {
return 0 , err
}
procGetTickCount := procGetTickCount64
err := procGetTickCount64 . Find ( )
if err != nil {
procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
}
r1 , _ , lastErr := syscall . Syscall ( procGetTickCount . Addr ( ) , 0 , 0 , 0 , 0 )
if lastErr != 0 {
return 0 , lastErr
}
now := time . Now ( )
t := osInfo . LastBootUpTime . Local ( )
return uint64 ( now . Sub ( t ) . Seconds ( ) ) , nil
return uint64 ( ( time . Duration ( r1 ) * time . Millisecond ) . Seconds ( ) ) , nil
}
func bootTime ( up uint64 ) uint64 {
func bootTime FromUptime ( up uint64 ) uint64 {
return uint64 ( time . Now ( ) . Unix ( ) ) - up
}
@ -164,7 +159,7 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) {
if err != nil {
return 0 , err
}
t = bootTime ( up )
t = bootTime FromUptime ( up )
atomic . StoreUint64 ( & cachedBootTime , t )
return t , nil
}
@ -174,18 +169,50 @@ func PlatformInformation() (platform string, family string, version string, err
}
func PlatformInformationWithContext ( ctx context . Context ) ( platform string , family string , version string , err error ) {
if osInfo == nil {
_ , err = GetOSInfoWithContext ( ctx )
if err != nil {
return
}
// GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
// RtlGetVersion bypasses this lying layer and returns the true Windows version
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
var osInfo osVersionInfoExW
osInfo . dwOSVersionInfoSize = uint32 ( unsafe . Sizeof ( osInfo ) )
ret , _ , err := procRtlGetVersion . Call ( uintptr ( unsafe . Pointer ( & osInfo ) ) )
if ret != 0 {
return
}
// Platform
platform = strings . Trim ( osInfo . Caption , " " )
var h windows . Handle // like getMachineGuid(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx
err = windows . RegOpenKeyEx ( windows . HKEY_LOCAL_MACHINE , windows . StringToUTF16Ptr ( ` SOFTWARE\Microsoft\Windows NT\CurrentVersion ` ) , 0 , windows . KEY_READ | windows . KEY_WOW64_64KEY , & h )
if err != nil {
return
}
defer windows . RegCloseKey ( h )
var bufLen uint32
var valType uint32
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` ProductName ` ) , nil , & valType , nil , & bufLen )
if err != nil {
return
}
regBuf := make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` ProductName ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
if err != nil {
return
}
platform = windows . UTF16ToString ( regBuf [ : ] )
if ! strings . HasPrefix ( platform , "Microsoft" ) {
platform = "Microsoft " + platform
}
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CSDVersion ` ) , nil , & valType , nil , & bufLen ) // append Service Pack number, only on success
if err == nil { // don't return an error if only the Service Pack retrieval fails
regBuf = make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CSDVersion ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
if err == nil {
platform += " " + windows . UTF16ToString ( regBuf [ : ] )
}
}
// PlatformFamily
switch osInfo . ProductType {
switch osInfo . w ProductType {
case 1 :
family = "Standalone Workstation"
case 2 :
@ -195,9 +222,9 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
}
// Platform Version
version = fmt . Sprintf ( "% s Build %s", osInfo . Version , osInfo . BuildNumber)
version = fmt . Sprintf ( "% d.%d.%d Build %d", osInfo . dwMajorVersion , osInfo . dwMinorVersion , osInfo . dwBuildNumber , osInfo . dw BuildNumber)
return
return platform , family , version , nil
}
func Users ( ) ( [ ] UserStat , error ) {