github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/toolkit/internal/common/common_windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package common
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strings"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	"github.com/yusufpapurcu/wmi"
    16  	"golang.org/x/sys/windows"
    17  )
    18  
    19  // for double values
    20  type PDH_FMT_COUNTERVALUE_DOUBLE struct {
    21  	CStatus     uint32
    22  	DoubleValue float64
    23  }
    24  
    25  // for 64 bit integer values
    26  type PDH_FMT_COUNTERVALUE_LARGE struct {
    27  	CStatus    uint32
    28  	LargeValue int64
    29  }
    30  
    31  // for long values
    32  type PDH_FMT_COUNTERVALUE_LONG struct {
    33  	CStatus   uint32
    34  	LongValue int32
    35  	padding   [4]byte
    36  }
    37  
    38  // windows system const
    39  const (
    40  	ERROR_SUCCESS        = 0
    41  	ERROR_FILE_NOT_FOUND = 2
    42  	DRIVE_REMOVABLE      = 2
    43  	DRIVE_FIXED          = 3
    44  	HKEY_LOCAL_MACHINE   = 0x80000002
    45  	RRF_RT_REG_SZ        = 0x00000002
    46  	RRF_RT_REG_DWORD     = 0x00000010
    47  	PDH_FMT_LONG         = 0x00000100
    48  	PDH_FMT_DOUBLE       = 0x00000200
    49  	PDH_FMT_LARGE        = 0x00000400
    50  	PDH_INVALID_DATA     = 0xc0000bc6
    51  	PDH_INVALID_HANDLE   = 0xC0000bbc
    52  	PDH_NO_DATA          = 0x800007d5
    53  
    54  	STATUS_BUFFER_OVERFLOW      = 0x80000005
    55  	STATUS_BUFFER_TOO_SMALL     = 0xC0000023
    56  	STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
    57  )
    58  
    59  const (
    60  	ProcessBasicInformation = 0
    61  	ProcessWow64Information = 26
    62  	ProcessQueryInformation = windows.PROCESS_DUP_HANDLE | windows.PROCESS_QUERY_INFORMATION
    63  
    64  	SystemExtendedHandleInformationClass = 64
    65  )
    66  
    67  var (
    68  	Modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
    69  	ModNt       = windows.NewLazySystemDLL("ntdll.dll")
    70  	ModPdh      = windows.NewLazySystemDLL("pdh.dll")
    71  	ModPsapi    = windows.NewLazySystemDLL("psapi.dll")
    72  
    73  	ProcGetSystemTimes                   = Modkernel32.NewProc("GetSystemTimes")
    74  	ProcNtQuerySystemInformation         = ModNt.NewProc("NtQuerySystemInformation")
    75  	ProcRtlGetNativeSystemInformation    = ModNt.NewProc("RtlGetNativeSystemInformation")
    76  	ProcRtlNtStatusToDosError            = ModNt.NewProc("RtlNtStatusToDosError")
    77  	ProcNtQueryInformationProcess        = ModNt.NewProc("NtQueryInformationProcess")
    78  	ProcNtReadVirtualMemory              = ModNt.NewProc("NtReadVirtualMemory")
    79  	ProcNtWow64QueryInformationProcess64 = ModNt.NewProc("NtWow64QueryInformationProcess64")
    80  	ProcNtWow64ReadVirtualMemory64       = ModNt.NewProc("NtWow64ReadVirtualMemory64")
    81  
    82  	PdhOpenQuery                = ModPdh.NewProc("PdhOpenQuery")
    83  	PdhAddEnglishCounterW       = ModPdh.NewProc("PdhAddEnglishCounterW")
    84  	PdhCollectQueryData         = ModPdh.NewProc("PdhCollectQueryData")
    85  	PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
    86  	PdhCloseQuery               = ModPdh.NewProc("PdhCloseQuery")
    87  
    88  	procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW")
    89  )
    90  
    91  type FILETIME struct {
    92  	DwLowDateTime  uint32
    93  	DwHighDateTime uint32
    94  }
    95  
    96  // borrowed from net/interface_windows.go
    97  func BytePtrToString(p *uint8) string {
    98  	a := (*[10000]uint8)(unsafe.Pointer(p))
    99  	i := 0
   100  	for a[i] != 0 {
   101  		i++
   102  	}
   103  	return string(a[:i])
   104  }
   105  
   106  // CounterInfo struct is used to track a windows performance counter
   107  // copied from https://github.com/mackerelio/mackerel-agent/
   108  type CounterInfo struct {
   109  	PostName    string
   110  	CounterName string
   111  	Counter     windows.Handle
   112  }
   113  
   114  // CreateQuery with a PdhOpenQuery call
   115  // copied from https://github.com/mackerelio/mackerel-agent/
   116  func CreateQuery() (windows.Handle, error) {
   117  	var query windows.Handle
   118  	r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query)))
   119  	if r != 0 {
   120  		return 0, err
   121  	}
   122  	return query, nil
   123  }
   124  
   125  // CreateCounter with a PdhAddEnglishCounterW call
   126  func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, error) {
   127  	var counter windows.Handle
   128  	r, _, err := PdhAddEnglishCounterW.Call(
   129  		uintptr(query),
   130  		uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cname))),
   131  		0,
   132  		uintptr(unsafe.Pointer(&counter)))
   133  	if r != 0 {
   134  		return nil, err
   135  	}
   136  	return &CounterInfo{
   137  		PostName:    pname,
   138  		CounterName: cname,
   139  		Counter:     counter,
   140  	}, nil
   141  }
   142  
   143  // GetCounterValue get counter value from handle
   144  // adapted from https://github.com/mackerelio/mackerel-agent/
   145  func GetCounterValue(counter windows.Handle) (float64, error) {
   146  	var value PDH_FMT_COUNTERVALUE_DOUBLE
   147  	r, _, err := PdhGetFormattedCounterValue.Call(uintptr(counter), PDH_FMT_DOUBLE, uintptr(0), uintptr(unsafe.Pointer(&value)))
   148  	if r != 0 && r != PDH_INVALID_DATA {
   149  		return 0.0, err
   150  	}
   151  	return value.DoubleValue, nil
   152  }
   153  
   154  type Win32PerformanceCounter struct {
   155  	PostName    string
   156  	CounterName string
   157  	Query       windows.Handle
   158  	Counter     windows.Handle
   159  }
   160  
   161  func NewWin32PerformanceCounter(postName, counterName string) (*Win32PerformanceCounter, error) {
   162  	query, err := CreateQuery()
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	counter := Win32PerformanceCounter{
   167  		Query:       query,
   168  		PostName:    postName,
   169  		CounterName: counterName,
   170  	}
   171  	r, _, err := PdhAddEnglishCounterW.Call(
   172  		uintptr(counter.Query),
   173  		uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(counter.CounterName))),
   174  		0,
   175  		uintptr(unsafe.Pointer(&counter.Counter)),
   176  	)
   177  	if r != 0 {
   178  		return nil, err
   179  	}
   180  	return &counter, nil
   181  }
   182  
   183  func (w *Win32PerformanceCounter) GetValue() (float64, error) {
   184  	r, _, err := PdhCollectQueryData.Call(uintptr(w.Query))
   185  	if r != 0 && err != nil {
   186  		if r == PDH_NO_DATA {
   187  			return 0.0, fmt.Errorf("%w: this counter has not data", err)
   188  		}
   189  		return 0.0, err
   190  	}
   191  
   192  	return GetCounterValue(w.Counter)
   193  }
   194  
   195  func ProcessorQueueLengthCounter() (*Win32PerformanceCounter, error) {
   196  	return NewWin32PerformanceCounter("processor_queue_length", `\System\Processor Queue Length`)
   197  }
   198  
   199  // WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging
   200  func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error {
   201  	if _, ok := ctx.Deadline(); !ok {
   202  		ctxTimeout, cancel := context.WithTimeout(ctx, Timeout)
   203  		defer cancel()
   204  		ctx = ctxTimeout
   205  	}
   206  
   207  	errChan := make(chan error, 1)
   208  	go func() {
   209  		errChan <- wmi.Query(query, dst, connectServerArgs...)
   210  	}()
   211  
   212  	select {
   213  	case <-ctx.Done():
   214  		return ctx.Err()
   215  	case err := <-errChan:
   216  		return err
   217  	}
   218  }
   219  
   220  // Convert paths using native DOS format like:
   221  //   "\Device\HarddiskVolume1\Windows\systemew\file.txt"
   222  // into:
   223  //   "C:\Windows\systemew\file.txt"
   224  func ConvertDOSPath(p string) string {
   225  	rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)
   226  
   227  	for d := 'A'; d <= 'Z'; d++ {
   228  		szDeviceName := string(d) + ":"
   229  		szTarget := make([]uint16, 512)
   230  		ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))),
   231  			uintptr(unsafe.Pointer(&szTarget[0])),
   232  			uintptr(len(szTarget)))
   233  		if ret != 0 && windows.UTF16ToString(szTarget[:]) == rawDrive {
   234  			return filepath.Join(szDeviceName, p[len(rawDrive):])
   235  		}
   236  	}
   237  	return p
   238  }
   239  
   240  type NtStatus uint32
   241  
   242  func (s NtStatus) Error() error {
   243  	if s == 0 {
   244  		return nil
   245  	}
   246  	return fmt.Errorf("NtStatus 0x%08x", uint32(s))
   247  }
   248  
   249  func (s NtStatus) IsError() bool {
   250  	return s>>30 == 3
   251  }
   252  
   253  type SystemExtendedHandleTableEntryInformation struct {
   254  	Object                uintptr
   255  	UniqueProcessId       uintptr
   256  	HandleValue           uintptr
   257  	GrantedAccess         uint32
   258  	CreatorBackTraceIndex uint16
   259  	ObjectTypeIndex       uint16
   260  	HandleAttributes      uint32
   261  	Reserved              uint32
   262  }
   263  
   264  type SystemExtendedHandleInformation struct {
   265  	NumberOfHandles uintptr
   266  	Reserved        uintptr
   267  	Handles         [1]SystemExtendedHandleTableEntryInformation
   268  }
   269  
   270  // CallWithExpandingBuffer https://github.com/hillu/go-ntdll
   271  func CallWithExpandingBuffer(fn func() NtStatus, buf *[]byte, resultLength *uint32) NtStatus {
   272  	for {
   273  		if st := fn(); st == STATUS_BUFFER_OVERFLOW || st == STATUS_BUFFER_TOO_SMALL || st == STATUS_INFO_LENGTH_MISMATCH {
   274  			if int(*resultLength) <= cap(*buf) {
   275  				(*reflect.SliceHeader)(unsafe.Pointer(buf)).Len = int(*resultLength)
   276  			} else {
   277  				*buf = make([]byte, int(*resultLength))
   278  			}
   279  			continue
   280  		} else {
   281  			if !st.IsError() {
   282  				*buf = (*buf)[:int(*resultLength)]
   283  			}
   284  			return st
   285  		}
   286  	}
   287  }
   288  
   289  func NtQuerySystemInformation(
   290  	SystemInformationClass uint32,
   291  	SystemInformation *byte,
   292  	SystemInformationLength uint32,
   293  	ReturnLength *uint32,
   294  ) NtStatus {
   295  	r0, _, _ := ProcNtQuerySystemInformation.Call(
   296  		uintptr(SystemInformationClass),
   297  		uintptr(unsafe.Pointer(SystemInformation)),
   298  		uintptr(SystemInformationLength),
   299  		uintptr(unsafe.Pointer(ReturnLength)))
   300  	return NtStatus(r0)
   301  }