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 }