nu_system/
windows.rs

1// Attribution: a lot of this came from procs https://github.com/dalance/procs
2// and sysinfo https://github.com/GuillaumeGomez/sysinfo
3
4use 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                // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and
151                // the Linux epoch (1970-01-01).
152                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        // Documentation states that the function fails if not all data is accessible.
497        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        // Get argc and argv from the command line
573        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        // First check if target process is running in wow64 compatibility emulator
601        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            // target is a 64 bit process
617
618            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        // target is a 32 bit process in wow64 mode
668
669        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    // Windows 8.1 is 6.3
713    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    /// PID of process
1016    pub fn pid(&self) -> i32 {
1017        self.pid
1018    }
1019
1020    /// Parent PID of process
1021    pub fn ppid(&self) -> i32 {
1022        self.ppid
1023    }
1024
1025    /// Name of command
1026    pub fn name(&self) -> String {
1027        self.command.clone()
1028    }
1029
1030    /// Full name of command, with arguments
1031    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    /// Get the status of the process
1044    pub fn status(&self) -> String {
1045        "unknown".to_string()
1046    }
1047
1048    /// CPU usage as a percent of total
1049    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    /// Memory size in number of bytes
1059    pub fn mem_size(&self) -> u64 {
1060        self.memory_info.working_set_size
1061    }
1062
1063    /// Virtual memory size in bytes
1064    pub fn virtual_size(&self) -> u64 {
1065        self.memory_info.private_usage
1066    }
1067}