1use chrono::offset::TimeZone;
5use chrono::{Local, NaiveDate};
6use libc::c_void;
7
8use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS;
9use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32};
10
11use std::cell::RefCell;
12use std::collections::HashMap;
13use std::ffi::OsString;
14use std::mem::{MaybeUninit, size_of, zeroed};
15use std::os::windows::ffi::OsStringExt;
16use std::path::PathBuf;
17use std::ptr;
18use std::ptr::null_mut;
19use std::sync::LazyLock;
20use std::thread;
21use std::time::Duration;
22use web_time::Instant;
23
24use windows::core::{PCWSTR, PWSTR};
25
26use windows::Wdk::System::SystemServices::RtlGetVersion;
27use windows::Wdk::System::Threading::{
28 NtQueryInformationProcess, PROCESSINFOCLASS, ProcessBasicInformation,
29 ProcessCommandLineInformation, ProcessWow64Information,
30};
31
32use windows::Win32::Foundation::{
33 CloseHandle, FALSE, FILETIME, HANDLE, HLOCAL, HMODULE, LocalFree, MAX_PATH,
34 STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING,
35};
36
37use windows::Win32::Security::{
38 AdjustTokenPrivileges, GetTokenInformation, LookupAccountSidW, LookupPrivilegeValueW, PSID,
39 SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED, SID, SID_NAME_USE, TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS,
40 TOKEN_PRIVILEGES, TOKEN_QUERY, TOKEN_USER, TokenGroups, TokenUser,
41};
42
43use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
44use windows::Win32::System::Diagnostics::ToolHelp::{
45 CreateToolhelp32Snapshot, PROCESSENTRY32, Process32First, Process32Next, TH32CS_SNAPPROCESS,
46};
47
48use windows::Win32::System::Memory::{MEMORY_BASIC_INFORMATION, VirtualQueryEx};
49
50use windows::Win32::System::ProcessStatus::{
51 GetModuleBaseNameW, GetProcessMemoryInfo, K32EnumProcesses, PROCESS_MEMORY_COUNTERS,
52 PROCESS_MEMORY_COUNTERS_EX,
53};
54
55use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW;
56
57use windows::Win32::System::Threading::{
58 GetCurrentProcess, GetPriorityClass, GetProcessIoCounters, GetProcessTimes, IO_COUNTERS,
59 OpenProcess, OpenProcessToken, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION,
60 PROCESS_VM_READ,
61};
62
63use windows::Win32::UI::Shell::CommandLineToArgvW;
64
65pub struct ProcessInfo {
66 pub pid: i32,
67 pub command: String,
68 pub ppid: i32,
69 pub start_time: chrono::DateTime<chrono::Local>,
70 pub cpu_info: CpuInfo,
71 pub memory_info: MemoryInfo,
72 pub disk_info: DiskInfo,
73 pub user: SidName,
74 pub groups: Vec<SidName>,
75 pub priority: u32,
76 pub thread: i32,
77 pub interval: Duration,
78 pub cmd: Vec<String>,
79 pub environ: Vec<String>,
80 pub cwd: PathBuf,
81}
82
83#[derive(Default)]
84pub struct MemoryInfo {
85 pub page_fault_count: u64,
86 pub peak_working_set_size: u64,
87 pub working_set_size: u64,
88 pub quota_peak_paged_pool_usage: u64,
89 pub quota_paged_pool_usage: u64,
90 pub quota_peak_non_paged_pool_usage: u64,
91 pub quota_non_paged_pool_usage: u64,
92 pub page_file_usage: u64,
93 pub peak_page_file_usage: u64,
94 pub private_usage: u64,
95}
96
97#[derive(Default)]
98pub struct DiskInfo {
99 pub prev_read: u64,
100 pub prev_write: u64,
101 pub curr_read: u64,
102 pub curr_write: u64,
103}
104
105#[derive(Default)]
106pub struct CpuInfo {
107 pub prev_sys: u64,
108 pub prev_user: u64,
109 pub curr_sys: u64,
110 pub curr_user: u64,
111}
112
113pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
114 let mut base_procs = Vec::new();
115 let mut ret = Vec::new();
116
117 let _ = set_privilege();
118
119 for pid in get_pids() {
120 let handle = get_handle(pid);
121
122 if let Some(handle) = handle {
123 let times = get_times(handle);
124 let io = get_io(handle);
125
126 let time = Instant::now();
127
128 if let (Some((_, _, sys, user)), Some((read, write))) = (times, io) {
129 base_procs.push((pid, sys, user, read, write, time));
130 }
131 }
132 }
133
134 thread::sleep(interval);
135
136 let (mut ppids, mut threads) = get_ppid_threads();
137
138 for (pid, prev_sys, prev_user, prev_read, prev_write, prev_time) in base_procs {
139 let ppid = ppids.remove(&pid);
140 let thread = threads.remove(&pid);
141 let handle = get_handle(pid);
142
143 if let Some(handle) = handle {
144 let command = get_command(handle);
145 let memory_info = get_memory_info(handle);
146 let times = get_times(handle);
147 let io = get_io(handle);
148
149 let start_time = if let Some((start, _, _, _)) = times {
150 let Some(time) = chrono::Duration::try_seconds(start as i64 / 10_000_000) else {
153 continue;
154 };
155 let base =
156 NaiveDate::from_ymd_opt(1601, 1, 1).and_then(|nd| nd.and_hms_opt(0, 0, 0));
157 if let Some(base) = base {
158 let time = base + time;
159 Local.from_utc_datetime(&time)
160 } else {
161 continue;
162 }
163 } else {
164 let time =
165 NaiveDate::from_ymd_opt(1601, 1, 1).and_then(|nt| nt.and_hms_opt(0, 0, 0));
166 if let Some(time) = time {
167 Local.from_utc_datetime(&time)
168 } else {
169 continue;
170 }
171 };
172
173 let cpu_info = if let Some((_, _, curr_sys, curr_user)) = times {
174 Some(CpuInfo {
175 prev_sys,
176 prev_user,
177 curr_sys,
178 curr_user,
179 })
180 } else {
181 None
182 };
183
184 let disk_info = if let Some((curr_read, curr_write)) = io {
185 Some(DiskInfo {
186 prev_read,
187 prev_write,
188 curr_read,
189 curr_write,
190 })
191 } else {
192 None
193 };
194
195 let user = get_user(handle);
196 let groups = get_groups(handle);
197
198 let priority = get_priority(handle);
199
200 let curr_time = Instant::now();
201 let interval = curr_time.saturating_duration_since(prev_time);
202
203 let mut all_ok = true;
204 all_ok &= command.is_some();
205 all_ok &= cpu_info.is_some();
206 all_ok &= memory_info.is_some();
207 all_ok &= disk_info.is_some();
208 all_ok &= user.is_some();
209 all_ok &= groups.is_some();
210 all_ok &= thread.is_some();
211
212 if all_ok {
213 let (proc_cmd, proc_env, proc_cwd) = match unsafe { get_process_params(handle) } {
214 Ok(pp) => (pp.0, pp.1, pp.2),
215 Err(_) => (vec![], vec![], PathBuf::new()),
216 };
217 let command = command.unwrap_or_default();
218 let ppid = ppid.unwrap_or(0);
219 let cpu_info = cpu_info.unwrap_or_default();
220 let memory_info = memory_info.unwrap_or_default();
221 let disk_info = disk_info.unwrap_or_default();
222 let user = user.unwrap_or_else(|| SidName {
223 sid: vec![],
224 name: None,
225 domainname: None,
226 });
227 let groups = groups.unwrap_or_default();
228 let thread = thread.unwrap_or_default();
229
230 let proc = ProcessInfo {
231 pid,
232 command,
233 ppid,
234 start_time,
235 cpu_info,
236 memory_info,
237 disk_info,
238 user,
239 groups,
240 priority,
241 thread,
242 interval,
243 cmd: proc_cmd,
244 environ: proc_env,
245 cwd: proc_cwd,
246 };
247
248 ret.push(proc);
249 }
250
251 unsafe {
252 let _ = CloseHandle(handle);
253 }
254 }
255 }
256
257 ret
258}
259
260fn set_privilege() -> bool {
261 unsafe {
262 let handle = GetCurrentProcess();
263 let mut token: HANDLE = zeroed();
264 let ret = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, &mut token);
265 if ret.is_err() {
266 return false;
267 }
268
269 let mut tps: TOKEN_PRIVILEGES = zeroed();
270 tps.PrivilegeCount = 1;
271 if LookupPrivilegeValueW(PCWSTR::null(), SE_DEBUG_NAME, &mut tps.Privileges[0].Luid)
272 .is_err()
273 {
274 return false;
275 }
276
277 tps.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
278 if AdjustTokenPrivileges(token, FALSE.into(), Some(&tps), 0, None, None).is_err() {
279 return false;
280 }
281
282 true
283 }
284}
285
286fn get_pids() -> Vec<i32> {
287 let dword_size = size_of::<u32>();
288 let mut pids: Vec<u32> = Vec::with_capacity(10192);
289 let mut cb_needed = 0;
290
291 unsafe {
292 pids.set_len(10192);
293 let result = K32EnumProcesses(
294 pids.as_mut_ptr(),
295 (dword_size * pids.len()) as u32,
296 &mut cb_needed,
297 );
298 if !result.as_bool() {
299 return Vec::new();
300 }
301 let pids_len = cb_needed / dword_size as u32;
302 pids.set_len(pids_len as usize);
303 }
304
305 pids.iter().map(|x| *x as i32).collect()
306}
307
308fn get_ppid_threads() -> (HashMap<i32, i32>, HashMap<i32, i32>) {
309 let mut ppids = HashMap::new();
310 let mut threads = HashMap::new();
311
312 unsafe {
313 let Ok(snapshot) = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) else {
314 return (ppids, threads);
315 };
316 let mut entry: PROCESSENTRY32 = zeroed();
317 entry.dwSize = size_of::<PROCESSENTRY32>() as u32;
318 let mut not_the_end = Process32First(snapshot, &mut entry);
319
320 while not_the_end.is_ok() {
321 ppids.insert(entry.th32ProcessID as i32, entry.th32ParentProcessID as i32);
322 threads.insert(entry.th32ProcessID as i32, entry.cntThreads as i32);
323 not_the_end = Process32Next(snapshot, &mut entry);
324 }
325
326 let _ = CloseHandle(snapshot);
327 }
328
329 (ppids, threads)
330}
331
332fn get_handle(pid: i32) -> Option<HANDLE> {
333 if pid == 0 {
334 return None;
335 }
336
337 let handle = unsafe {
338 OpenProcess(
339 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
340 FALSE.into(),
341 pid as u32,
342 )
343 }
344 .ok();
345
346 match handle {
347 Some(h) if h.is_invalid() => None,
348 h => h,
349 }
350}
351
352fn get_times(handle: HANDLE) -> Option<(u64, u64, u64, u64)> {
353 unsafe {
354 let mut start: FILETIME = zeroed();
355 let mut exit: FILETIME = zeroed();
356 let mut sys: FILETIME = zeroed();
357 let mut user: FILETIME = zeroed();
358
359 let ret = GetProcessTimes(
360 handle,
361 &mut start as *mut FILETIME,
362 &mut exit as *mut FILETIME,
363 &mut sys as *mut FILETIME,
364 &mut user as *mut FILETIME,
365 );
366
367 let start = (u64::from(start.dwHighDateTime) << 32) | u64::from(start.dwLowDateTime);
368 let exit = (u64::from(exit.dwHighDateTime) << 32) | u64::from(exit.dwLowDateTime);
369 let sys = (u64::from(sys.dwHighDateTime) << 32) | u64::from(sys.dwLowDateTime);
370 let user = (u64::from(user.dwHighDateTime) << 32) | u64::from(user.dwLowDateTime);
371
372 if ret.is_ok() {
373 Some((start, exit, sys, user))
374 } else {
375 None
376 }
377 }
378}
379
380fn get_memory_info(handle: HANDLE) -> Option<MemoryInfo> {
381 unsafe {
382 let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed();
383 let ret = GetProcessMemoryInfo(
384 handle,
385 &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void
386 as *mut PROCESS_MEMORY_COUNTERS,
387 size_of::<PROCESS_MEMORY_COUNTERS_EX>() as u32,
388 );
389
390 if ret.is_ok() {
391 let info = MemoryInfo {
392 page_fault_count: u64::from(pmc.PageFaultCount),
393 peak_working_set_size: pmc.PeakWorkingSetSize as u64,
394 working_set_size: pmc.WorkingSetSize as u64,
395 quota_peak_paged_pool_usage: pmc.QuotaPeakPagedPoolUsage as u64,
396 quota_paged_pool_usage: pmc.QuotaPagedPoolUsage as u64,
397 quota_peak_non_paged_pool_usage: pmc.QuotaPeakNonPagedPoolUsage as u64,
398 quota_non_paged_pool_usage: pmc.QuotaNonPagedPoolUsage as u64,
399 page_file_usage: pmc.PagefileUsage as u64,
400 peak_page_file_usage: pmc.PeakPagefileUsage as u64,
401 private_usage: pmc.PrivateUsage as u64,
402 };
403 Some(info)
404 } else {
405 None
406 }
407 }
408}
409
410fn get_command(handle: HANDLE) -> Option<String> {
411 unsafe {
412 let mut exe_buf = [0u16; MAX_PATH as usize + 1];
413 let h_mod = HMODULE::default();
414
415 let ret = GetModuleBaseNameW(handle, h_mod.into(), exe_buf.as_mut_slice());
416
417 let mut pos = 0;
418 for x in exe_buf.iter() {
419 if *x == 0 {
420 break;
421 }
422 pos += 1;
423 }
424
425 if ret != 0 {
426 Some(String::from_utf16_lossy(&exe_buf[..pos]))
427 } else {
428 None
429 }
430 }
431}
432
433trait RtlUserProcessParameters {
434 fn get_cmdline(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
435 fn get_cwd(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
436 fn get_environ(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
437}
438
439macro_rules! impl_RtlUserProcessParameters {
440 ($t:ty) => {
441 impl RtlUserProcessParameters for $t {
442 fn get_cmdline(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str> {
443 let ptr = self.CommandLine.Buffer;
444 let size = self.CommandLine.Length;
445 unsafe { get_process_data(handle, ptr as _, size as _) }
446 }
447 fn get_cwd(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str> {
448 let ptr = self.CurrentDirectory.DosPath.Buffer;
449 let size = self.CurrentDirectory.DosPath.Length;
450 unsafe { get_process_data(handle, ptr as _, size as _) }
451 }
452 fn get_environ(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str> {
453 let ptr = self.Environment;
454 unsafe {
455 let size = get_region_size(handle, ptr as _)?;
456 get_process_data(handle, ptr as _, size as _)
457 }
458 }
459 }
460 };
461}
462
463impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32);
464impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS);
465
466unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String {
467 match slice.iter().position(|&x| x == 0) {
468 Some(pos) => OsString::from_wide(&slice[..pos])
469 .to_string_lossy()
470 .into_owned(),
471 None => OsString::from_wide(slice).to_string_lossy().into_owned(),
472 }
473}
474
475unsafe fn get_process_data(
476 handle: HANDLE,
477 ptr: *const c_void,
478 size: usize,
479) -> Result<Vec<u16>, &'static str> {
480 let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1);
481 let mut bytes_read = 0;
482
483 unsafe {
484 if ReadProcessMemory(
485 handle,
486 ptr,
487 buffer.as_mut_ptr().cast(),
488 size,
489 Some(&mut bytes_read),
490 )
491 .is_err()
492 {
493 return Err("Unable to read process data");
494 }
495
496 if bytes_read != size {
498 return Err("ReadProcessMemory returned unexpected number of bytes read");
499 }
500
501 buffer.set_len(size / 2);
502 buffer.push(0);
503 }
504
505 Ok(buffer)
506}
507
508unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result<usize, &'static str> {
509 unsafe {
510 let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit();
511 if VirtualQueryEx(
512 handle,
513 Some(ptr),
514 meminfo.as_mut_ptr().cast(),
515 size_of::<MEMORY_BASIC_INFORMATION>(),
516 ) == 0
517 {
518 return Err("Unable to read process memory information");
519 }
520 let meminfo = meminfo.assume_init();
521 Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize)
522 }
523}
524
525unsafe fn ph_query_process_variable_size(
526 process_handle: HANDLE,
527 process_information_class: PROCESSINFOCLASS,
528) -> Option<Vec<u16>> {
529 unsafe {
530 let mut return_length = MaybeUninit::<u32>::uninit();
531
532 if let Err(err) = NtQueryInformationProcess(
533 process_handle,
534 process_information_class,
535 std::ptr::null_mut(),
536 0,
537 return_length.as_mut_ptr() as *mut _,
538 )
539 .ok()
540 && ![
541 STATUS_BUFFER_OVERFLOW.into(),
542 STATUS_BUFFER_TOO_SMALL.into(),
543 STATUS_INFO_LENGTH_MISMATCH.into(),
544 ]
545 .contains(&err.code())
546 {
547 return None;
548 }
549
550 let mut return_length = return_length.assume_init();
551 let buf_len = (return_length as usize) / 2;
552 let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1);
553 if NtQueryInformationProcess(
554 process_handle,
555 process_information_class,
556 buffer.as_mut_ptr() as *mut _,
557 return_length,
558 &mut return_length as *mut _,
559 )
560 .is_err()
561 {
562 return None;
563 }
564 buffer.set_len(buf_len);
565 buffer.push(0);
566 Some(buffer)
567 }
568}
569
570unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec<String> {
571 unsafe {
572 let mut argc = MaybeUninit::<i32>::uninit();
574 let argv_p = CommandLineToArgvW(buffer, argc.as_mut_ptr());
575 if argv_p.is_null() {
576 return Vec::new();
577 }
578 let argc = argc.assume_init();
579 let argv = std::slice::from_raw_parts(argv_p, argc as usize);
580
581 let mut res = Vec::new();
582 for arg in argv {
583 res.push(String::from_utf16_lossy(arg.as_wide()));
584 }
585
586 let _err = LocalFree(HLOCAL(argv_p as _).into());
587
588 res
589 }
590}
591
592unsafe fn get_process_params(
593 handle: HANDLE,
594) -> Result<(Vec<String>, Vec<String>, PathBuf), &'static str> {
595 unsafe {
596 if !cfg!(target_pointer_width = "64") {
597 return Err("Non 64 bit targets are not supported");
598 }
599
600 let mut pwow32info = MaybeUninit::<*const c_void>::uninit();
602 if NtQueryInformationProcess(
603 handle,
604 ProcessWow64Information,
605 pwow32info.as_mut_ptr().cast(),
606 size_of::<*const c_void>() as u32,
607 null_mut(),
608 )
609 .is_err()
610 {
611 return Err("Unable to check WOW64 information about the process");
612 }
613 let pwow32info = pwow32info.assume_init();
614
615 if pwow32info.is_null() {
616 let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit();
619 if NtQueryInformationProcess(
620 handle,
621 ProcessBasicInformation,
622 pbasicinfo.as_mut_ptr().cast(),
623 size_of::<PROCESS_BASIC_INFORMATION>() as u32,
624 null_mut(),
625 )
626 .is_err()
627 {
628 return Err("Unable to get basic process information");
629 }
630 let pinfo = pbasicinfo.assume_init();
631
632 let mut peb = MaybeUninit::<PEB>::uninit();
633 if ReadProcessMemory(
634 handle,
635 pinfo.PebBaseAddress.cast(),
636 peb.as_mut_ptr().cast(),
637 size_of::<PEB>(),
638 None,
639 )
640 .is_err()
641 {
642 return Err("Unable to read process PEB");
643 }
644
645 let peb = peb.assume_init();
646
647 let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit();
648 if ReadProcessMemory(
649 handle,
650 peb.ProcessParameters.cast(),
651 proc_params.as_mut_ptr().cast(),
652 size_of::<RTL_USER_PROCESS_PARAMETERS>(),
653 None,
654 )
655 .is_err()
656 {
657 return Err("Unable to read process parameters");
658 }
659
660 let proc_params = proc_params.assume_init();
661 return Ok((
662 get_cmd_line(&proc_params, handle),
663 get_proc_env(&proc_params, handle),
664 get_cwd(&proc_params, handle),
665 ));
666 }
667 let mut peb32 = MaybeUninit::<PEB32>::uninit();
670 if ReadProcessMemory(
671 handle,
672 pwow32info,
673 peb32.as_mut_ptr().cast(),
674 size_of::<PEB32>(),
675 None,
676 )
677 .is_err()
678 {
679 return Err("Unable to read PEB32");
680 }
681 let peb32 = peb32.assume_init();
682
683 let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit();
684 if ReadProcessMemory(
685 handle,
686 peb32.ProcessParameters as *mut _,
687 proc_params.as_mut_ptr().cast(),
688 size_of::<RTL_USER_PROCESS_PARAMETERS32>(),
689 None,
690 )
691 .is_err()
692 {
693 return Err("Unable to read 32 bit process parameters");
694 }
695 let proc_params = proc_params.assume_init();
696 Ok((
697 get_cmd_line(&proc_params, handle),
698 get_proc_env(&proc_params, handle),
699 get_cwd(&proc_params, handle),
700 ))
701 }
702}
703
704static WINDOWS_8_1_OR_NEWER: LazyLock<bool> = LazyLock::new(|| unsafe {
705 let mut version_info: OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init();
706
707 version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
708 if RtlGetVersion((&mut version_info as *mut OSVERSIONINFOEXW).cast()).is_err() {
709 return true;
710 }
711
712 version_info.dwMajorVersion > 6
714 || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3
715});
716
717fn get_cmd_line<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> Vec<String> {
718 if *WINDOWS_8_1_OR_NEWER {
719 get_cmd_line_new(handle)
720 } else {
721 get_cmd_line_old(params, handle)
722 }
723}
724
725#[allow(clippy::cast_ptr_alignment)]
726fn get_cmd_line_new(handle: HANDLE) -> Vec<String> {
727 unsafe {
728 if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation)
729 {
730 let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer;
731
732 get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr()))
733 } else {
734 Vec::new()
735 }
736 }
737}
738
739fn get_cmd_line_old<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> Vec<String> {
740 match params.get_cmdline(handle) {
741 Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) },
742 Err(_e) => Vec::new(),
743 }
744}
745
746fn get_proc_env<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> Vec<String> {
747 match params.get_environ(handle) {
748 Ok(buffer) => {
749 let equals = "="
750 .encode_utf16()
751 .next()
752 .expect("unable to get next utf16 value");
753 let raw_env = buffer;
754 let mut result = Vec::new();
755 let mut begin = 0;
756 while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) {
757 let end = begin + offset;
758 if raw_env[begin..end].contains(&equals) {
759 result.push(
760 OsString::from_wide(&raw_env[begin..end])
761 .to_string_lossy()
762 .into_owned(),
763 );
764 begin = end + 1;
765 } else {
766 break;
767 }
768 }
769 result
770 }
771 Err(_e) => Vec::new(),
772 }
773}
774
775fn get_cwd<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> PathBuf {
776 match params.get_cwd(handle) {
777 Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) },
778 Err(_e) => PathBuf::new(),
779 }
780}
781
782fn get_io(handle: HANDLE) -> Option<(u64, u64)> {
783 unsafe {
784 let mut io: IO_COUNTERS = zeroed();
785 let ret = GetProcessIoCounters(handle, &mut io);
786
787 if ret.is_ok() {
788 Some((io.ReadTransferCount, io.WriteTransferCount))
789 } else {
790 None
791 }
792 }
793}
794
795#[derive(Clone)]
796pub struct SidName {
797 pub sid: Vec<u64>,
798 pub name: Option<String>,
799 pub domainname: Option<String>,
800}
801
802fn get_user(handle: HANDLE) -> Option<SidName> {
803 unsafe {
804 let mut token: HANDLE = zeroed();
805 let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
806
807 if ret.is_err() {
808 return None;
809 }
810
811 let mut cb_needed = 0;
812 let _ = GetTokenInformation(
813 token,
814 TokenUser,
815 Some(ptr::null::<c_void>() as *mut c_void),
816 0,
817 &mut cb_needed,
818 );
819
820 let mut buf: Vec<u8> = Vec::with_capacity(cb_needed as usize);
821
822 let ret = GetTokenInformation(
823 token,
824 TokenUser,
825 Some(buf.as_mut_ptr() as *mut c_void),
826 cb_needed,
827 &mut cb_needed,
828 );
829 buf.set_len(cb_needed as usize);
830
831 if ret.is_err() {
832 return None;
833 }
834
835 #[allow(clippy::cast_ptr_alignment)]
836 let token_user = buf.as_ptr() as *const TOKEN_USER;
837 let psid = (*token_user).User.Sid;
838
839 let sid = get_sid(psid);
840 let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) {
841 (Some(x), Some(y))
842 } else {
843 (None, None)
844 };
845
846 Some(SidName {
847 sid,
848 name,
849 domainname,
850 })
851 }
852}
853
854fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
855 unsafe {
856 let mut token: HANDLE = zeroed();
857 let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
858
859 if ret.is_err() {
860 return None;
861 }
862
863 let mut cb_needed = 0;
864 let _ = GetTokenInformation(
865 token,
866 TokenGroups,
867 Some(ptr::null::<c_void>() as *mut c_void),
868 0,
869 &mut cb_needed,
870 );
871
872 let mut buf: Vec<u8> = Vec::with_capacity(cb_needed as usize);
873
874 let ret = GetTokenInformation(
875 token,
876 TokenGroups,
877 Some(buf.as_mut_ptr() as *mut c_void),
878 cb_needed,
879 &mut cb_needed,
880 );
881 buf.set_len(cb_needed as usize);
882
883 if ret.is_err() {
884 return None;
885 }
886
887 #[allow(clippy::cast_ptr_alignment)]
888 let token_groups = buf.as_ptr() as *const TOKEN_GROUPS;
889
890 let mut ret = Vec::new();
891 let sa = (*token_groups).Groups.as_ptr();
892 for i in 0..(*token_groups).GroupCount {
893 let psid = (*sa.offset(i as isize)).Sid;
894 let sid = get_sid(psid);
895 let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) {
896 (Some(x), Some(y))
897 } else {
898 (None, None)
899 };
900
901 let sid_name = SidName {
902 sid,
903 name,
904 domainname,
905 };
906 ret.push(sid_name);
907 }
908
909 Some(ret)
910 }
911}
912
913fn get_sid(psid: PSID) -> Vec<u64> {
914 unsafe {
915 let mut ret = Vec::new();
916 let psid = psid.0 as *const SID;
917
918 let mut ia = 0;
919 ia |= u64::from((*psid).IdentifierAuthority.Value[0]) << 40;
920 ia |= u64::from((*psid).IdentifierAuthority.Value[1]) << 32;
921 ia |= u64::from((*psid).IdentifierAuthority.Value[2]) << 24;
922 ia |= u64::from((*psid).IdentifierAuthority.Value[3]) << 16;
923 ia |= u64::from((*psid).IdentifierAuthority.Value[4]) << 8;
924 ia |= u64::from((*psid).IdentifierAuthority.Value[5]);
925
926 ret.push(u64::from((*psid).Revision));
927 ret.push(ia);
928 let cnt = (*psid).SubAuthorityCount;
929 let sa = (*psid).SubAuthority.as_ptr();
930 for i in 0..cnt {
931 ret.push(u64::from(*sa.offset(i as isize)));
932 }
933
934 ret
935 }
936}
937
938thread_local!(
939 pub static NAME_CACHE: RefCell<HashMap<*mut c_void, Option<(String, String)>>> =
940 RefCell::new(HashMap::new());
941);
942
943fn get_name_cached(psid: PSID) -> Option<(String, String)> {
944 NAME_CACHE.with(|c| {
945 let mut c = c.borrow_mut();
946 if let Some(x) = c.get(&psid.0) {
947 x.clone()
948 } else {
949 let x = get_name(psid);
950 c.insert(psid.0, x.clone());
951 x
952 }
953 })
954}
955
956fn get_name(psid: PSID) -> Option<(String, String)> {
957 unsafe {
958 let mut cc_name = 0;
959 let mut cc_domainname = 0;
960 let mut pe_use = SID_NAME_USE::default();
961 let _ = LookupAccountSidW(
962 None,
963 psid,
964 None,
965 &mut cc_name,
966 None,
967 &mut cc_domainname,
968 &mut pe_use,
969 );
970
971 if cc_name == 0 || cc_domainname == 0 {
972 return None;
973 }
974
975 let mut name: Vec<u16> = Vec::with_capacity(cc_name as usize);
976 let mut domainname: Vec<u16> = Vec::with_capacity(cc_domainname as usize);
977 name.set_len(cc_name as usize);
978 domainname.set_len(cc_domainname as usize);
979 if LookupAccountSidW(
980 None,
981 psid,
982 PWSTR::from_raw(name.as_mut_ptr()).into(),
983 &mut cc_name,
984 PWSTR::from_raw(domainname.as_mut_ptr()).into(),
985 &mut cc_domainname,
986 &mut pe_use,
987 )
988 .is_err()
989 {
990 return None;
991 }
992
993 let name = from_wide_ptr(name.as_ptr());
994 let domainname = from_wide_ptr(domainname.as_ptr());
995 Some((name, domainname))
996 }
997}
998
999fn from_wide_ptr(ptr: *const u16) -> String {
1000 unsafe {
1001 assert!(!ptr.is_null());
1002 let len = (0..isize::MAX)
1003 .position(|i| *ptr.offset(i) == 0)
1004 .unwrap_or_default();
1005 let slice = std::slice::from_raw_parts(ptr, len);
1006 OsString::from_wide(slice).to_string_lossy().into_owned()
1007 }
1008}
1009
1010fn get_priority(handle: HANDLE) -> u32 {
1011 unsafe { GetPriorityClass(handle) }
1012}
1013
1014impl ProcessInfo {
1015 pub fn pid(&self) -> i32 {
1017 self.pid
1018 }
1019
1020 pub fn ppid(&self) -> i32 {
1022 self.ppid
1023 }
1024
1025 pub fn name(&self) -> String {
1027 self.command.clone()
1028 }
1029
1030 pub fn command(&self) -> String {
1032 self.cmd.join(" ")
1033 }
1034
1035 pub fn environ(&self) -> Vec<String> {
1036 self.environ.clone()
1037 }
1038
1039 pub fn cwd(&self) -> String {
1040 self.cwd.display().to_string()
1041 }
1042
1043 pub fn status(&self) -> String {
1045 "unknown".to_string()
1046 }
1047
1048 pub fn cpu_usage(&self) -> f64 {
1050 let curr_time = self.cpu_info.curr_sys + self.cpu_info.curr_user;
1051 let prev_time = self.cpu_info.prev_sys + self.cpu_info.prev_user;
1052
1053 let usage_ms = curr_time.saturating_sub(prev_time) / 10000u64;
1054 let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis());
1055 usage_ms as f64 * 100.0 / interval_ms as f64
1056 }
1057
1058 pub fn mem_size(&self) -> u64 {
1060 self.memory_info.working_set_size
1061 }
1062
1063 pub fn virtual_size(&self) -> u64 {
1065 self.memory_info.private_usage
1066 }
1067}