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