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