mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 16:22:03 +01:00 
			
		
		
		
	There is an undocumented 16KiB limit for text log messages. However, the limit for JSON messages is 256KiB. Even worse, logging JSON as text results in significant overhead since each double quote needs to be escaped. Instead, use logger.Logf.JSON to explicitly log the info as JSON. We also modify osdiag to return the information as structured data rather than implicitly have the package log on our behalf. This gives more control to the caller on how to log. Updates #7802 Signed-off-by: Joe Tsai <joetsai@digital-static.net>
		
			
				
	
	
		
			656 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			656 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package osdiag
 | |
| 
 | |
| import (
 | |
| 	"encoding/binary"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"unicode/utf16"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"github.com/dblohm7/wingoes/com"
 | |
| 	"github.com/dblohm7/wingoes/pe"
 | |
| 	"golang.org/x/sys/windows"
 | |
| 	"golang.org/x/sys/windows/registry"
 | |
| 	"tailscale.com/util/osdiag/internal/wsc"
 | |
| 	"tailscale.com/util/winutil"
 | |
| 	"tailscale.com/util/winutil/authenticode"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	errUnexpectedResult = errors.New("API call returned an unexpected value")
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	maxBinaryValueLen  = 128   // we'll truncate any binary values longer than this
 | |
| 	maxRegValueNameLen = 16384 // maximum length supported by Windows + 1
 | |
| 	initialValueBufLen = 80    // large enough to contain a stringified GUID encoded as UTF-16
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	supportInfoKeyModules    = "modules"
 | |
| 	supportInfoKeyPageFile   = "pageFile"
 | |
| 	supportInfoKeyRegistry   = "registry"
 | |
| 	supportInfoKeySecurity   = "securitySoftware"
 | |
| 	supportInfoKeyWinsockLSP = "winsockLSP"
 | |
| )
 | |
| 
 | |
| func supportInfo(reason LogSupportInfoReason) map[string]any {
 | |
| 	output := make(map[string]any)
 | |
| 
 | |
| 	regInfo, err := getRegistrySupportInfo(registry.LOCAL_MACHINE, []string{winutil.RegPolicyBase, winutil.RegBase})
 | |
| 	if err == nil {
 | |
| 		output[supportInfoKeyRegistry] = regInfo
 | |
| 	} else {
 | |
| 		output[supportInfoKeyRegistry] = err
 | |
| 	}
 | |
| 
 | |
| 	pageFileInfo, err := getPageFileInfo()
 | |
| 	if err == nil {
 | |
| 		output[supportInfoKeyPageFile] = pageFileInfo
 | |
| 	} else {
 | |
| 		output[supportInfoKeyPageFile] = err
 | |
| 	}
 | |
| 
 | |
| 	if reason == LogSupportInfoReasonBugReport {
 | |
| 		modInfo, err := getModuleInfo()
 | |
| 		if err == nil {
 | |
| 			output[supportInfoKeyModules] = modInfo
 | |
| 		} else {
 | |
| 			output[supportInfoKeyModules] = err
 | |
| 		}
 | |
| 
 | |
| 		output[supportInfoKeySecurity] = getSecurityInfo()
 | |
| 
 | |
| 		lspInfo, err := getWinsockLSPInfo()
 | |
| 		if err == nil {
 | |
| 			output[supportInfoKeyWinsockLSP] = lspInfo
 | |
| 		} else {
 | |
| 			output[supportInfoKeyWinsockLSP] = err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return output
 | |
| }
 | |
| 
 | |
| type getRegistrySupportInfoBufs struct {
 | |
| 	nameBuf  []uint16
 | |
| 	valueBuf []byte
 | |
| }
 | |
| 
 | |
| func getRegistrySupportInfo(root registry.Key, subKeys []string) (map[string]any, error) {
 | |
| 	bufs := getRegistrySupportInfoBufs{
 | |
| 		nameBuf:  make([]uint16, maxRegValueNameLen),
 | |
| 		valueBuf: make([]byte, initialValueBufLen),
 | |
| 	}
 | |
| 
 | |
| 	output := make(map[string]any)
 | |
| 
 | |
| 	for _, subKey := range subKeys {
 | |
| 		if err := getRegSubKey(root, subKey, 5, &bufs, output); err != nil && !errors.Is(err, registry.ErrNotExist) {
 | |
| 			return nil, fmt.Errorf("getRegistrySupportInfo: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return output, nil
 | |
| }
 | |
| 
 | |
| func keyString(key registry.Key, subKey string) string {
 | |
| 	var keyStr string
 | |
| 	switch key {
 | |
| 	case registry.CLASSES_ROOT:
 | |
| 		keyStr = `HKCR\`
 | |
| 	case registry.CURRENT_USER:
 | |
| 		keyStr = `HKCU\`
 | |
| 	case registry.LOCAL_MACHINE:
 | |
| 		keyStr = `HKLM\`
 | |
| 	case registry.USERS:
 | |
| 		keyStr = `HKU\`
 | |
| 	case registry.CURRENT_CONFIG:
 | |
| 		keyStr = `HKCC\`
 | |
| 	case registry.PERFORMANCE_DATA:
 | |
| 		keyStr = `HKPD\`
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	return keyStr + subKey
 | |
| }
 | |
| 
 | |
| func getRegSubKey(key registry.Key, subKey string, recursionLimit int, bufs *getRegistrySupportInfoBufs, output map[string]any) error {
 | |
| 	keyStr := keyString(key, subKey)
 | |
| 	k, err := registry.OpenKey(key, subKey, registry.READ)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("opening %q: %w", keyStr, err)
 | |
| 	}
 | |
| 	defer k.Close()
 | |
| 
 | |
| 	kv := make(map[string]any)
 | |
| 	index := uint32(0)
 | |
| 
 | |
| loopValues:
 | |
| 	for {
 | |
| 		nbuf := bufs.nameBuf
 | |
| 		nameLen := uint32(len(nbuf))
 | |
| 		valueType := uint32(0)
 | |
| 		vbuf := bufs.valueBuf
 | |
| 		valueLen := uint32(len(vbuf))
 | |
| 
 | |
| 		err := regEnumValue(k, index, &nbuf[0], &nameLen, nil, &valueType, &vbuf[0], &valueLen)
 | |
| 		switch err {
 | |
| 		case windows.ERROR_NO_MORE_ITEMS:
 | |
| 			break loopValues
 | |
| 		case windows.ERROR_MORE_DATA:
 | |
| 			bufs.valueBuf = make([]byte, valueLen)
 | |
| 			continue
 | |
| 		case nil:
 | |
| 		default:
 | |
| 			return fmt.Errorf("regEnumValue: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		var value any
 | |
| 
 | |
| 		switch valueType {
 | |
| 		case registry.SZ, registry.EXPAND_SZ:
 | |
| 			value = windows.UTF16PtrToString((*uint16)(unsafe.Pointer(&vbuf[0])))
 | |
| 		case registry.BINARY:
 | |
| 			if valueLen > maxBinaryValueLen {
 | |
| 				valueLen = maxBinaryValueLen
 | |
| 			}
 | |
| 			value = append([]byte{}, vbuf[:valueLen]...)
 | |
| 		case registry.DWORD:
 | |
| 			value = binary.LittleEndian.Uint32(vbuf[:4])
 | |
| 		case registry.MULTI_SZ:
 | |
| 			// Adapted from x/sys/windows/registry/(Key).GetStringsValue
 | |
| 			p := (*[1 << 29]uint16)(unsafe.Pointer(&vbuf[0]))[: valueLen/2 : valueLen/2]
 | |
| 			var strs []string
 | |
| 			if len(p) > 0 {
 | |
| 				if p[len(p)-1] == 0 {
 | |
| 					p = p[:len(p)-1]
 | |
| 				}
 | |
| 				strs = make([]string, 0, 5)
 | |
| 				from := 0
 | |
| 				for i, c := range p {
 | |
| 					if c == 0 {
 | |
| 						strs = append(strs, string(utf16.Decode(p[from:i])))
 | |
| 						from = i + 1
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			value = strs
 | |
| 		case registry.QWORD:
 | |
| 			value = binary.LittleEndian.Uint64(vbuf[:8])
 | |
| 		default:
 | |
| 			value = fmt.Sprintf("<unsupported value type %d>", valueType)
 | |
| 		}
 | |
| 
 | |
| 		kv[windows.UTF16PtrToString(&nbuf[0])] = value
 | |
| 		index++
 | |
| 	}
 | |
| 
 | |
| 	if recursionLimit > 0 {
 | |
| 		if sks, err := k.ReadSubKeyNames(0); err == nil {
 | |
| 			for _, sk := range sks {
 | |
| 				if err := getRegSubKey(k, sk, recursionLimit-1, bufs, kv); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	output[keyStr] = kv
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type moduleInfo struct {
 | |
| 	path         string            `json:"-"` // internal use only
 | |
| 	BaseAddress  uintptr           `json:"baseAddress"`
 | |
| 	Size         uint32            `json:"size"`
 | |
| 	DebugInfo    map[string]string `json:"debugInfo,omitempty"` // map for JSON marshaling purposes
 | |
| 	DebugInfoErr error             `json:"debugInfoErr,omitempty"`
 | |
| 	Signature    map[string]string `json:"signature,omitempty"` // map for JSON marshaling purposes
 | |
| 	SignatureErr error             `json:"signatureErr,omitempty"`
 | |
| 	VersionInfo  map[string]string `json:"versionInfo,omitempty"` // map for JSON marshaling purposes
 | |
| 	VersionErr   error             `json:"versionErr,omitempty"`
 | |
| }
 | |
| 
 | |
| func (mi *moduleInfo) setVersionInfo() {
 | |
| 	vi, err := pe.NewVersionInfo(mi.path)
 | |
| 	if err != nil {
 | |
| 		if !errors.Is(err, pe.ErrNotPresent) {
 | |
| 			mi.VersionErr = err
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	info := map[string]string{
 | |
| 		"": vi.VersionNumber().String(),
 | |
| 	}
 | |
| 
 | |
| 	ci, err := vi.Field("CompanyName")
 | |
| 	if err == nil {
 | |
| 		info["companyName"] = ci
 | |
| 	}
 | |
| 
 | |
| 	mi.VersionInfo = info
 | |
| }
 | |
| 
 | |
| var errAssertingType = errors.New("asserting DataDirectory type")
 | |
| 
 | |
| func (mi *moduleInfo) setDebugInfo() {
 | |
| 	pem, err := pe.NewPEFromBaseAddressAndSize(mi.BaseAddress, mi.Size)
 | |
| 	if err != nil {
 | |
| 		mi.DebugInfoErr = err
 | |
| 		return
 | |
| 	}
 | |
| 	defer pem.Close()
 | |
| 
 | |
| 	debugDirAny, err := pem.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_DEBUG)
 | |
| 	if err != nil {
 | |
| 		if !errors.Is(err, pe.ErrNotPresent) {
 | |
| 			mi.DebugInfoErr = err
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	debugDir, ok := debugDirAny.([]pe.IMAGE_DEBUG_DIRECTORY)
 | |
| 	if !ok {
 | |
| 		mi.DebugInfoErr = errAssertingType
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, dde := range debugDir {
 | |
| 		if dde.Type != pe.IMAGE_DEBUG_TYPE_CODEVIEW {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		cv, err := pem.ExtractCodeViewInfo(dde)
 | |
| 		if err == nil {
 | |
| 			mi.DebugInfo = map[string]string{
 | |
| 				"id":  cv.String(),
 | |
| 				"pdb": strings.ToLower(filepath.Base(cv.PDBPath)),
 | |
| 			}
 | |
| 		} else {
 | |
| 			mi.DebugInfoErr = err
 | |
| 		}
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (mi *moduleInfo) setAuthenticodeInfo() {
 | |
| 	certSubject, provenance, err := authenticode.QueryCertSubject(mi.path)
 | |
| 	if err != nil {
 | |
| 		if !errors.Is(err, authenticode.ErrSigNotFound) {
 | |
| 			mi.SignatureErr = err
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	sigInfo := map[string]string{
 | |
| 		"subject": certSubject,
 | |
| 	}
 | |
| 
 | |
| 	switch provenance {
 | |
| 	case authenticode.SigProvEmbedded:
 | |
| 		sigInfo["provenance"] = "embedded"
 | |
| 	case authenticode.SigProvCatalog:
 | |
| 		sigInfo["provenance"] = "catalog"
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	mi.Signature = sigInfo
 | |
| }
 | |
| 
 | |
| func getModuleInfo() (map[string]moduleInfo, error) {
 | |
| 	// Take a snapshot of all modules currently loaded into the current process
 | |
| 	snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer windows.CloseHandle(snap)
 | |
| 
 | |
| 	result := make(map[string]moduleInfo)
 | |
| 	me := windows.ModuleEntry32{
 | |
| 		Size: uint32(unsafe.Sizeof(windows.ModuleEntry32{})),
 | |
| 	}
 | |
| 
 | |
| 	// Now walk the list
 | |
| 	for merr := windows.Module32First(snap, &me); merr == nil; merr = windows.Module32Next(snap, &me) {
 | |
| 		name := strings.ToLower(windows.UTF16ToString(me.Module[:]))
 | |
| 		path := windows.UTF16ToString(me.ExePath[:])
 | |
| 		base := me.ModBaseAddr
 | |
| 		size := me.ModBaseSize
 | |
| 
 | |
| 		entry := moduleInfo{
 | |
| 			path:        path,
 | |
| 			BaseAddress: base,
 | |
| 			Size:        size,
 | |
| 		}
 | |
| 
 | |
| 		entry.setVersionInfo()
 | |
| 		entry.setDebugInfo()
 | |
| 		entry.setAuthenticodeInfo()
 | |
| 
 | |
| 		result[name] = entry
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| type _WSC_PROVIDER_INFO_TYPE int32
 | |
| 
 | |
| const (
 | |
| 	providerInfoLspCategories _WSC_PROVIDER_INFO_TYPE = 0
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	_SOCKET_ERROR = -1
 | |
| )
 | |
| 
 | |
| // Note that wsaProtocolInfo needs to be identical to windows.WSAProtocolInfo;
 | |
| // the purpose of this type is to have the ability to use it as a reciever in
 | |
| // the path and categoryFlags funcs defined below.
 | |
| type wsaProtocolInfo windows.WSAProtocolInfo
 | |
| 
 | |
| func (pi *wsaProtocolInfo) path() (string, error) {
 | |
| 	var errno int32
 | |
| 	var buf [windows.MAX_PATH]uint16
 | |
| 	bufCount := int32(len(buf))
 | |
| 	ret := wscGetProviderPath(&pi.ProviderId, &buf[0], &bufCount, &errno)
 | |
| 	if ret == _SOCKET_ERROR {
 | |
| 		return "", windows.Errno(errno)
 | |
| 	}
 | |
| 	if ret != 0 {
 | |
| 		return "", errUnexpectedResult
 | |
| 	}
 | |
| 
 | |
| 	return windows.UTF16ToString(buf[:bufCount]), nil
 | |
| }
 | |
| 
 | |
| func (pi *wsaProtocolInfo) categoryFlags() (uint32, error) {
 | |
| 	var errno int32
 | |
| 	var result uint32
 | |
| 	bufLen := uintptr(unsafe.Sizeof(result))
 | |
| 	ret := wscGetProviderInfo(&pi.ProviderId, providerInfoLspCategories, unsafe.Pointer(&result), &bufLen, 0, &errno)
 | |
| 	if ret == _SOCKET_ERROR {
 | |
| 		return 0, windows.Errno(errno)
 | |
| 	}
 | |
| 	if ret != 0 {
 | |
| 		return 0, errUnexpectedResult
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| type wsaProtocolInfoOutput struct {
 | |
| 	Description     string   `json:"description,omitempty"`
 | |
| 	Version         int32    `json:"version"`
 | |
| 	AddressFamily   int32    `json:"addressFamily"`
 | |
| 	SocketType      int32    `json:"socketType"`
 | |
| 	Protocol        int32    `json:"protocol"`
 | |
| 	ServiceFlags1   string   `json:"serviceFlags1"`
 | |
| 	ProviderFlags   string   `json:"providerFlags"`
 | |
| 	Path            string   `json:"path,omitempty"`
 | |
| 	PathErr         error    `json:"pathErr,omitempty"`
 | |
| 	Category        string   `json:"category,omitempty"`
 | |
| 	CategoryErr     error    `json:"categoryErr,omitempty"`
 | |
| 	BaseProviderID  string   `json:"baseProviderID,omitempty"`
 | |
| 	LayerProviderID string   `json:"layerProviderID,omitempty"`
 | |
| 	Chain           []uint32 `json:"chain,omitempty"`
 | |
| }
 | |
| 
 | |
| func getWinsockLSPInfo() (map[uint32]wsaProtocolInfoOutput, error) {
 | |
| 	protocols, err := enumWinsockProtocols()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	result := make(map[uint32]wsaProtocolInfoOutput, len(protocols))
 | |
| 	for _, p := range protocols {
 | |
| 		v := wsaProtocolInfoOutput{
 | |
| 			Description:   windows.UTF16ToString(p.ProtocolName[:]),
 | |
| 			Version:       p.Version,
 | |
| 			AddressFamily: p.AddressFamily,
 | |
| 			SocketType:    p.SocketType,
 | |
| 			Protocol:      p.Protocol,
 | |
| 			ServiceFlags1: fmt.Sprintf("0x%08X", p.ServiceFlags1), // Serializing as hex string to make the flags easier to decode by human inspection
 | |
| 			ProviderFlags: fmt.Sprintf("0x%08X", p.ProviderFlags),
 | |
| 		}
 | |
| 
 | |
| 		switch p.ProtocolChain.ChainLen {
 | |
| 		case windows.BASE_PROTOCOL:
 | |
| 			v.BaseProviderID = p.ProviderId.String()
 | |
| 		case windows.LAYERED_PROTOCOL:
 | |
| 			v.LayerProviderID = p.ProviderId.String()
 | |
| 		default:
 | |
| 			v.Chain = p.ProtocolChain.ChainEntries[:p.ProtocolChain.ChainLen]
 | |
| 		}
 | |
| 
 | |
| 		// Queries that are only valid for base and layered protocols (not chains)
 | |
| 		if v.Chain == nil {
 | |
| 			path, err := p.path()
 | |
| 			if err == nil {
 | |
| 				v.Path = strings.ToLower(path)
 | |
| 			} else {
 | |
| 				v.PathErr = err
 | |
| 			}
 | |
| 
 | |
| 			category, err := p.categoryFlags()
 | |
| 			if err == nil {
 | |
| 				v.Category = fmt.Sprintf("0x%08X", category)
 | |
| 			} else if !errors.Is(err, windows.WSAEINVALIDPROVIDER) {
 | |
| 				// WSAEINVALIDPROVIDER == "no category info found", so we only log
 | |
| 				// errors other than that one.
 | |
| 				v.CategoryErr = err
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Chains reference other providers using catalog entry IDs, so we use that
 | |
| 		// value as the key in our map.
 | |
| 		result[p.CatalogEntryId] = v
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| func enumWinsockProtocols() ([]wsaProtocolInfo, error) {
 | |
| 	// Get the required size
 | |
| 	var errno int32
 | |
| 	var bytesReqd uint32
 | |
| 	ret := wscEnumProtocols(nil, nil, &bytesReqd, &errno)
 | |
| 	if ret != _SOCKET_ERROR {
 | |
| 		return nil, errUnexpectedResult
 | |
| 	}
 | |
| 	if e := windows.Errno(errno); e != windows.WSAENOBUFS {
 | |
| 		return nil, e
 | |
| 	}
 | |
| 
 | |
| 	// Allocate
 | |
| 	szEntry := uint32(unsafe.Sizeof(wsaProtocolInfo{}))
 | |
| 	buf := make([]wsaProtocolInfo, bytesReqd/szEntry)
 | |
| 
 | |
| 	// Now do the query for real
 | |
| 	bufLen := uint32(len(buf)) * szEntry
 | |
| 	ret = wscEnumProtocols(nil, &buf[0], &bufLen, &errno)
 | |
| 	if ret == _SOCKET_ERROR {
 | |
| 		return nil, windows.Errno(errno)
 | |
| 	}
 | |
| 
 | |
| 	return buf, nil
 | |
| }
 | |
| 
 | |
| type providerKey struct {
 | |
| 	provType wsc.WSC_SECURITY_PROVIDER
 | |
| 	provKey  string
 | |
| }
 | |
| 
 | |
| var providerKeys = []providerKey{
 | |
| 	providerKey{
 | |
| 		wsc.WSC_SECURITY_PROVIDER_ANTIVIRUS,
 | |
| 		"av",
 | |
| 	},
 | |
| 	providerKey{
 | |
| 		wsc.WSC_SECURITY_PROVIDER_ANTISPYWARE,
 | |
| 		"antispy",
 | |
| 	},
 | |
| 	providerKey{
 | |
| 		wsc.WSC_SECURITY_PROVIDER_FIREWALL,
 | |
| 		"firewall",
 | |
| 	},
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	maxProvCount = 100
 | |
| )
 | |
| 
 | |
| type secProductInfo struct {
 | |
| 	Name     string `json:"name,omitempty"`
 | |
| 	NameErr  error  `json:"nameErr,omitempty"`
 | |
| 	State    string `json:"state,omitempty"`
 | |
| 	StateErr error  `json:"stateErr,omitempty"`
 | |
| }
 | |
| 
 | |
| func getSecurityInfo() map[string]any {
 | |
| 	result := make(map[string]any)
 | |
| 
 | |
| 	for _, prov := range providerKeys {
 | |
| 		// Note that we need to obtain a new product list for each provider type;
 | |
| 		// the docs clearly state that we cannot reuse objects.
 | |
| 		productList, err := com.CreateInstance[wsc.WSCProductList](wsc.CLSID_WSCProductList)
 | |
| 		if err != nil {
 | |
| 			result[prov.provKey] = err
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		err = productList.Initialize(prov.provType)
 | |
| 		if err != nil {
 | |
| 			result[prov.provKey] = err
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		n, err := productList.GetCount()
 | |
| 		if err != nil {
 | |
| 			result[prov.provKey] = err
 | |
| 			continue
 | |
| 		}
 | |
| 		if n == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		n = min(n, maxProvCount)
 | |
| 		values := make([]any, 0, n)
 | |
| 
 | |
| 		for i := int32(0); i < n; i++ {
 | |
| 			product, err := productList.GetItem(uint32(i))
 | |
| 			if err != nil {
 | |
| 				values = append(values, err)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			var value secProductInfo
 | |
| 
 | |
| 			value.Name, err = product.GetProductName()
 | |
| 			if err != nil {
 | |
| 				value.NameErr = err
 | |
| 			}
 | |
| 
 | |
| 			state, err := product.GetProductState()
 | |
| 			if err == nil {
 | |
| 				switch state {
 | |
| 				case wsc.WSC_SECURITY_PRODUCT_STATE_ON:
 | |
| 					value.State = "on"
 | |
| 				case wsc.WSC_SECURITY_PRODUCT_STATE_OFF:
 | |
| 					value.State = "off"
 | |
| 				case wsc.WSC_SECURITY_PRODUCT_STATE_SNOOZED:
 | |
| 					value.State = "snoozed"
 | |
| 				case wsc.WSC_SECURITY_PRODUCT_STATE_EXPIRED:
 | |
| 					value.State = "expired"
 | |
| 				default:
 | |
| 					value.State = fmt.Sprintf("<unknown state value %d>", state)
 | |
| 				}
 | |
| 			} else {
 | |
| 				value.StateErr = err
 | |
| 			}
 | |
| 
 | |
| 			values = append(values, value)
 | |
| 		}
 | |
| 
 | |
| 		result[prov.provKey] = values
 | |
| 	}
 | |
| 
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| type _MEMORYSTATUSEX struct {
 | |
| 	Length               uint32
 | |
| 	MemoryLoad           uint32
 | |
| 	TotalPhys            uint64
 | |
| 	AvailPhys            uint64
 | |
| 	TotalPageFile        uint64
 | |
| 	AvailPageFile        uint64
 | |
| 	TotalVirtual         uint64
 | |
| 	AvailVirtual         uint64
 | |
| 	AvailExtendedVirtual uint64
 | |
| }
 | |
| 
 | |
| func getPageFileInfo() (map[string]any, error) {
 | |
| 	memStatus := _MEMORYSTATUSEX{
 | |
| 		Length: uint32(unsafe.Sizeof(_MEMORYSTATUSEX{})),
 | |
| 	}
 | |
| 	if err := globalMemoryStatusEx(&memStatus); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	result := map[string]any{
 | |
| 		"bytesAvailable": memStatus.AvailPageFile,
 | |
| 		"bytesTotal":     memStatus.TotalPageFile,
 | |
| 	}
 | |
| 
 | |
| 	if entries, err := getEffectivePageFileValue(); err == nil {
 | |
| 		// autoManaged is set to true when there is at least one page file that
 | |
| 		// is automatically managed.
 | |
| 		autoManaged := false
 | |
| 
 | |
| 		// If there is only one entry that consists of only one part, then
 | |
| 		// the page files are 100% managed by the system.
 | |
| 		// If there are multiple entries, then each one must be checked.
 | |
| 		// Each entry then consists of three components, deliminated by spaces.
 | |
| 		// If the latter two components are both "0", then that entry is auto-managed.
 | |
| 		for _, entry := range entries {
 | |
| 			if parts := strings.Split(entry, " "); (len(parts) == 1 && len(entries) == 1) ||
 | |
| 				(len(parts) == 3 && parts[1] == "0" && parts[2] == "0") {
 | |
| 				autoManaged = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		result["autoManaged"] = autoManaged
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| func getEffectivePageFileValue() ([]string, error) {
 | |
| 	const subKey = `SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management`
 | |
| 	key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.QUERY_VALUE)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer key.Close()
 | |
| 
 | |
| 	// Rare but possible case: the user has updated their page file config but
 | |
| 	// they haven't yet rebooted for the change to take effect. This is the
 | |
| 	// current setting that the machine is still operating with.
 | |
| 	if entries, _, err := key.GetStringsValue("ExistingPageFiles"); err == nil {
 | |
| 		return entries, nil
 | |
| 	}
 | |
| 
 | |
| 	// Otherwise we use this value (yes, the above value uses "Page" and this one uses "Paging").
 | |
| 	entries, _, err := key.GetStringsValue("PagingFiles")
 | |
| 	return entries, err
 | |
| }
 |