diff --git a/src/common.rs b/src/common.rs index a676faa3b..23b1a9805 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1401,6 +1401,12 @@ pub struct ProcessRefreshKind { cpu: bool, disk_usage: bool, user: bool, + memory: bool, + cwd: bool, + root: bool, + environ: bool, + cmd: bool, + exe: bool, } impl ProcessRefreshKind { @@ -1433,6 +1439,12 @@ impl ProcessRefreshKind { cpu: true, disk_usage: true, user: true, + memory: true, + cwd: true, + root: true, + environ: true, + cmd: true, + exe: true, } } @@ -1443,14 +1455,13 @@ impl ProcessRefreshKind { with_disk_usage, without_disk_usage ); - impl_get_set!( - ProcessRefreshKind, - user, - with_user, - without_user, - r#"This refresh is about `user_id` and `group_id`. Please note that it has an effect mostly -on Windows as other platforms get this information alongside the Process information directly."#, - ); + impl_get_set!(ProcessRefreshKind, user, with_user, without_user); + impl_get_set!(ProcessRefreshKind, memory, with_memory, without_memory); + impl_get_set!(ProcessRefreshKind, cwd, with_cwd, without_cwd); + impl_get_set!(ProcessRefreshKind, root, with_root, without_root); + impl_get_set!(ProcessRefreshKind, environ, with_environ, without_environ); + impl_get_set!(ProcessRefreshKind, cmd, with_cmd, without_cmd); + impl_get_set!(ProcessRefreshKind, exe, with_exe, without_exe); } /// Used to determine what you want to refresh specifically on the [`Cpu`] type. diff --git a/src/lib.rs b/src/lib.rs index 540980ab6..ca9e0ce52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,21 +506,6 @@ mod test { ); } - // We ensure that the `Process` cmd information is retrieved as expected. - #[test] - fn check_cmd_line() { - if !IS_SUPPORTED { - return; - } - let mut sys = System::new(); - sys.refresh_processes_specifics(ProcessRefreshKind::new()); - - assert!(sys - .processes() - .iter() - .any(|(_, process)| !process.cmd().is_empty())); - } - #[test] fn check_cpu_arch() { let s = System::new(); diff --git a/src/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs index 7bd997ed4..f7fe1df49 100644 --- a/src/unix/apple/macos/process.rs +++ b/src/unix/apple/macos/process.rs @@ -1,11 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. use std::mem::{self, MaybeUninit}; -use std::ops::Deref; use std::path::{Path, PathBuf}; -use std::borrow::Borrow; - use libc::{c_int, c_void, kill}; use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid}; @@ -48,22 +45,16 @@ pub(crate) struct ProcessInner { } impl ProcessInner { - pub(crate) fn new_empty( - pid: Pid, - exe: PathBuf, - name: String, - cwd: PathBuf, - root: PathBuf, - ) -> Self { + pub(crate) fn new_empty(pid: Pid) -> Self { Self { - name, + name: String::new(), pid, parent: None, cmd: Vec::new(), environ: Vec::new(), - exe, - cwd, - root, + exe: PathBuf::new(), + cwd: PathBuf::new(), + root: PathBuf::new(), memory: 0, virtual_memory: 0, cpu_usage: 0., @@ -85,14 +76,7 @@ impl ProcessInner { } } - pub(crate) fn new( - pid: Pid, - parent: Option, - start_time: u64, - run_time: u64, - cwd: PathBuf, - root: PathBuf, - ) -> Self { + pub(crate) fn new(pid: Pid, parent: Option, start_time: u64, run_time: u64) -> Self { Self { name: String::new(), pid, @@ -100,8 +84,8 @@ impl ProcessInner { cmd: Vec::new(), environ: Vec::new(), exe: PathBuf::new(), - cwd, - root, + cwd: PathBuf::new(), + root: PathBuf::new(), memory: 0, virtual_memory: 0, cpu_usage: 0., @@ -349,65 +333,21 @@ unsafe fn get_bsd_info(pid: Pid) -> Option { } } -unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> PathBuf { - if node.vip_vi.vi_stat.vst_dev == 0 { - return PathBuf::new(); - } - cstr_to_rust_with_size( - node.vip_path.as_ptr() as _, - Some(node.vip_path.len() * node.vip_path[0].len()), - ) - .map(PathBuf::from) - .unwrap_or_default() -} - unsafe fn create_new_process( pid: Pid, now: u64, refresh_kind: ProcessRefreshKind, info: Option, ) -> Result, ()> { - let mut vnodepathinfo = mem::zeroed::(); - let result = libc::proc_pidinfo( - pid.0, - libc::PROC_PIDVNODEPATHINFO, - 0, - &mut vnodepathinfo as *mut _ as *mut _, - mem::size_of::() as _, - ); - let (cwd, root) = if result > 0 { - ( - convert_node_path_info(&vnodepathinfo.pvi_cdir), - convert_node_path_info(&vnodepathinfo.pvi_rdir), - ) - } else { - (PathBuf::new(), PathBuf::new()) - }; - let info = match info { Some(info) => info, None => { - let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); - match libc::proc_pidpath( - pid.0, - buffer.as_mut_ptr() as *mut _, - libc::PROC_PIDPATHINFO_MAXSIZE as _, - ) { - x if x > 0 => { - buffer.set_len(x as _); - let tmp = String::from_utf8_unchecked(buffer); - let exe = PathBuf::from(tmp); - let name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); - return Ok(Some(Process { - inner: ProcessInner::new_empty(pid, exe, name, cwd, root), - })); - } - _ => {} + let mut p = ProcessInner::new_empty(pid); + if get_exe_and_name_backup(&mut p, refresh_kind) { + get_cwd_root(&mut p, refresh_kind); + return Ok(Some(Process { inner: p })); } + // If we can't even have the name, no point in keeping it. return Err(()); } }; @@ -416,6 +356,107 @@ unsafe fn create_new_process( p => Some(Pid(p)), }; + let start_time = info.pbi_start_tvsec; + let run_time = now.saturating_sub(start_time); + + let mut p = ProcessInner::new(pid, parent, start_time, run_time); + if !get_process_infos(&mut p, refresh_kind) && !get_exe_and_name_backup(&mut p, refresh_kind) { + // If we can't even have the name, no point in keeping it. + return Err(()); + } + get_cwd_root(&mut p, refresh_kind); + + if refresh_kind.memory() { + let task_info = get_task_info(pid); + p.memory = task_info.pti_resident_size; + p.virtual_memory = task_info.pti_virtual_size; + } + + p.user_id = Some(Uid(info.pbi_ruid)); + p.effective_user_id = Some(Uid(info.pbi_uid)); + p.group_id = Some(Gid(info.pbi_rgid)); + p.effective_group_id = Some(Gid(info.pbi_gid)); + p.process_status = ProcessStatus::from(info.pbi_status); + if refresh_kind.disk_usage() { + update_proc_disk_activity(&mut p); + } + Ok(Some(Process { inner: p })) +} + +/// Less efficient way to retrieve `exe` and `name`. +unsafe fn get_exe_and_name_backup( + process: &mut ProcessInner, + refresh_kind: ProcessRefreshKind, +) -> bool { + if !refresh_kind.exe() && !process.name.is_empty() { + return false; + } + let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); + match libc::proc_pidpath( + process.pid.0, + buffer.as_mut_ptr() as *mut _, + libc::PROC_PIDPATHINFO_MAXSIZE as _, + ) { + x if x > 0 => { + buffer.set_len(x as _); + let tmp = String::from_utf8_unchecked(buffer); + let exe = PathBuf::from(tmp); + if process.name.is_empty() { + process.name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + } + if refresh_kind.exe() { + process.exe = exe; + } + true + } + _ => false, + } +} + +unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> Option { + if node.vip_vi.vi_stat.vst_dev == 0 { + return None; + } + cstr_to_rust_with_size( + node.vip_path.as_ptr() as _, + Some(node.vip_path.len() * node.vip_path[0].len()), + ) + .map(PathBuf::from) +} + +unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { + if !refresh_kind.cwd() && !refresh_kind.root() { + return; + } + let mut vnodepathinfo = mem::zeroed::(); + let result = libc::proc_pidinfo( + process.pid.0, + libc::PROC_PIDVNODEPATHINFO, + 0, + &mut vnodepathinfo as *mut _ as *mut _, + mem::size_of::() as _, + ); + if result < 1 { + sysinfo_debug!("Failed to retrieve cwd and root for {}", process.pid.0); + return; + } + if refresh_kind.cwd() { + if let Some(cwd) = convert_node_path_info(&vnodepathinfo.pvi_cdir) { + process.cwd = cwd; + } + } + if refresh_kind.root() { + if let Some(root) = convert_node_path_info(&vnodepathinfo.pvi_rdir) { + process.root = root; + } + } +} + +unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) -> bool { /* * /---------------\ 0x00000000 * | ::::::::::::: | @@ -446,7 +487,7 @@ unsafe fn create_new_process( * : : * \---------------/ 0xffffffff */ - let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _]; + let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, process.pid.0 as _]; let mut arg_max = 0; // First we retrieve the size we will need for our data (in `arg_max`). if libc::sysctl( @@ -460,9 +501,9 @@ unsafe fn create_new_process( { sysinfo_debug!( "couldn't get arguments and environment size for PID {}", - pid.0 + process.pid.0 ); - return Err(()); // not enough rights I assume? + return false; // not enough rights I assume? } let mut proc_args: Vec = Vec::with_capacity(arg_max as _); @@ -475,62 +516,51 @@ unsafe fn create_new_process( 0, ) == -1 { - sysinfo_debug!("couldn't get arguments and environment for PID {}", pid.0); - return Err(()); // What changed since the previous call? Dark magic! + sysinfo_debug!( + "couldn't get arguments and environment for PID {}", + process.pid.0 + ); + return false; // What changed since the previous call? Dark magic! } proc_args.set_len(arg_max); - let start_time = info.pbi_start_tvsec; - let run_time = now.saturating_sub(start_time); - - let mut p = if !proc_args.is_empty() { - // We copy the number of arguments (`argc`) to `n_args`. - let mut n_args: c_int = 0; - libc::memcpy( - &mut n_args as *mut _ as *mut _, - proc_args.as_slice().as_ptr() as *const _, - mem::size_of::(), - ); + if proc_args.is_empty() { + return false; + } + // We copy the number of arguments (`argc`) to `n_args`. + let mut n_args: c_int = 0; + libc::memcpy( + &mut n_args as *mut _ as *mut _, + proc_args.as_slice().as_ptr() as *const _, + mem::size_of::(), + ); - // We skip `argc`. - let proc_args = &proc_args[mem::size_of::()..]; + // We skip `argc`. + let proc_args = &proc_args[mem::size_of::()..]; - let (exe, proc_args) = get_exe(proc_args); - let name = exe + let (exe, proc_args) = get_exe(proc_args); + if process.name.is_empty() { + process.name = exe .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .to_owned(); + } - let (cmd, proc_args) = get_arguments(proc_args, n_args); - - let environ = get_environ(proc_args); - let mut p = ProcessInner::new(pid, parent, start_time, run_time, cwd, root); - - p.exe = exe; - p.name = name; - p.cmd = parse_command_line(&cmd); - p.environ = environ; - p - } else { - ProcessInner::new(pid, parent, start_time, run_time, cwd, root) - }; - - let task_info = get_task_info(pid); - - p.memory = task_info.pti_resident_size; - p.virtual_memory = task_info.pti_virtual_size; + if refresh_kind.exe() { + process.exe = exe; + } - p.user_id = Some(Uid(info.pbi_ruid)); - p.effective_user_id = Some(Uid(info.pbi_uid)); - p.group_id = Some(Gid(info.pbi_rgid)); - p.effective_group_id = Some(Gid(info.pbi_gid)); - p.process_status = ProcessStatus::from(info.pbi_status); - if refresh_kind.disk_usage() { - update_proc_disk_activity(&mut p); + if !refresh_kind.environ() && !refresh_kind.cmd() { + // Nothing else to be done! + return true; } - Ok(Some(Process { inner: p })) + let proc_args = get_arguments(&mut process.cmd, proc_args, n_args, refresh_kind.cmd()); + if refresh_kind.environ() { + get_environ(&mut process.environ, proc_args); + } + true } fn get_exe(data: &[u8]) -> (PathBuf, &[u8]) { @@ -543,20 +573,28 @@ fn get_exe(data: &[u8]) -> (PathBuf, &[u8]) { } } -fn get_arguments(mut data: &[u8], mut n_args: c_int) -> (Vec, &[u8]) { +fn get_arguments<'a>( + cmd: &mut Vec, + mut data: &'a [u8], + mut n_args: c_int, + refresh_cmd: bool, +) -> &'a [u8] { + if refresh_cmd { + cmd.clear(); + } + if n_args < 1 { - return (Vec::new(), data); + return data; } while data.first() == Some(&0) { data = &data[1..]; } - let mut cmd = Vec::with_capacity(n_args as _); unsafe { while n_args > 0 && !data.is_empty() { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); let arg = std::str::from_utf8_unchecked(&data[..pos]); - if !arg.is_empty() { + if !arg.is_empty() && refresh_cmd { cmd.push(arg.to_string()); } data = &data[pos..]; @@ -565,21 +603,23 @@ fn get_arguments(mut data: &[u8], mut n_args: c_int) -> (Vec, &[u8]) { } n_args -= 1; } - (cmd, data) + data } } -fn get_environ(mut data: &[u8]) -> Vec { +fn get_environ(environ: &mut Vec, mut data: &[u8]) { + environ.clear(); + while data.first() == Some(&0) { data = &data[1..]; } - let mut environ = Vec::new(); + unsafe { while !data.is_empty() { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); let arg = std::str::from_utf8_unchecked(&data[..pos]); if arg.is_empty() { - return environ; + return; } environ.push(arg.to_string()); data = &data[pos..]; @@ -587,7 +627,6 @@ fn get_environ(mut data: &[u8]) -> Vec { data = &data[1..]; } } - environ } } @@ -602,15 +641,7 @@ pub(crate) fn update_process( unsafe { if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { let p = &mut p.inner; - if p.memory == 0 { - // We don't have access to this process' information. - return if check_if_pid_is_alive(pid, check_if_alive) { - p.updated = true; - Ok(None) - } else { - Err(()) - }; - } + if let Some(info) = get_bsd_info(pid) { if info.pbi_start_tvsec != p.start_time { // We don't it to be removed, just replaced. @@ -619,7 +650,16 @@ pub(crate) fn update_process( return create_new_process(pid, now, refresh_kind, Some(info)); } } - let task_info = get_task_info(pid); + + if !get_process_infos(p, refresh_kind) { + get_exe_and_name_backup(p, refresh_kind); + } + get_cwd_root(p, refresh_kind); + + if refresh_kind.disk_usage() { + update_proc_disk_activity(p); + } + let mut thread_info = mem::zeroed::(); let (user_time, system_time, thread_status) = if libc::proc_pidinfo( pid.0, @@ -644,19 +684,22 @@ pub(crate) fn update_process( }; p.status = thread_status; - if refresh_kind.cpu() { - compute_cpu_usage(p, task_info, system_time, user_time, time_interval); - } + if refresh_kind.cpu() || refresh_kind.memory() { + let task_info = get_task_info(pid); - p.memory = task_info.pti_resident_size; - p.virtual_memory = task_info.pti_virtual_size; - if refresh_kind.disk_usage() { - update_proc_disk_activity(p); + if refresh_kind.cpu() { + compute_cpu_usage(p, task_info, system_time, user_time, time_interval); + } + if refresh_kind.memory() { + p.memory = task_info.pti_resident_size; + p.virtual_memory = task_info.pti_virtual_size; + } } p.updated = true; - return Ok(None); + Ok(None) + } else { + create_new_process(pid, now, refresh_kind, get_bsd_info(pid)) } - create_new_process(pid, now, refresh_kind, get_bsd_info(pid)) } } @@ -703,23 +746,3 @@ pub(crate) fn get_proc_list() -> Option> { } } } - -fn parse_command_line + Borrow>(cmd: &[T]) -> Vec { - let mut x = 0; - let mut command = Vec::with_capacity(cmd.len()); - while x < cmd.len() { - let mut y = x; - if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { - let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; - while y < cmd.len() && !cmd[y].ends_with(c) { - y += 1; - } - command.push(cmd[x..y].join(" ")); - x = y; - } else { - command.push(cmd[x].to_owned()); - } - x += 1; - } - command -} diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs index d7fb7cae9..42289dc61 100644 --- a/src/unix/freebsd/process.rs +++ b/src/unix/freebsd/process.rs @@ -207,8 +207,15 @@ pub(crate) unsafe fn get_process_data( let status = ProcessStatus::from(kproc.ki_stat); // from FreeBSD source /src/usr.bin/top/machine.c - let virtual_memory = kproc.ki_size as _; - let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _); + let (virtual_memory, memory) = if refresh_kind.memory() { + ( + kproc.ki_size as _, + (kproc.ki_rssize as u64).saturating_mul(page_size as _), + ) + } else { + (0, 0) + }; + // FIXME: This is to get the "real" run time (in micro-seconds). // let run_time = (kproc.ki_runtime + 5_000) / 10_000; @@ -223,8 +230,10 @@ pub(crate) unsafe fn get_process_data( proc_.cpu_usage = cpu_usage; proc_.parent = parent; proc_.status = status; - proc_.virtual_memory = virtual_memory; - proc_.memory = memory; + if refresh_kind.memory() { + proc_.virtual_memory = virtual_memory; + proc_.memory = memory; + } proc_.run_time = now.saturating_sub(proc_.start_time); if refresh_kind.disk_usage() { @@ -239,18 +248,7 @@ pub(crate) unsafe fn get_process_data( } // This is a new process, we need to get more information! - let mut buffer = [0; libc::PATH_MAX as usize + 1]; - - let exe = get_sys_value_str( - &[ - libc::CTL_KERN, - libc::KERN_PROC, - libc::KERN_PROC_PATHNAME, - kproc.ki_pid, - ], - &mut buffer, - ) - .unwrap_or_default(); + // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use // procstat to get around this problem. // let cwd = get_sys_value_str( @@ -280,7 +278,7 @@ pub(crate) unsafe fn get_process_data( memory, // procstat_getfiles cwd: PathBuf::new(), - exe: exe.into(), + exe: PathBuf::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. name: String::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. @@ -298,3 +296,24 @@ pub(crate) unsafe fn get_process_data( }, })) } + +pub(crate) unsafe fn get_exe(exe: &mut PathBuf, pid: crate::Pid, refresh_kind: ProcessRefreshKind) { + if refresh_kind.exe() && exe.as_os_str().is_empty() { + let mut buffer = [0; libc::PATH_MAX as usize + 1]; + + *exe = get_sys_value_str( + &[ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PATHNAME, + pid.0, + ], + &mut buffer, + ) + .map(PathBuf::from) + .unwrap_or_else(|| { + sysinfo_debug!("Failed to get `exe` for {}", pid.0); + PathBuf::new() + }); + } +} diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs index 23f0fc770..373fb138b 100644 --- a/src/unix/freebsd/system.rs +++ b/src/unix/freebsd/system.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use std::ptr::NonNull; use crate::sys::cpu::{physical_core_count, CpusWrapper}; +use crate::sys::process::get_exe; use crate::sys::utils::{ self, boot_time, c_buf_to_string, from_cstr_array, get_sys_value, get_sys_value_by_name, get_system_info, init_mib, @@ -116,13 +117,15 @@ impl SystemInner { now, refresh_kind, ) { - Ok(Some(proc_)) => { - self.add_missing_proc_info(self.system_info.kd.as_ptr(), kproc, proc_); - true + Ok(Some(process)) => { + self.process_list.insert(process.inner.pid, process); } - Ok(None) => true, - Err(_) => false, + Ok(None) => {} + Err(_) => return false, } + let process = self.process_list.get_mut(&Pid(kproc.ki_pid)).unwrap(); + add_missing_proc_info(&mut self.system_info, kproc, process, refresh_kind); + true } } @@ -248,27 +251,33 @@ impl SystemInner { impl SystemInner { unsafe fn refresh_procs(&mut self, refresh_kind: ProcessRefreshKind) { - let kd = self.system_info.kd.as_ptr(); - let procs = { - let mut count = 0; - let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); - if count < 1 { - sysinfo_debug!("kvm_getprocs returned nothing..."); - return; - } + let mut count = 0; + let kvm_procs = libc::kvm_getprocs( + self.system_info.kd.as_ptr(), + libc::KERN_PROC_PROC, + 0, + &mut count, + ); + if count < 1 { + sysinfo_debug!("kvm_getprocs returned nothing..."); + return; + } + + let new_processes = { #[cfg(feature = "multithread")] use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; #[cfg(not(feature = "multithread"))] use std::iter::Iterator as IterTrait; + let kvm_procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(kvm_procs as _, count as _); + let fscale = self.system_info.fscale; let page_size = self.system_info.page_size as isize; let now = super::utils::get_now(); let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); - let procs: &mut [utils::KInfoProc] = - std::slice::from_raw_parts_mut(procs as _, count as _); - IterTrait::filter_map(crate::utils::into_iter(procs), |kproc| { + IterTrait::filter_map(crate::utils::into_iter(kvm_procs), |kproc| { super::process::get_process_data( kproc, &proc_list, @@ -277,8 +286,7 @@ impl SystemInner { now, refresh_kind, ) - .ok() - .and_then(|p| p.map(|p| (kproc, p))) + .ok()? }) .collect::>() }; @@ -287,42 +295,55 @@ impl SystemInner { self.process_list .retain(|_, v| std::mem::replace(&mut v.inner.updated, false)); - for (kproc, proc_) in procs { - self.add_missing_proc_info(kd, kproc, proc_); + for process in new_processes { + self.process_list.insert(process.inner.pid, process); + } + let kvm_procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(kvm_procs as _, count as _); + + for kproc in kvm_procs { + if let Some(process) = self.process_list.get_mut(&Pid(kproc.ki_pid)) { + add_missing_proc_info(&mut self.system_info, kproc, process, refresh_kind); + } } } +} - unsafe fn add_missing_proc_info( - &mut self, - kd: *mut libc::kvm_t, - kproc: &libc::kinfo_proc, - mut proc_: Process, - ) { - { - let proc_inner = &mut proc_.inner; - proc_inner.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); - self.system_info.get_proc_missing_info(kproc, proc_inner); - if !proc_inner.cmd.is_empty() { +unsafe fn add_missing_proc_info( + system_info: &mut SystemInfo, + kproc: &libc::kinfo_proc, + proc_: &mut Process, + refresh_kind: ProcessRefreshKind, +) { + { + let kd = system_info.kd.as_ptr(); + let proc_inner = &mut proc_.inner; + if refresh_kind.cmd() || proc_inner.name.is_empty() { + let cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); + + if !cmd.is_empty() { // First, we try to retrieve the name from the command line. - let p = Path::new(&proc_inner.cmd[0]); + let p = Path::new(&cmd[0]); if let Some(name) = p.file_name().and_then(|s| s.to_str()) { proc_inner.name = name.to_owned(); } - if proc_inner.root.as_os_str().is_empty() { - if let Some(parent) = p.parent() { - proc_inner.root = parent.to_path_buf(); - } - } } - if proc_inner.name.is_empty() { - // The name can be cut short because the `ki_comm` field size is limited, - // which is why we prefer to get the name from the command line as much as - // possible. - proc_inner.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + + if refresh_kind.cmd() { + proc_inner.cmd = cmd; } + } + get_exe(&mut proc_inner.exe, proc_inner.pid, refresh_kind); + system_info.get_proc_missing_info(kproc, proc_inner, refresh_kind); + if proc_inner.name.is_empty() { + // The name can be cut short because the `ki_comm` field size is limited, + // which is why we prefer to get the name from the command line as much as + // possible. + proc_inner.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + } + if refresh_kind.environ() { proc_inner.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); } - self.process_list.insert(proc_.inner.pid, proc_); } } @@ -565,34 +586,49 @@ impl SystemInfo { } #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. - unsafe fn get_proc_missing_info(&mut self, kproc: &libc::kinfo_proc, proc_: &mut ProcessInner) { - if self.procstat.is_null() { - self.procstat = libc::procstat_open_sysctl(); + unsafe fn get_proc_missing_info( + &mut self, + kproc: &libc::kinfo_proc, + proc_: &mut ProcessInner, + refresh_kind: ProcessRefreshKind, + ) { + let mut done = 0; + if refresh_kind.cwd() { + done += 1; } - if self.procstat.is_null() { + if refresh_kind.root() { + done += 1; + } + if done == 0 { return; } + if self.procstat.is_null() { + self.procstat = libc::procstat_open_sysctl(); + if self.procstat.is_null() { + sysinfo_debug!("procstat_open_sysctl failed"); + return; + } + } let head = libc::procstat_getfiles(self.procstat, kproc as *const _ as usize as *mut _, 0); if head.is_null() { return; } let mut entry = (*head).stqh_first; - let mut done = 0; - while !entry.is_null() && done < 2 { + while !entry.is_null() && done > 0 { { let tmp = &*entry; if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { - if !tmp.fs_path.is_null() { + if refresh_kind.cwd() && !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.cwd = PathBuf::from(p); - done += 1; + done -= 1; } } } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { - if !tmp.fs_path.is_null() { + if refresh_kind.root() && !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.root = PathBuf::from(p); - done += 1; + done -= 1; } } } diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs index 1b13b2084..f7977d06e 100644 --- a/src/unix/linux/process.rs +++ b/src/unix/linux/process.rs @@ -92,11 +92,11 @@ pub(crate) struct ProcessInner { impl ProcessInner { pub(crate) fn new(pid: Pid) -> Self { Self { - name: String::with_capacity(20), + name: String::new(), pid, parent: None, - cmd: Vec::with_capacity(2), - environ: Vec::with_capacity(10), + cmd: Vec::new(), + environ: Vec::new(), exe: PathBuf::new(), cwd: PathBuf::new(), root: PathBuf::new(), @@ -277,7 +277,7 @@ pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) { p.updated = true; } -pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &Path) { +pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) { let data = match get_all_data(path.join("io"), 16_384) { Ok(d) => d, Err(_) => return, @@ -345,7 +345,7 @@ fn get_status(p: &mut ProcessInner, part: &str) { .unwrap_or_else(|| ProcessStatus::Unknown(0)); } -fn refresh_user_group_ids(p: &mut ProcessInner, path: &mut P) { +fn refresh_user_group_ids(p: &mut ProcessInner, path: &mut PathHandler) { if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) = get_uid_and_gid(path.join("status")) { @@ -356,6 +356,63 @@ fn refresh_user_group_ids(p: &mut ProcessInner, path: &mut P) { } } +#[allow(clippy::too_many_arguments)] +fn update_proc_info( + p: &mut ProcessInner, + refresh_kind: ProcessRefreshKind, + proc_path: &mut PathHandler, + parts: &[&str], + memory: u64, + virtual_memory: u64, + uptime: u64, + info: &SystemInfo, +) { + get_status(p, parts[2]); + + if refresh_kind.user() && p.user_id.is_none() { + refresh_user_group_ids(p, proc_path); + } + + if refresh_kind.exe() && p.exe == Path::new("") { + match proc_path.join("exe").read_link() { + Ok(exe_path) => p.exe = exe_path, + Err(_error) => { + sysinfo_debug!("Failed to retrieve exe for {}: {_error:?}", p.pid().0); + // Do not use cmd[0] because it is not the same thing. + // See https://github.com/GuillaumeGomez/sysinfo/issues/697. + p.exe = PathBuf::new(); + } + } + } + + if refresh_kind.cmd() && p.cmd.is_empty() { + p.cmd = copy_from_file(proc_path.join("cmdline")); + } + if refresh_kind.environ() { + p.environ = copy_from_file(proc_path.join("environ")); + } + if refresh_kind.cwd() { + p.cwd = realpath(proc_path.join("cwd")); + } + if refresh_kind.root() { + p.root = realpath(proc_path.join("root")); + } + + update_time_and_memory( + proc_path, + p, + parts, + memory, + virtual_memory, + uptime, + info, + refresh_kind, + ); + if refresh_kind.disk_usage() { + update_process_disk_activity(p, proc_path); + } +} + fn retrieve_all_new_process_info( pid: Pid, proc_list: &ProcessInner, @@ -366,7 +423,7 @@ fn retrieve_all_new_process_info( uptime: u64, ) -> Process { let mut p = ProcessInner::new(pid); - let mut tmp = PathHandler::new(path); + let mut proc_path = PathHandler::new(path); let name = parts[1]; p.parent = if proc_list.pid.0 != 0 { @@ -383,43 +440,19 @@ fn retrieve_all_new_process_info( .start_time_without_boot_time .saturating_add(info.boot_time); - get_status(&mut p, parts[2]); - - if refresh_kind.user() { - refresh_user_group_ids(&mut p, &mut tmp); - } - p.name = name.into(); - match tmp.join("exe").read_link() { - Ok(exe_path) => { - p.exe = exe_path; - } - Err(_) => { - // Do not use cmd[0] because it is not the same thing. - // See https://github.com/GuillaumeGomez/sysinfo/issues/697. - p.exe = PathBuf::new() - } - } - - p.cmd = copy_from_file(tmp.join("cmdline")); - p.environ = copy_from_file(tmp.join("environ")); - p.cwd = realpath(tmp.join("cwd")); - p.root = realpath(tmp.join("root")); - - update_time_and_memory( - path, + update_proc_info( &mut p, + refresh_kind, + &mut proc_path, parts, proc_list.memory, proc_list.virtual_memory, uptime, info, - refresh_kind, ); - if refresh_kind.disk_usage() { - update_process_disk_activity(&mut p, path); - } + Process { inner: p } } @@ -441,9 +474,6 @@ pub(crate) fn _get_process_data( _ => return Err(()), }; - let parent_memory = proc_list.memory; - let parent_virtual_memory = proc_list.virtual_memory; - let data; let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { let entry = &mut entry.inner; @@ -470,22 +500,21 @@ pub(crate) fn _get_process_data( // If the start time differs, then it means it's not the same process anymore and that we // need to get all its information, hence why we check it here. if start_time_without_boot_time == entry.start_time_without_boot_time { - get_status(entry, parts[2]); - update_time_and_memory( - path, + let mut proc_path = PathHandler::new(path); + + update_proc_info( entry, + refresh_kind, + &mut proc_path, &parts, - parent_memory, - parent_virtual_memory, + proc_list.memory, + proc_list.virtual_memory, uptime, info, - refresh_kind, ); - if refresh_kind.disk_usage() { - update_process_disk_activity(entry, path); - } + if refresh_kind.user() && entry.user_id.is_none() { - refresh_user_group_ids(entry, &mut PathBuf::from(path)); + refresh_user_group_ids(entry, &mut proc_path); } return Ok((None, pid)); } @@ -515,7 +544,7 @@ pub(crate) fn _get_process_data( #[allow(clippy::too_many_arguments)] fn update_time_and_memory( - path: &Path, + path: &mut PathHandler, entry: &mut ProcessInner, parts: &[&str], parent_memory: u64, @@ -525,18 +554,20 @@ fn update_time_and_memory( refresh_kind: ProcessRefreshKind, ) { { - // rss - entry.memory = u64::from_str(parts[23]) - .unwrap_or(0) - .saturating_mul(info.page_size_b); - if entry.memory >= parent_memory { - entry.memory -= parent_memory; - } - // vsz correspond to the Virtual memory size in bytes. - // see: https://man7.org/linux/man-pages/man5/proc.5.html - entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); - if entry.virtual_memory >= parent_virtual_memory { - entry.virtual_memory -= parent_virtual_memory; + if refresh_kind.memory() { + // rss + entry.memory = u64::from_str(parts[23]) + .unwrap_or(0) + .saturating_mul(info.page_size_b); + if entry.memory >= parent_memory { + entry.memory -= parent_memory; + } + // vsz correspond to the Virtual memory size in bytes. + // see: https://man7.org/linux/man-pages/man5/proc.5.html + entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); + if entry.virtual_memory >= parent_virtual_memory { + entry.virtual_memory -= parent_virtual_memory; + } } set_time( entry, @@ -547,7 +578,7 @@ fn update_time_and_memory( } refresh_procs( entry, - &path.join("task"), + path.join("task"), entry.pid, uptime, info, @@ -633,19 +664,14 @@ fn copy_from_file(entry: &Path) -> Vec { sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e); Vec::new() } else { - let mut out = Vec::with_capacity(20); - let mut start = 0; - for (pos, x) in data.iter().enumerate() { - if *x == 0 { - if pos - start >= 1 { - if let Ok(s) = - std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned()) - { - out.push(s); - } - } - start = pos + 1; // to keeping prevent '\0' + let mut out = Vec::with_capacity(10); + let mut data = data.as_slice(); + while let Some(pos) = data.iter().position(|c| *c == 0) { + match std::str::from_utf8(&data[..pos]).map(|s| s.trim()) { + Ok(s) if !s.is_empty() => out.push(s.to_string()), + _ => {} } + data = &data[pos + 1..]; } out } diff --git a/src/windows/process.rs b/src/windows/process.rs index f2f8b148a..9ea4f8ed0 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -357,15 +357,10 @@ impl ProcessInner { let info = info.assume_init(); let name = get_process_name(pid).unwrap_or_default(); - let exe = get_exe(&process_handler); - let mut root = exe.clone(); - root.pop(); - let (cmd, environ, cwd) = match get_process_params(&process_handler) { - Ok(args) => args, - Err(_e) => { - sysinfo_debug!("Failed to get process parameters: {}", _e); - (Vec::new(), Vec::new(), PathBuf::new()) - } + let exe = if refresh_kind.exe() { + get_exe(&process_handler) + } else { + PathBuf::new() }; let (start_time, run_time) = get_start_and_run_time(*process_handler, now); let parent = if info.InheritedFromUniqueProcessId != 0 { @@ -374,17 +369,17 @@ impl ProcessInner { None }; let user_id = get_process_user_id(&process_handler, refresh_kind); - Some(Self { + let mut p = Self { handle: Some(Arc::new(process_handler)), name, pid, parent, user_id, - cmd, - environ, + cmd: Vec::new(), + environ: Vec::new(), exe, - cwd, - root, + cwd: PathBuf::new(), + root: PathBuf::new(), status: ProcessStatus::Run, memory: 0, virtual_memory: 0, @@ -397,7 +392,9 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - }) + }; + get_process_params(&mut p, refresh_kind); + Some(p) } } @@ -412,29 +409,24 @@ impl ProcessInner { ) -> Self { if let Some(handle) = get_process_handler(pid) { unsafe { - let exe = get_exe(&handle); - let mut root = exe.clone(); - root.pop(); - let (cmd, environ, cwd) = match get_process_params(&handle) { - Ok(args) => args, - Err(_e) => { - sysinfo_debug!("Failed to get process parameters: {}", _e); - (Vec::new(), Vec::new(), PathBuf::new()) - } + let exe = if refresh_kind.exe() { + get_exe(&handle) + } else { + PathBuf::new() }; let (start_time, run_time) = get_start_and_run_time(*handle, now); let user_id = get_process_user_id(&handle, refresh_kind); - Self { + let mut p = Self { handle: Some(Arc::new(handle)), name, pid, user_id, parent, - cmd, - environ, + cmd: Vec::new(), + environ: Vec::new(), exe, - cwd, - root, + cwd: PathBuf::new(), + root: PathBuf::new(), status: ProcessStatus::Run, memory, virtual_memory, @@ -447,9 +439,17 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - } + }; + + get_process_params(&mut p, refresh_kind); + p } } else { + let exe = if refresh_kind.exe() { + get_executable_path(pid) + } else { + PathBuf::new() + }; Self { handle: None, name, @@ -458,7 +458,7 @@ impl ProcessInner { parent, cmd: Vec::new(), environ: Vec::new(), - exe: get_executable_path(pid), + exe, cwd: PathBuf::new(), root: PathBuf::new(), status: ProcessStatus::Run, @@ -489,7 +489,20 @@ impl ProcessInner { if refresh_kind.disk_usage() { update_disk_usage(self); } - update_memory(self); + if refresh_kind.memory() { + update_memory(self); + } + unsafe { + get_process_params(self, refresh_kind); + } + if refresh_kind.exe() { + unsafe { + self.exe = match self.handle.as_ref() { + Some(handle) => get_exe(handle), + None => get_executable_path(self.pid), + }; + } + } self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } @@ -639,6 +652,24 @@ unsafe fn get_process_times(handle: HANDLE) -> u64 { super::utils::filetime_to_u64(fstart) } +// On Windows, the root folder is always the current drive. So we get it from its `cwd`. +fn update_root(refresh_kind: ProcessRefreshKind, cwd: &Path, root: &mut PathBuf) { + if !refresh_kind.root() { + return; + } + if cwd.as_os_str().is_empty() || !cwd.has_root() { + *root = PathBuf::new(); + return; + } + let mut ancestors = cwd.ancestors().peekable(); + while let Some(path) = ancestors.next() { + if ancestors.peek().is_none() { + *root = path.into(); + break; + } + } +} + #[inline] fn compute_start(process_times: u64) -> u64 { // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and @@ -664,13 +695,13 @@ pub(crate) fn get_start_time(handle: HANDLE) -> u64 { } unsafe fn ph_query_process_variable_size( - process_handle: &HandleWrapper, + process_handle: HANDLE, process_information_class: PROCESSINFOCLASS, ) -> Option> { let mut return_length = MaybeUninit::::uninit(); let mut status = NtQueryInformationProcess( - **process_handle, + process_handle, process_information_class as _, null_mut(), 0, @@ -693,7 +724,7 @@ unsafe fn ph_query_process_variable_size( let buf_len = (return_length as usize) / 2; let mut buffer: Vec = Vec::with_capacity(buf_len + 1); status = NtQueryInformationProcess( - **process_handle, + process_handle, process_information_class as _, buffer.as_mut_ptr() as *mut _, return_length, @@ -727,13 +758,10 @@ unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec { res } -unsafe fn get_region_size( - handle: &HandleWrapper, - ptr: *const c_void, -) -> Result { +unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result { let mut meminfo = MaybeUninit::::uninit(); if VirtualQueryEx( - **handle, + handle, Some(ptr), meminfo.as_mut_ptr().cast(), size_of::(), @@ -746,7 +774,7 @@ unsafe fn get_region_size( } unsafe fn get_process_data( - handle: &HandleWrapper, + handle: HANDLE, ptr: *const c_void, size: usize, ) -> Result, &'static str> { @@ -754,7 +782,7 @@ unsafe fn get_process_data( let mut bytes_read = 0; if ReadProcessMemory( - **handle, + handle, ptr, buffer.as_mut_ptr().cast(), size, @@ -777,25 +805,25 @@ unsafe fn get_process_data( } trait RtlUserProcessParameters { - fn get_cmdline(&self, handle: &HandleWrapper) -> Result, &'static str>; - fn get_cwd(&self, handle: &HandleWrapper) -> Result, &'static str>; - fn get_environ(&self, handle: &HandleWrapper) -> Result, &'static str>; + fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str>; + fn get_cwd(&self, handle: HANDLE) -> Result, &'static str>; + fn get_environ(&self, handle: HANDLE) -> Result, &'static str>; } macro_rules! impl_RtlUserProcessParameters { ($t:ty) => { impl RtlUserProcessParameters for $t { - fn get_cmdline(&self, handle: &HandleWrapper) -> Result, &'static str> { + fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.CommandLine.Buffer; let size = self.CommandLine.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } - fn get_cwd(&self, handle: &HandleWrapper) -> Result, &'static str> { + fn get_cwd(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.CurrentDirectory.DosPath.Buffer; let size = self.CurrentDirectory.DosPath.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } - fn get_environ(&self, handle: &HandleWrapper) -> Result, &'static str> { + fn get_environ(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.Environment; unsafe { let size = get_region_size(handle, ptr as _)?; @@ -809,17 +837,24 @@ macro_rules! impl_RtlUserProcessParameters { impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); -unsafe fn get_process_params( - handle: &HandleWrapper, -) -> Result<(Vec, Vec, PathBuf), &'static str> { +unsafe fn get_process_params(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { if !cfg!(target_pointer_width = "64") { - return Err("Non 64 bit targets are not supported"); + sysinfo_debug!("Non 64 bit targets are not supported"); + return; + } + if !(refresh_kind.cmd() || refresh_kind.environ() || refresh_kind.cwd() || refresh_kind.root()) + { + return; } + let handle = match process.handle.as_ref().map(|handle| handle.0) { + Some(h) => h, + None => return, + }; // First check if target process is running in wow64 compatibility emulator let mut pwow32info = MaybeUninit::<*const c_void>::uninit(); if NtQueryInformationProcess( - **handle, + handle, ProcessWow64Information, pwow32info.as_mut_ptr().cast(), size_of::<*const c_void>() as u32, @@ -827,7 +862,8 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to check WOW64 information about the process"); + sysinfo_debug!("Unable to check WOW64 information about the process"); + return; } let pwow32info = pwow32info.assume_init(); @@ -836,7 +872,7 @@ unsafe fn get_process_params( let mut pbasicinfo = MaybeUninit::::uninit(); if NtQueryInformationProcess( - **handle, + handle, ProcessBasicInformation, pbasicinfo.as_mut_ptr().cast(), size_of::() as u32, @@ -844,13 +880,14 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to get basic process information"); + sysinfo_debug!("Unable to get basic process information"); + return; } let pinfo = pbasicinfo.assume_init(); let mut peb = MaybeUninit::::uninit(); if ReadProcessMemory( - **handle, + handle, pinfo.PebBaseAddress.cast(), peb.as_mut_ptr().cast(), size_of::(), @@ -858,14 +895,15 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to read process PEB"); + sysinfo_debug!("Unable to read process PEB"); + return; } let peb = peb.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( - **handle, + handle, peb.ProcessParameters.cast(), proc_params.as_mut_ptr().cast(), size_of::(), @@ -873,21 +911,26 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to read process parameters"); + sysinfo_debug!("Unable to read process parameters"); + return; } let proc_params = proc_params.assume_init(); - return Ok(( - get_cmd_line(&proc_params, handle), - get_proc_env(&proc_params, handle), - get_cwd(&proc_params, handle), - )); + get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); + get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); + get_cwd_and_root( + &proc_params, + handle, + refresh_kind, + &mut process.cwd, + &mut process.root, + ); } // target is a 32 bit process in wow64 mode let mut peb32 = MaybeUninit::::uninit(); if ReadProcessMemory( - **handle, + handle, pwow32info, peb32.as_mut_ptr().cast(), size_of::(), @@ -895,13 +938,14 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to read PEB32"); + sysinfo_debug!("Unable to read PEB32"); + return; } let peb32 = peb32.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( - **handle, + handle, peb32.ProcessParameters as *mut _, proc_params.as_mut_ptr().cast(), size_of::(), @@ -909,22 +953,43 @@ unsafe fn get_process_params( ) .is_err() { - return Err("Unable to read 32 bit process parameters"); + sysinfo_debug!("Unable to read 32 bit process parameters"); + return; } let proc_params = proc_params.assume_init(); - Ok(( - get_cmd_line(&proc_params, handle), - get_proc_env(&proc_params, handle), - get_cwd(&proc_params, handle), - )) + get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); + get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); + get_cwd_and_root( + &proc_params, + handle, + refresh_kind, + &mut process.cwd, + &mut process.root, + ); } -fn get_cwd(params: &T, handle: &HandleWrapper) -> PathBuf { +fn get_cwd_and_root( + params: &T, + handle: HANDLE, + refresh_kind: ProcessRefreshKind, + cwd: &mut PathBuf, + root: &mut PathBuf, +) { + if !refresh_kind.cwd() && !refresh_kind.root() { + return; + } match params.get_cwd(handle) { - Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Ok(buffer) => unsafe { + let tmp_cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())); + // Should always be called after we refreshed `cwd`. + update_root(refresh_kind, &tmp_cwd, root); + if refresh_kind.cwd() { + *cwd = tmp_cwd; + } + }, Err(_e) => { - sysinfo_debug!("get_cwd failed to get data: {}", _e); - PathBuf::new() + sysinfo_debug!("get_cwd_and_root failed to get data: {:?}", _e); + *cwd = PathBuf::new(); } } } @@ -938,10 +1003,7 @@ unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String { } } -fn get_cmd_line_old( - params: &T, - handle: &HandleWrapper, -) -> Vec { +fn get_cmd_line_old(params: &T, handle: HANDLE) -> Vec { match params.get_cmdline(handle) { Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) }, Err(_e) => { @@ -952,7 +1014,7 @@ fn get_cmd_line_old( } #[allow(clippy::cast_ptr_alignment)] -fn get_cmd_line_new(handle: &HandleWrapper) -> Vec { +fn get_cmd_line_new(handle: HANDLE) -> Vec { unsafe { if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) { @@ -965,25 +1027,41 @@ fn get_cmd_line_new(handle: &HandleWrapper) -> Vec { } } -fn get_cmd_line(params: &T, handle: &HandleWrapper) -> Vec { +fn get_cmd_line( + params: &T, + handle: HANDLE, + refresh_kind: ProcessRefreshKind, + cmd_line: &mut Vec, +) { + if !refresh_kind.cmd() { + return; + } if *WINDOWS_8_1_OR_NEWER { - get_cmd_line_new(handle) + *cmd_line = get_cmd_line_new(handle); } else { - get_cmd_line_old(params, handle) + *cmd_line = get_cmd_line_old(params, handle); } } -fn get_proc_env(params: &T, handle: &HandleWrapper) -> Vec { +fn get_proc_env( + params: &T, + handle: HANDLE, + refresh_kind: ProcessRefreshKind, + environ: &mut Vec, +) { + if !refresh_kind.environ() { + return; + } match params.get_environ(handle) { Ok(buffer) => { let equals = "=".encode_utf16().next().unwrap(); let raw_env = buffer; - let mut result = Vec::new(); + environ.clear(); let mut begin = 0; while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { let end = begin + offset; if raw_env[begin..end].iter().any(|&c| c == equals) { - result.push( + environ.push( OsString::from_wide(&raw_env[begin..end]) .to_string_lossy() .into_owned(), @@ -993,11 +1071,10 @@ fn get_proc_env(params: &T, handle: &HandleWrapper) break; } } - result } Err(_e) => { sysinfo_debug!("get_proc_env failed to get data: {}", _e); - Vec::new() + *environ = Vec::new(); } } } diff --git a/src/windows/system.rs b/src/windows/system.rs index 3a7c6566b..685e75272 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -296,8 +296,10 @@ impl SystemInner { .map(|start| start == proc_.start_time()) .unwrap_or(true) { - proc_.memory = pi.WorkingSetSize as _; - proc_.virtual_memory = pi.VirtualSize as _; + if refresh_kind.memory() { + proc_.memory = pi.WorkingSetSize as _; + proc_.virtual_memory = pi.VirtualSize as _; + } proc_.update(refresh_kind, nb_cpus, now); return None; } @@ -305,6 +307,11 @@ impl SystemInner { sysinfo_debug!("owner changed for PID {}", proc_.pid()); } let name = get_process_name(&pi, pid); + let (memory, virtual_memory) = if refresh_kind.memory() { + (pi.WorkingSetSize as _, pi.VirtualSize as _) + } else { + (0, 0) + }; let mut p = ProcessInner::new_full( pid, if pi.InheritedFromUniqueProcessId as usize != 0 { @@ -312,13 +319,13 @@ impl SystemInner { } else { None }, - pi.WorkingSetSize as _, - pi.VirtualSize as _, + memory, + virtual_memory, name, now, refresh_kind, ); - p.update(refresh_kind, nb_cpus, now); + p.update(refresh_kind.without_memory(), nb_cpus, now); Some(Process { inner: p }) }) .collect::>(); diff --git a/tests/process.rs b/tests/process.rs index 47d95acde..32c12f810 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -632,22 +632,106 @@ fn test_process_creds() { })); } -// Regression test for +// This test ensures that only the requested information is retrieved. #[test] -fn test_process_memory_refresh() { +fn test_process_specific_refresh() { + use std::path::Path; + use sysinfo::{DiskUsage, ProcessRefreshKind}; + if !sysinfo::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } - // Ensure the process memory is available on the first refresh. - let mut s = System::new(); + fn check_empty(s: &System, pid: Pid) { + let p = s.process(pid).unwrap(); + + // Name should never be empty. + assert!(!p.name().is_empty()); + if cfg!(target_os = "windows") { + assert_eq!(p.user_id(), None); + } + assert_eq!(p.environ().len(), 0); + assert_eq!(p.cmd().len(), 0); + assert_eq!(p.exe(), Path::new("")); + assert_eq!(p.cwd(), Path::new("")); + assert_eq!(p.root(), Path::new("")); + assert_eq!(p.memory(), 0); + assert_eq!(p.virtual_memory(), 0); + // These two won't be checked, too much lazyness in testing them... + assert_eq!(p.disk_usage(), DiskUsage::default()); + assert_eq!(p.cpu_usage(), 0.); + } - // Refresh our own process + let mut s = System::new(); let pid = Pid::from_u32(std::process::id()); - s.refresh_process_specifics(pid, sysinfo::ProcessRefreshKind::new()); - let proc = s.process(pid).unwrap(); - // Check that the memory values re not empty. - assert!(proc.memory() > 0); - assert!(proc.virtual_memory() > 0); + macro_rules! update_specific_and_check { + (memory) => { + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + { + let p = s.process(pid).unwrap(); + assert_eq!(p.memory(), 0, "failed 0 check for memory"); + assert_eq!(p.virtual_memory(), 0, "failed 0 check for virtual memory"); + } + s.refresh_process_specifics(pid, ProcessRefreshKind::new().with_memory()); + { + let p = s.process(pid).unwrap(); + assert_ne!(p.memory(), 0, "failed non-0 check for memory"); + assert_ne!(p.virtual_memory(), 0, "failed non-0 check for virtual memory"); + } + // And now we check that re-refreshing nothing won't remove the + // information. + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + { + let p = s.process(pid).unwrap(); + assert_ne!(p.memory(), 0, "failed non-0 check (number 2) for memory"); + assert_ne!(p.virtual_memory(), 0, "failed non-0 check(number 2) for virtual memory"); + } + }; + ($name:ident, $method:ident, $($extra:tt)+) => { + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + { + let p = s.process(pid).unwrap(); + assert_eq!( + p.$name()$($extra)+, + concat!("failed 0 check check for ", stringify!($name)), + ); + } + s.refresh_process_specifics(pid, ProcessRefreshKind::new().$method()); + { + let p = s.process(pid).unwrap(); + assert_ne!( + p.$name()$($extra)+, + concat!("failed non-0 check check for ", stringify!($name)),); + } + // And now we check that re-refreshing nothing won't remove the + // information. + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + { + let p = s.process(pid).unwrap(); + assert_ne!( + p.$name()$($extra)+, + concat!("failed non-0 check (number 2) check for ", stringify!($name)),); + } + } + } + + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + check_empty(&s, pid); + + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + check_empty(&s, pid); + + update_specific_and_check!(memory); + update_specific_and_check!(environ, with_environ, .len(), 0); + update_specific_and_check!(cmd, with_cmd, .len(), 0); + if !cfg!(any( + target_os = "macos", + target_os = "ios", + feature = "apple-sandbox", + )) { + update_specific_and_check!(root, with_root, , Path::new("")); + } + update_specific_and_check!(exe, with_exe, , Path::new("")); + update_specific_and_check!(cwd, with_cwd, , Path::new("")); }