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(&regBuf[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(&regBuf[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(&regBuf[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