common_windows.go 8.0 KB

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