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