1// +build windows 2 3package host 4 5import ( 6 "context" 7 "fmt" 8 "math" 9 "os" 10 "runtime" 11 "strings" 12 "sync/atomic" 13 "syscall" 14 "time" 15 "unsafe" 16 17 "github.com/StackExchange/wmi" 18 "github.com/shirou/gopsutil/internal/common" 19 process "github.com/shirou/gopsutil/process" 20 "golang.org/x/sys/windows" 21) 22 23var ( 24 procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime") 25 procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount") 26 procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64") 27 procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") 28 procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion") 29) 30 31// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw 32type osVersionInfoExW struct { 33 dwOSVersionInfoSize uint32 34 dwMajorVersion uint32 35 dwMinorVersion uint32 36 dwBuildNumber uint32 37 dwPlatformId uint32 38 szCSDVersion [128]uint16 39 wServicePackMajor uint16 40 wServicePackMinor uint16 41 wSuiteMask uint16 42 wProductType uint8 43 wReserved uint8 44} 45 46type systemInfo struct { 47 wProcessorArchitecture uint16 48 wReserved uint16 49 dwPageSize uint32 50 lpMinimumApplicationAddress uintptr 51 lpMaximumApplicationAddress uintptr 52 dwActiveProcessorMask uintptr 53 dwNumberOfProcessors uint32 54 dwProcessorType uint32 55 dwAllocationGranularity uint32 56 wProcessorLevel uint16 57 wProcessorRevision uint16 58} 59 60type msAcpi_ThermalZoneTemperature struct { 61 Active bool 62 CriticalTripPoint uint32 63 CurrentTemperature uint32 64 InstanceName string 65} 66 67func Info() (*InfoStat, error) { 68 return InfoWithContext(context.Background()) 69} 70 71func InfoWithContext(ctx context.Context) (*InfoStat, error) { 72 ret := &InfoStat{ 73 OS: runtime.GOOS, 74 } 75 76 { 77 hostname, err := os.Hostname() 78 if err == nil { 79 ret.Hostname = hostname 80 } 81 } 82 83 { 84 platform, family, version, err := PlatformInformationWithContext(ctx) 85 if err == nil { 86 ret.Platform = platform 87 ret.PlatformFamily = family 88 ret.PlatformVersion = version 89 } else { 90 return ret, err 91 } 92 } 93 94 { 95 kernelArch, err := kernelArch() 96 if err == nil { 97 ret.KernelArch = kernelArch 98 } 99 } 100 101 { 102 boot, err := BootTimeWithContext(ctx) 103 if err == nil { 104 ret.BootTime = boot 105 ret.Uptime, _ = Uptime() 106 } 107 } 108 109 { 110 hostID, err := getMachineGuid() 111 if err == nil { 112 ret.HostID = hostID 113 } 114 } 115 116 { 117 procs, err := process.PidsWithContext(ctx) 118 if err == nil { 119 ret.Procs = uint64(len(procs)) 120 } 121 } 122 123 return ret, nil 124} 125 126func getMachineGuid() (string, error) { 127 // 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 128 // for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue 129 var h windows.Handle 130 err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) 131 if err != nil { 132 return "", err 133 } 134 defer windows.RegCloseKey(h) 135 136 const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 137 const uuidLen = 36 138 139 var regBuf [windowsRegBufLen]uint16 140 bufLen := uint32(windowsRegBufLen) 141 var valType uint32 142 err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) 143 if err != nil { 144 return "", err 145 } 146 147 hostID := windows.UTF16ToString(regBuf[:]) 148 hostIDLen := len(hostID) 149 if hostIDLen != uuidLen { 150 return "", fmt.Errorf("HostID incorrect: %q\n", hostID) 151 } 152 153 return strings.ToLower(hostID), nil 154} 155 156func Uptime() (uint64, error) { 157 return UptimeWithContext(context.Background()) 158} 159 160func UptimeWithContext(ctx context.Context) (uint64, error) { 161 procGetTickCount := procGetTickCount64 162 err := procGetTickCount64.Find() 163 if err != nil { 164 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 165 } 166 r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0) 167 if lastErr != 0 { 168 return 0, lastErr 169 } 170 return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil 171} 172 173func bootTimeFromUptime(up uint64) uint64 { 174 return uint64(time.Now().Unix()) - up 175} 176 177// cachedBootTime must be accessed via atomic.Load/StoreUint64 178var cachedBootTime uint64 179 180func BootTime() (uint64, error) { 181 return BootTimeWithContext(context.Background()) 182} 183 184func BootTimeWithContext(ctx context.Context) (uint64, error) { 185 t := atomic.LoadUint64(&cachedBootTime) 186 if t != 0 { 187 return t, nil 188 } 189 up, err := Uptime() 190 if err != nil { 191 return 0, err 192 } 193 t = bootTimeFromUptime(up) 194 atomic.StoreUint64(&cachedBootTime, t) 195 return t, nil 196} 197 198func PlatformInformation() (platform string, family string, version string, err error) { 199 return PlatformInformationWithContext(context.Background()) 200} 201 202func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) { 203 // GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest 204 // RtlGetVersion bypasses this lying layer and returns the true Windows version 205 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion 206 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw 207 var osInfo osVersionInfoExW 208 osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo)) 209 ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo))) 210 if ret != 0 { 211 return 212 } 213 214 // Platform 215 var h windows.Handle // like getMachineGuid(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx 216 err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) 217 if err != nil { 218 return 219 } 220 defer windows.RegCloseKey(h) 221 var bufLen uint32 222 var valType uint32 223 err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, nil, &bufLen) 224 if err != nil { 225 return 226 } 227 regBuf := make([]uint16, bufLen/2+1) 228 err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) 229 if err != nil { 230 return 231 } 232 platform = windows.UTF16ToString(regBuf[:]) 233 if !strings.HasPrefix(platform, "Microsoft") { 234 platform = "Microsoft " + platform 235 } 236 err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, nil, &bufLen) // append Service Pack number, only on success 237 if err == nil { // don't return an error if only the Service Pack retrieval fails 238 regBuf = make([]uint16, bufLen/2+1) 239 err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) 240 if err == nil { 241 platform += " " + windows.UTF16ToString(regBuf[:]) 242 } 243 } 244 245 // PlatformFamily 246 switch osInfo.wProductType { 247 case 1: 248 family = "Standalone Workstation" 249 case 2: 250 family = "Server (Domain Controller)" 251 case 3: 252 family = "Server" 253 } 254 255 // Platform Version 256 version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber) 257 258 return platform, family, version, nil 259} 260 261func Users() ([]UserStat, error) { 262 return UsersWithContext(context.Background()) 263} 264 265func UsersWithContext(ctx context.Context) ([]UserStat, error) { 266 var ret []UserStat 267 268 return ret, nil 269} 270 271func SensorsTemperatures() ([]TemperatureStat, error) { 272 return SensorsTemperaturesWithContext(context.Background()) 273} 274 275func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { 276 var ret []TemperatureStat 277 var dst []msAcpi_ThermalZoneTemperature 278 q := wmi.CreateQuery(&dst, "") 279 if err := common.WMIQueryWithContext(ctx, q, &dst, nil, "root/wmi"); err != nil { 280 return ret, err 281 } 282 283 for _, v := range dst { 284 ts := TemperatureStat{ 285 SensorKey: v.InstanceName, 286 Temperature: kelvinToCelsius(v.CurrentTemperature, 2), 287 } 288 ret = append(ret, ts) 289 } 290 291 return ret, nil 292} 293 294func kelvinToCelsius(temp uint32, n int) float64 { 295 // wmi return temperature Kelvin * 10, so need to divide the result by 10, 296 // and then minus 273.15 to get °Celsius. 297 t := float64(temp/10) - 273.15 298 n10 := math.Pow10(n) 299 return math.Trunc((t+0.5/n10)*n10) / n10 300} 301 302func Virtualization() (string, string, error) { 303 return VirtualizationWithContext(context.Background()) 304} 305 306func VirtualizationWithContext(ctx context.Context) (string, string, error) { 307 return "", "", common.ErrNotImplementedError 308} 309 310func KernelVersion() (string, error) { 311 return KernelVersionWithContext(context.Background()) 312} 313 314func KernelVersionWithContext(ctx context.Context) (string, error) { 315 _, _, version, err := PlatformInformation() 316 return version, err 317} 318 319func kernelArch() (string, error) { 320 var systemInfo systemInfo 321 procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo))) 322 323 const ( 324 PROCESSOR_ARCHITECTURE_INTEL = 0 325 PROCESSOR_ARCHITECTURE_ARM = 5 326 PROCESSOR_ARCHITECTURE_ARM64 = 12 327 PROCESSOR_ARCHITECTURE_IA64 = 6 328 PROCESSOR_ARCHITECTURE_AMD64 = 9 329 ) 330 switch systemInfo.wProcessorArchitecture { 331 case PROCESSOR_ARCHITECTURE_INTEL: 332 if systemInfo.wProcessorLevel < 3 { 333 return "i386", nil 334 } 335 if systemInfo.wProcessorLevel > 6 { 336 return "i686", nil 337 } 338 return fmt.Sprintf("i%d86", systemInfo.wProcessorLevel), nil 339 case PROCESSOR_ARCHITECTURE_ARM: 340 return "arm", nil 341 case PROCESSOR_ARCHITECTURE_ARM64: 342 return "aarch64", nil 343 case PROCESSOR_ARCHITECTURE_IA64: 344 return "ia64", nil 345 case PROCESSOR_ARCHITECTURE_AMD64: 346 return "x86_64", nil 347 } 348 return "", nil 349} 350