1// +build windows
2
3package process
4
5import (
6	"bufio"
7	"context"
8	"errors"
9	"fmt"
10	"io"
11	"os"
12	"strings"
13	"syscall"
14	"unsafe"
15
16	"github.com/shirou/gopsutil/v3/cpu"
17	"github.com/shirou/gopsutil/v3/internal/common"
18	"github.com/shirou/gopsutil/v3/net"
19	"golang.org/x/sys/windows"
20)
21
22var (
23	modntdll             = windows.NewLazySystemDLL("ntdll.dll")
24	procNtResumeProcess  = modntdll.NewProc("NtResumeProcess")
25	procNtSuspendProcess = modntdll.NewProc("NtSuspendProcess")
26
27	modpsapi                     = windows.NewLazySystemDLL("psapi.dll")
28	procGetProcessMemoryInfo     = modpsapi.NewProc("GetProcessMemoryInfo")
29	procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW")
30
31	advapi32                  = windows.NewLazySystemDLL("advapi32.dll")
32	procLookupPrivilegeValue  = advapi32.NewProc("LookupPrivilegeValueW")
33	procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
34
35	procQueryFullProcessImageNameW = common.Modkernel32.NewProc("QueryFullProcessImageNameW")
36	procGetPriorityClass           = common.Modkernel32.NewProc("GetPriorityClass")
37	procGetProcessIoCounters       = common.Modkernel32.NewProc("GetProcessIoCounters")
38	procGetNativeSystemInfo        = common.Modkernel32.NewProc("GetNativeSystemInfo")
39
40	processorArchitecture uint
41)
42
43const processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION
44
45type systemProcessorInformation struct {
46	ProcessorArchitecture uint16
47	ProcessorLevel        uint16
48	ProcessorRevision     uint16
49	Reserved              uint16
50	ProcessorFeatureBits  uint16
51}
52
53type systemInfo struct {
54	wProcessorArchitecture      uint16
55	wReserved                   uint16
56	dwpageSize                  uint32
57	lpMinimumApplicationAddress uintptr
58	lpMaximumApplicationAddress uintptr
59	dwActiveProcessorMask       uintptr
60	dwNumberOfProcessors        uint32
61	dwProcessorType             uint32
62	dwAllocationGranularity     uint32
63	wProcessorLevel             uint16
64	wProcessorRevision          uint16
65}
66
67// Memory_info_ex is different between OSes
68type MemoryInfoExStat struct {
69}
70
71type MemoryMapsStat struct {
72}
73
74// ioCounters is an equivalent representation of IO_COUNTERS in the Windows API.
75// https://docs.microsoft.com/windows/win32/api/winnt/ns-winnt-io_counters
76type ioCounters struct {
77	ReadOperationCount  uint64
78	WriteOperationCount uint64
79	OtherOperationCount uint64
80	ReadTransferCount   uint64
81	WriteTransferCount  uint64
82	OtherTransferCount  uint64
83}
84
85type processBasicInformation32 struct {
86	Reserved1       uint32
87	PebBaseAddress  uint32
88	Reserved2       uint32
89	Reserved3       uint32
90	UniqueProcessId uint32
91	Reserved4       uint32
92}
93
94type processBasicInformation64 struct {
95	Reserved1       uint64
96	PebBaseAddress  uint64
97	Reserved2       uint64
98	Reserved3       uint64
99	UniqueProcessId uint64
100	Reserved4       uint64
101}
102
103type processEnvironmentBlock32 struct {
104	Reserved1 [2]uint8
105	BeingDebugged uint8
106	Reserved2 uint8
107	Reserved3 [2]uint32
108	Ldr uint32
109	ProcessParameters uint32
110	// More fields which we don't use so far
111}
112
113type processEnvironmentBlock64 struct {
114	Reserved1 [2]uint8
115	BeingDebugged uint8
116	Reserved2 uint8
117	_ [4]uint8 // padding, since we are 64 bit, the next pointer is 64 bit aligned (when compiling for 32 bit, this is not the case without manual padding)
118	Reserved3 [2]uint64
119	Ldr uint64
120	ProcessParameters uint64
121	// More fields which we don't use so far
122}
123
124type rtlUserProcessParameters32 struct {
125	Reserved1 [16]uint8
126	Reserved2 [10]uint32
127	ImagePathNameLength uint16
128	_ uint16
129	ImagePathAddress uint32
130	CommandLineLength uint16
131	_ uint16
132	CommandLineAddress uint32
133	EnvironmentAddress uint32
134	// More fields which we don't use so far
135}
136
137type rtlUserProcessParameters64 struct {
138	Reserved1 [16]uint8
139	Reserved2 [10]uint64
140	ImagePathNameLength uint16
141	_ uint16 // Max Length
142	_ uint32 // Padding
143	ImagePathAddress uint64
144	CommandLineLength uint16
145	_ uint16 // Max Length
146	_ uint32 // Padding
147	CommandLineAddress uint64
148	EnvironmentAddress uint64
149	// More fields which we don't use so far
150}
151
152type winLUID struct {
153	LowPart  winDWord
154	HighPart winLong
155}
156
157// LUID_AND_ATTRIBUTES
158type winLUIDAndAttributes struct {
159	Luid       winLUID
160	Attributes winDWord
161}
162
163// TOKEN_PRIVILEGES
164type winTokenPriviledges struct {
165	PrivilegeCount winDWord
166	Privileges     [1]winLUIDAndAttributes
167}
168
169type winLong int32
170type winDWord uint32
171
172func init() {
173	var systemInfo systemInfo
174
175	procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
176	processorArchitecture = uint(systemInfo.wProcessorArchitecture)
177
178	// enable SeDebugPrivilege https://github.com/midstar/proci/blob/6ec79f57b90ba3d9efa2a7b16ef9c9369d4be875/proci_windows.go#L80-L119
179	handle, err := syscall.GetCurrentProcess()
180	if err != nil {
181		return
182	}
183
184	var token syscall.Token
185	err = syscall.OpenProcessToken(handle, 0x0028, &token)
186	if err != nil {
187		return
188	}
189	defer token.Close()
190
191	tokenPriviledges := winTokenPriviledges{PrivilegeCount: 1}
192	lpName := syscall.StringToUTF16("SeDebugPrivilege")
193	ret, _, _ := procLookupPrivilegeValue.Call(
194		0,
195		uintptr(unsafe.Pointer(&lpName[0])),
196		uintptr(unsafe.Pointer(&tokenPriviledges.Privileges[0].Luid)))
197	if ret == 0 {
198		return
199	}
200
201	tokenPriviledges.Privileges[0].Attributes = 0x00000002 // SE_PRIVILEGE_ENABLED
202
203	procAdjustTokenPrivileges.Call(
204		uintptr(token),
205		0,
206		uintptr(unsafe.Pointer(&tokenPriviledges)),
207		uintptr(unsafe.Sizeof(tokenPriviledges)),
208		0,
209		0)
210}
211
212func pidsWithContext(ctx context.Context) ([]int32, error) {
213	// inspired by https://gist.github.com/henkman/3083408
214	// and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329
215	var ret []int32
216	var read uint32 = 0
217	var psSize uint32 = 1024
218	const dwordSize uint32 = 4
219
220	for {
221		ps := make([]uint32, psSize)
222		if err := windows.EnumProcesses(ps, &read); err != nil {
223			return nil, err
224		}
225		if uint32(len(ps)) == read { // ps buffer was too small to host every results, retry with a bigger one
226			psSize += 1024
227			continue
228		}
229		for _, pid := range ps[:read/dwordSize] {
230			ret = append(ret, int32(pid))
231		}
232		return ret, nil
233
234	}
235
236}
237
238func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
239	if pid == 0 { // special case for pid 0 System Idle Process
240		return true, nil
241	}
242	if pid < 0 {
243		return false, fmt.Errorf("invalid pid %v", pid)
244	}
245	if pid%4 != 0 {
246		// OpenProcess will succeed even on non-existing pid here https://devblogs.microsoft.com/oldnewthing/20080606-00/?p=22043
247		// so we list every pid just to be sure and be future-proof
248		pids, err := PidsWithContext(ctx)
249		if err != nil {
250			return false, err
251		}
252		for _, i := range pids {
253			if i == pid {
254				return true, err
255			}
256		}
257		return false, err
258	}
259	const STILL_ACTIVE = 259 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
260	h, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
261	if err == windows.ERROR_ACCESS_DENIED {
262		return true, nil
263	}
264	if err == windows.ERROR_INVALID_PARAMETER {
265		return false, nil
266	}
267	if err != nil {
268		return false, err
269	}
270	defer syscall.CloseHandle(syscall.Handle(h))
271	var exitCode uint32
272	err = windows.GetExitCodeProcess(h, &exitCode)
273	return exitCode == STILL_ACTIVE, err
274}
275
276func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
277	// if cached already, return from cache
278	cachedPpid := p.getPpid()
279	if cachedPpid != 0 {
280		return cachedPpid, nil
281	}
282
283	ppid, _, _, err := getFromSnapProcess(p.Pid)
284	if err != nil {
285		return 0, err
286	}
287
288	// no errors and not cached already, so cache it
289	p.setPpid(ppid)
290
291	return ppid, nil
292}
293
294func (p *Process) NameWithContext(ctx context.Context) (string, error) {
295	ppid, _, name, err := getFromSnapProcess(p.Pid)
296	if err != nil {
297		return "", fmt.Errorf("could not get Name: %s", err)
298	}
299
300	// if no errors and not cached already, cache ppid
301	p.parent = ppid
302	if 0 == p.getPpid() {
303		p.setPpid(ppid)
304	}
305
306	return name, nil
307}
308
309func (p *Process) TgidWithContext(ctx context.Context) (int32, error) {
310	return 0, common.ErrNotImplementedError
311}
312
313func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
314	c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid))
315	if err != nil {
316		return "", err
317	}
318	defer windows.CloseHandle(c)
319	buf := make([]uint16, syscall.MAX_LONG_PATH)
320	size := uint32(syscall.MAX_LONG_PATH)
321	if err := procQueryFullProcessImageNameW.Find(); err == nil { // Vista+
322		ret, _, err := procQueryFullProcessImageNameW.Call(
323			uintptr(c),
324			uintptr(0),
325			uintptr(unsafe.Pointer(&buf[0])),
326			uintptr(unsafe.Pointer(&size)))
327		if ret == 0 {
328			return "", err
329		}
330		return windows.UTF16ToString(buf[:]), nil
331	}
332	// XP fallback
333	ret, _, err := procGetProcessImageFileNameW.Call(uintptr(c), uintptr(unsafe.Pointer(&buf[0])), uintptr(size))
334	if ret == 0 {
335		return "", err
336	}
337	return common.ConvertDOSPath(windows.UTF16ToString(buf[:])), nil
338}
339
340func (p *Process) CmdlineWithContext(_ context.Context) (string, error) {
341	cmdline, err := getProcessCommandLine(p.Pid)
342	if err != nil {
343		return "", fmt.Errorf("could not get CommandLine: %s", err)
344	}
345	return cmdline, nil
346}
347
348func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
349	cmdline, err := p.CmdlineWithContext(ctx)
350	if err != nil {
351		return nil, err
352	}
353	return strings.Split(cmdline, " "), nil
354}
355
356func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
357	ru, err := getRusage(p.Pid)
358	if err != nil {
359		return 0, fmt.Errorf("could not get CreationDate: %s", err)
360	}
361
362	return ru.CreationTime.Nanoseconds() / 1000000, nil
363}
364
365func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
366	return "", common.ErrNotImplementedError
367}
368
369func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
370	ppid, err := p.PpidWithContext(ctx)
371	if err != nil {
372		return nil, fmt.Errorf("could not get ParentProcessID: %s", err)
373	}
374
375	return NewProcessWithContext(ctx, ppid)
376}
377
378func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) {
379	return []string{""}, common.ErrNotImplementedError
380}
381
382func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
383	return false, common.ErrNotImplementedError
384}
385
386func (p *Process) UsernameWithContext(ctx context.Context) (string, error) {
387	pid := p.Pid
388	c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
389	if err != nil {
390		return "", err
391	}
392	defer windows.CloseHandle(c)
393
394	var token syscall.Token
395	err = syscall.OpenProcessToken(syscall.Handle(c), syscall.TOKEN_QUERY, &token)
396	if err != nil {
397		return "", err
398	}
399	defer token.Close()
400	tokenUser, err := token.GetTokenUser()
401	if err != nil {
402		return "", err
403	}
404
405	user, domain, _, err := tokenUser.User.Sid.LookupAccount("")
406	return domain + "\\" + user, err
407}
408
409func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
410	return nil, common.ErrNotImplementedError
411}
412
413func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
414	return nil, common.ErrNotImplementedError
415}
416
417func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) {
418	return nil, common.ErrNotImplementedError
419}
420
421func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
422	return "", common.ErrNotImplementedError
423}
424
425// priorityClasses maps a win32 priority class to its WMI equivalent Win32_Process.Priority
426// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass
427// https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
428var priorityClasses = map[int]int32{
429	0x00008000: 10, // ABOVE_NORMAL_PRIORITY_CLASS
430	0x00004000: 6,  // BELOW_NORMAL_PRIORITY_CLASS
431	0x00000080: 13, // HIGH_PRIORITY_CLASS
432	0x00000040: 4,  // IDLE_PRIORITY_CLASS
433	0x00000020: 8,  // NORMAL_PRIORITY_CLASS
434	0x00000100: 24, // REALTIME_PRIORITY_CLASS
435}
436
437func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
438	c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid))
439	if err != nil {
440		return 0, err
441	}
442	defer windows.CloseHandle(c)
443	ret, _, err := procGetPriorityClass.Call(uintptr(c))
444	if ret == 0 {
445		return 0, err
446	}
447	priority, ok := priorityClasses[int(ret)]
448	if !ok {
449		return 0, fmt.Errorf("unknown priority class %v", ret)
450	}
451	return priority, nil
452}
453
454func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) {
455	return 0, common.ErrNotImplementedError
456}
457
458func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) {
459	return nil, common.ErrNotImplementedError
460}
461
462func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) {
463	return nil, common.ErrNotImplementedError
464}
465
466func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
467	c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid))
468	if err != nil {
469		return nil, err
470	}
471	defer windows.CloseHandle(c)
472	var ioCounters ioCounters
473	ret, _, err := procGetProcessIoCounters.Call(uintptr(c), uintptr(unsafe.Pointer(&ioCounters)))
474	if ret == 0 {
475		return nil, err
476	}
477	stats := &IOCountersStat{
478		ReadCount:  ioCounters.ReadOperationCount,
479		ReadBytes:  ioCounters.ReadTransferCount,
480		WriteCount: ioCounters.WriteOperationCount,
481		WriteBytes: ioCounters.WriteTransferCount,
482	}
483
484	return stats, nil
485}
486
487func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) {
488	return nil, common.ErrNotImplementedError
489}
490
491func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) {
492	return 0, common.ErrNotImplementedError
493}
494
495func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
496	ppid, ret, _, err := getFromSnapProcess(p.Pid)
497	if err != nil {
498		return 0, err
499	}
500
501	// if no errors and not cached already, cache ppid
502	p.parent = ppid
503	if 0 == p.getPpid() {
504		p.setPpid(ppid)
505	}
506
507	return ret, nil
508}
509
510func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) {
511	return nil, common.ErrNotImplementedError
512}
513
514func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
515	sysTimes, err := getProcessCPUTimes(p.Pid)
516	if err != nil {
517		return nil, err
518	}
519
520	// User and kernel times are represented as a FILETIME structure
521	// which contains a 64-bit value representing the number of
522	// 100-nanosecond intervals since January 1, 1601 (UTC):
523	// http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx
524	// To convert it into a float representing the seconds that the
525	// process has executed in user/kernel mode I borrowed the code
526	// below from psutil's _psutil_windows.c, and in turn from Python's
527	// Modules/posixmodule.c
528
529	user := float64(sysTimes.UserTime.HighDateTime)*429.4967296 + float64(sysTimes.UserTime.LowDateTime)*1e-7
530	kernel := float64(sysTimes.KernelTime.HighDateTime)*429.4967296 + float64(sysTimes.KernelTime.LowDateTime)*1e-7
531
532	return &cpu.TimesStat{
533		User:   user,
534		System: kernel,
535	}, nil
536}
537
538func (p *Process) CPUAffinityWithContext(ctx context.Context) ([]int32, error) {
539	return nil, common.ErrNotImplementedError
540}
541
542func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
543	mem, err := getMemoryInfo(p.Pid)
544	if err != nil {
545		return nil, err
546	}
547
548	ret := &MemoryInfoStat{
549		RSS: uint64(mem.WorkingSetSize),
550		VMS: uint64(mem.PagefileUsage),
551	}
552
553	return ret, nil
554}
555
556func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) {
557	return nil, common.ErrNotImplementedError
558}
559
560func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
561	return nil, common.ErrNotImplementedError
562}
563
564func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
565	out := []*Process{}
566	snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(0))
567	if err != nil {
568		return out, err
569	}
570	defer windows.CloseHandle(snap)
571	var pe32 windows.ProcessEntry32
572	pe32.Size = uint32(unsafe.Sizeof(pe32))
573	if err := windows.Process32First(snap, &pe32); err != nil {
574		return out, err
575	}
576	for {
577		if pe32.ParentProcessID == uint32(p.Pid) {
578			p, err := NewProcessWithContext(ctx, int32(pe32.ProcessID))
579			if err == nil {
580				out = append(out, p)
581			}
582		}
583		if err = windows.Process32Next(snap, &pe32); err != nil {
584			break
585		}
586	}
587	return out, nil
588}
589
590func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) {
591	return nil, common.ErrNotImplementedError
592}
593
594func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
595	return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
596}
597
598func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
599	return nil, common.ErrNotImplementedError
600}
601
602func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) {
603	return nil, common.ErrNotImplementedError
604}
605
606func (p *Process) SendSignalWithContext(ctx context.Context, sig syscall.Signal) error {
607	return common.ErrNotImplementedError
608}
609
610func (p *Process) SuspendWithContext(ctx context.Context) error {
611	c, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(p.Pid))
612	if err != nil {
613		return err
614	}
615	defer windows.CloseHandle(c)
616
617	r1, _, _ := procNtSuspendProcess.Call(uintptr(c))
618	if r1 != 0 {
619		// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
620		return fmt.Errorf("NtStatus='0x%.8X'", r1)
621	}
622
623	return nil
624}
625
626func (p *Process) ResumeWithContext(ctx context.Context) error {
627	c, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(p.Pid))
628	if err != nil {
629		return err
630	}
631	defer windows.CloseHandle(c)
632
633	r1, _, _ := procNtResumeProcess.Call(uintptr(c))
634	if r1 != 0 {
635		// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
636		return fmt.Errorf("NtStatus='0x%.8X'", r1)
637	}
638
639	return nil
640}
641
642func (p *Process) TerminateWithContext(ctx context.Context) error {
643	proc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, uint32(p.Pid))
644	if err != nil {
645		return err
646	}
647	err = windows.TerminateProcess(proc, 0)
648	windows.CloseHandle(proc)
649	return err
650}
651
652func (p *Process) KillWithContext(ctx context.Context) error {
653	process := os.Process{Pid: int(p.Pid)}
654	return process.Kill()
655}
656
657func (p *Process) EnvironWithContext(ctx context.Context) ([]string, error) {
658	envVars, err := getProcessEnvironmentVariables(p.Pid, ctx)
659	if err != nil {
660		return nil, fmt.Errorf("could not get environment variables: %s", err)
661	}
662	return envVars, nil
663}
664
665// retrieve Ppid in a thread-safe manner
666func (p *Process) getPpid() int32 {
667	p.parentMutex.RLock()
668	defer p.parentMutex.RUnlock()
669	return p.parent
670}
671
672// cache Ppid in a thread-safe manner (WINDOWS ONLY)
673// see https://psutil.readthedocs.io/en/latest/#psutil.Process.ppid
674func (p *Process) setPpid(ppid int32) {
675	p.parentMutex.Lock()
676	defer p.parentMutex.Unlock()
677	p.parent = ppid
678}
679
680func getFromSnapProcess(pid int32) (int32, int32, string, error) {
681	snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(pid))
682	if err != nil {
683		return 0, 0, "", err
684	}
685	defer windows.CloseHandle(snap)
686	var pe32 windows.ProcessEntry32
687	pe32.Size = uint32(unsafe.Sizeof(pe32))
688	if err = windows.Process32First(snap, &pe32); err != nil {
689		return 0, 0, "", err
690	}
691	for {
692		if pe32.ProcessID == uint32(pid) {
693			szexe := windows.UTF16ToString(pe32.ExeFile[:])
694			return int32(pe32.ParentProcessID), int32(pe32.Threads), szexe, nil
695		}
696		if err = windows.Process32Next(snap, &pe32); err != nil {
697			break
698		}
699	}
700	return 0, 0, "", fmt.Errorf("couldn't find pid: %d", pid)
701}
702
703func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
704	out := []*Process{}
705
706	pids, err := PidsWithContext(ctx)
707	if err != nil {
708		return out, fmt.Errorf("could not get Processes %s", err)
709	}
710
711	for _, pid := range pids {
712		p, err := NewProcessWithContext(ctx, pid)
713		if err != nil {
714			continue
715		}
716		out = append(out, p)
717	}
718
719	return out, nil
720}
721
722func getRusage(pid int32) (*windows.Rusage, error) {
723	var CPU windows.Rusage
724
725	c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
726	if err != nil {
727		return nil, err
728	}
729	defer windows.CloseHandle(c)
730
731	if err := windows.GetProcessTimes(c, &CPU.CreationTime, &CPU.ExitTime, &CPU.KernelTime, &CPU.UserTime); err != nil {
732		return nil, err
733	}
734
735	return &CPU, nil
736}
737
738func getMemoryInfo(pid int32) (PROCESS_MEMORY_COUNTERS, error) {
739	var mem PROCESS_MEMORY_COUNTERS
740	c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
741	if err != nil {
742		return mem, err
743	}
744	defer windows.CloseHandle(c)
745	if err := getProcessMemoryInfo(c, &mem); err != nil {
746		return mem, err
747	}
748
749	return mem, err
750}
751
752func getProcessMemoryInfo(h windows.Handle, mem *PROCESS_MEMORY_COUNTERS) (err error) {
753	r1, _, e1 := syscall.Syscall(procGetProcessMemoryInfo.Addr(), 3, uintptr(h), uintptr(unsafe.Pointer(mem)), uintptr(unsafe.Sizeof(*mem)))
754	if r1 == 0 {
755		if e1 != 0 {
756			err = error(e1)
757		} else {
758			err = syscall.EINVAL
759		}
760	}
761	return
762}
763
764type SYSTEM_TIMES struct {
765	CreateTime syscall.Filetime
766	ExitTime   syscall.Filetime
767	KernelTime syscall.Filetime
768	UserTime   syscall.Filetime
769}
770
771func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) {
772	var times SYSTEM_TIMES
773
774	h, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
775	if err != nil {
776		return times, err
777	}
778	defer windows.CloseHandle(h)
779
780	err = syscall.GetProcessTimes(
781		syscall.Handle(h),
782		&times.CreateTime,
783		&times.ExitTime,
784		&times.KernelTime,
785		&times.UserTime,
786	)
787
788	return times, err
789}
790
791
792func getUserProcessParams32(handle windows.Handle) (rtlUserProcessParameters32, error) {
793	pebAddress, err := queryPebAddress(syscall.Handle(handle), true)
794	if err != nil {
795		return rtlUserProcessParameters32{}, fmt.Errorf("cannot locate process PEB: %w", err)
796	}
797
798	buf := readProcessMemory(syscall.Handle(handle), true, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock32{})))
799	if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock32{})) {
800		return rtlUserProcessParameters32{}, fmt.Errorf("cannot read process PEB")
801	}
802	peb := (*processEnvironmentBlock32)(unsafe.Pointer(&buf[0]))
803	userProcessAddress := uint64(peb.ProcessParameters)
804	buf = readProcessMemory(syscall.Handle(handle), true, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters32{})))
805	if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters32{})) {
806		return rtlUserProcessParameters32{}, fmt.Errorf("cannot read user process parameters")
807	}
808	return *(*rtlUserProcessParameters32)(unsafe.Pointer(&buf[0])), nil
809}
810
811func getUserProcessParams64(handle windows.Handle) (rtlUserProcessParameters64, error) {
812	pebAddress, err := queryPebAddress(syscall.Handle(handle), false)
813	if err != nil {
814		return rtlUserProcessParameters64{}, fmt.Errorf("cannot locate process PEB: %w", err)
815	}
816
817	buf := readProcessMemory(syscall.Handle(handle), false, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock64{})))
818	if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock64{})) {
819		return rtlUserProcessParameters64{}, fmt.Errorf("cannot read process PEB")
820	}
821	peb := (*processEnvironmentBlock64)(unsafe.Pointer(&buf[0]))
822	userProcessAddress := peb.ProcessParameters
823	buf = readProcessMemory(syscall.Handle(handle), false, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters64{})))
824	if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters64{})) {
825		return rtlUserProcessParameters64{}, fmt.Errorf("cannot read user process parameters")
826	}
827	return *(*rtlUserProcessParameters64)(unsafe.Pointer(&buf[0])), nil
828}
829
830func is32BitProcess(h windows.Handle) bool {
831	const (
832		PROCESSOR_ARCHITECTURE_INTEL = 0
833		PROCESSOR_ARCHITECTURE_ARM   = 5
834		PROCESSOR_ARCHITECTURE_ARM64 = 12
835		PROCESSOR_ARCHITECTURE_IA64  = 6
836		PROCESSOR_ARCHITECTURE_AMD64 = 9
837	)
838
839	var procIs32Bits bool
840	switch processorArchitecture {
841	case PROCESSOR_ARCHITECTURE_INTEL:
842		fallthrough
843	case PROCESSOR_ARCHITECTURE_ARM:
844		procIs32Bits = true
845	case PROCESSOR_ARCHITECTURE_ARM64:
846		fallthrough
847	case PROCESSOR_ARCHITECTURE_IA64:
848		fallthrough
849	case PROCESSOR_ARCHITECTURE_AMD64:
850		var wow64 uint
851
852		ret, _, _ := common.ProcNtQueryInformationProcess.Call(
853			uintptr(h),
854			uintptr(common.ProcessWow64Information),
855			uintptr(unsafe.Pointer(&wow64)),
856			uintptr(unsafe.Sizeof(wow64)),
857			uintptr(0),
858		)
859		if int(ret) >= 0 {
860			if wow64 != 0 {
861				procIs32Bits = true
862			}
863		} else {
864			//if the OS does not support the call, we fallback into the bitness of the app
865			if unsafe.Sizeof(wow64) == 4 {
866				procIs32Bits = true
867			}
868		}
869
870	default:
871		//for other unknown platforms, we rely on process platform
872		if unsafe.Sizeof(processorArchitecture) == 8 {
873			procIs32Bits = false
874		} else {
875			procIs32Bits = true
876		}
877	}
878	return procIs32Bits
879}
880
881func getProcessEnvironmentVariables(pid int32, ctx context.Context) ([]string, error) {
882	h, err := windows.OpenProcess(processQueryInformation|windows.PROCESS_VM_READ, false, uint32(pid))
883	if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER {
884		return nil, nil
885	}
886	if err != nil {
887		return nil, err
888	}
889	defer syscall.CloseHandle(syscall.Handle(h))
890
891	procIs32Bits := is32BitProcess(h)
892
893	var processParameterBlockAddress uint64
894
895	if procIs32Bits {
896		peb, err := getUserProcessParams32(h)
897		if err != nil {
898			return nil, err
899		}
900		processParameterBlockAddress = uint64(peb.EnvironmentAddress)
901	} else {
902		peb, err := getUserProcessParams64(h)
903		if err != nil {
904			return nil, err
905		}
906		processParameterBlockAddress = peb.EnvironmentAddress
907	}
908	envvarScanner := bufio.NewScanner(&processReader{
909		processHandle:  h,
910		is32BitProcess: procIs32Bits,
911		offset:         processParameterBlockAddress,
912	})
913	envvarScanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error){
914		if atEOF && len(data) == 0 {
915			return 0, nil, nil
916		}
917		// Check for UTF-16 zero character
918		for i := 0; i < len(data) - 1; i+=2 {
919			if data[i] == 0 && data[i+1] == 0 {
920				return i+2, data[0:i], nil
921			}
922		}
923		if atEOF {
924			return len(data), data, nil
925		}
926		// Request more data
927		return 0, nil, nil
928	})
929	var envVars []string
930	for envvarScanner.Scan() {
931		entry := envvarScanner.Bytes()
932		if len(entry) == 0 {
933			break // Block is finished
934		}
935		envVars = append(envVars, convertUTF16ToString(entry))
936		select {
937		case <-ctx.Done():
938			break
939		default:
940			continue
941		}
942	}
943	if err := envvarScanner.Err(); err != nil {
944		return nil, err
945	}
946	return envVars, nil
947}
948
949type processReader struct {
950	processHandle windows.Handle
951	is32BitProcess bool
952	offset uint64
953}
954
955func (p *processReader) Read(buf []byte) (int, error) {
956	processMemory := readProcessMemory(syscall.Handle(p.processHandle), p.is32BitProcess, p.offset, uint(len(buf)))
957	if len(processMemory) == 0 {
958		return 0, io.EOF
959	}
960	copy(buf, processMemory)
961	p.offset += uint64(len(processMemory))
962	return len(processMemory), nil
963}
964
965func getProcessCommandLine(pid int32) (string, error) {
966	h, err := windows.OpenProcess(processQueryInformation|windows.PROCESS_VM_READ, false, uint32(pid))
967	if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER {
968		return "", nil
969	}
970	if err != nil {
971		return "", err
972	}
973	defer syscall.CloseHandle(syscall.Handle(h))
974
975	procIs32Bits := is32BitProcess(h)
976
977	if procIs32Bits {
978		userProcParams, err := getUserProcessParams32(h)
979		if err != nil {
980			return "", err
981		}
982		if userProcParams.CommandLineLength > 0 {
983			cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CommandLineAddress), uint(userProcParams.CommandLineLength))
984			if len(cmdLine) != int(userProcParams.CommandLineLength) {
985				return "", errors.New("cannot read cmdline")
986			}
987
988			return convertUTF16ToString(cmdLine), nil
989		}
990	} else {
991		userProcParams, err := getUserProcessParams64(h)
992		if err != nil {
993			return "", err
994		}
995		if userProcParams.CommandLineLength > 0 {
996			cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CommandLineAddress, uint(userProcParams.CommandLineLength))
997			if len(cmdLine) != int(userProcParams.CommandLineLength) {
998				return "", errors.New("cannot read cmdline")
999			}
1000
1001			return convertUTF16ToString(cmdLine), nil
1002		}
1003	}
1004
1005	//if we reach here, we have no command line
1006	return "", nil
1007}
1008
1009func convertUTF16ToString(src []byte) string {
1010	srcLen := len(src) / 2
1011
1012	codePoints := make([]uint16, srcLen)
1013
1014	srcIdx := 0
1015	for i := 0; i < srcLen; i++ {
1016		codePoints[i] = uint16(src[srcIdx]) | uint16(src[srcIdx+1])<<8
1017		srcIdx += 2
1018	}
1019	return syscall.UTF16ToString(codePoints)
1020}
1021