From 02dfb892f265c7347588361aed85821129aa0caf Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 8 Nov 2023 22:56:24 +0100 Subject: [PATCH 1/7] Extend `ProcessRefreshKind` to allow more specific updates --- src/common.rs | 18 +++ src/unix/apple/macos/process.rs | 239 ++++++++++++++++++++------------ src/unix/freebsd/process.rs | 47 +++++-- src/unix/freebsd/system.rs | 40 ++++-- src/unix/linux/process.rs | 65 +++++---- src/windows/process.rs | 72 +++++++--- src/windows/system.rs | 17 ++- tests/process.rs | 76 ++++++++++ 8 files changed, 413 insertions(+), 161 deletions(-) diff --git a/src/common.rs b/src/common.rs index a676faa3b..18bb82120 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, } } @@ -1451,6 +1463,12 @@ impl ProcessRefreshKind { 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, 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/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs index 7bd997ed4..90769b0e9 100644 --- a/src/unix/apple/macos/process.rs +++ b/src/unix/apple/macos/process.rs @@ -367,47 +367,17 @@ unsafe fn create_new_process( 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 (cwd, root) = get_cwd_root(pid, refresh_kind); 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), - })); - } - _ => {} + if let Some((exe, name)) = get_exe_and_name_backup(pid, refresh_kind) { + return Ok(Some(Process { + inner: ProcessInner::new_empty(pid, exe, name, cwd, root), + })); } + // If we can't even have the name, no point in keeping it. return Err(()); } }; @@ -416,6 +386,108 @@ 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, cwd, root); + match get_process_infos(pid, refresh_kind) { + Some((exe, name, cmd, environ)) => { + p.exe = exe; + p.name = name; + p.cmd = cmd; + p.environ = environ; + } + None => { + if let Some((exe, name)) = get_exe_and_name_backup(pid, refresh_kind) { + p.exe = exe; + p.name = name; + } else { + // If we can't even have the name, no point in keeping it. + return Err(()); + } + } + } + + 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`. +fn get_exe_and_name_backup( + pid: Pid, + refresh_kind: ProcessRefreshKind, +) -> Option<(PathBuf, String)> { + 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 mut exe = PathBuf::from(tmp); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + if !refresh_kind.exe() { + exe = PathBuf::new(); + } + Some((exe, name)) + } + _ => Err(()), + } +} + +/// Returns `cwd` and `root`. +fn get_cwd_root(pid: Pid, refresh_kind: ProcessRefreshKind) -> (PathBuf, PathBuf) { + if !refresh_kind.cwd() && !refresh_kind.root() { + return (PathBuf::new(), PathBuf::new()); + } + 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 _, + ); + if result < 1 { + return (PathBuf::new(), PathBuf::new()); + } + let cwd = if refresh_kind.cwd() { + convert_node_path_info(&vnodepathinfo.pvi_cdir) + } else { + PathBuf::new() + }; + let root = if refresh_kind.root() { + convert_node_path_info(&vnodepathinfo.pvi_rdir) + } else { + PathBuf::new() + }; + (cwd, root) +} + +/// Returns (exe, name, cmd, environ) +fn get_process_infos( + pid: Pid, + refresh_kind: ProcessRefreshKind, +) -> Option<(PathBuf, String, Vec, Vec)> { /* * /---------------\ 0x00000000 * | ::::::::::::: | @@ -446,7 +518,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, 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( @@ -462,7 +534,7 @@ unsafe fn create_new_process( "couldn't get arguments and environment size for PID {}", pid.0 ); - return Err(()); // not enough rights I assume? + return None; // not enough rights I assume? } let mut proc_args: Vec = Vec::with_capacity(arg_max as _); @@ -476,61 +548,47 @@ unsafe fn create_new_process( ) == -1 { sysinfo_debug!("couldn't get arguments and environment for PID {}", pid.0); - return Err(()); // What changed since the previous call? Dark magic! + return None; // 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::(), - ); - - // We skip `argc`. - let proc_args = &proc_args[mem::size_of::()..]; + if proc_args.is_empty() { + return None; + } + // 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::(), + ); - let (exe, proc_args) = get_exe(proc_args); - let name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); + // We skip `argc`. + let proc_args = &proc_args[mem::size_of::()..]; - let (cmd, proc_args) = get_arguments(proc_args, n_args); + let (mut exe, proc_args) = get_exe(proc_args); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); - let environ = get_environ(proc_args); - let mut p = ProcessInner::new(pid, parent, start_time, run_time, cwd, root); + if !refresh_kind.exe() { + exe = PathBuf::new(); + } - p.exe = exe; - p.name = name; - p.cmd = parse_command_line(&cmd); - p.environ = environ; - p + let (cmd, proc_args) = if refresh_kind.environ() || refresh_kind.cmd() { + get_arguments(proc_args, n_args); } else { - ProcessInner::new(pid, parent, start_time, run_time, cwd, root) + (Vec::new(), &[]) }; - - 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 })) + let environ = if refresh_kind.environ() { + get_environ(proc_args) + } else { + Vec::new() + }; + Some((exe, name, parse_command_line(&cmd), environ)) } fn get_exe(data: &[u8]) -> (PathBuf, &[u8]) { @@ -619,7 +677,6 @@ pub(crate) fn update_process( return create_new_process(pid, now, refresh_kind, Some(info)); } } - let task_info = get_task_info(pid); let mut thread_info = mem::zeroed::(); let (user_time, system_time, thread_status) = if libc::proc_pidinfo( pid.0, @@ -644,12 +701,18 @@ 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); + + 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.memory = task_info.pti_resident_size; - p.virtual_memory = task_info.pti_virtual_size; if refresh_kind.disk_usage() { update_proc_disk_activity(p); } diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs index d7fb7cae9..567b409fa 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() { @@ -241,16 +250,24 @@ 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(); + let exe = if refresh_kind.exe() { + get_sys_value_str( + &[ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PATHNAME, + kproc.ki_pid, + ], + &mut buffer, + ) + .map(|exe| PathBuf::from(exe)) + .unwrap_or_else(|| { + sysinfo_debug!("Failed to get `exe` for {}", kproc.ki_pid); + PathBuf::new() + }) + } else { + PathBuf::new() + }; // 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 +297,7 @@ pub(crate) unsafe fn get_process_data( memory, // procstat_getfiles cwd: PathBuf::new(), - exe: exe.into(), + exe, // 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. diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs index 23f0fc770..f0a8883bc 100644 --- a/src/unix/freebsd/system.rs +++ b/src/unix/freebsd/system.rs @@ -297,11 +297,15 @@ impl SystemInner { kd: *mut libc::kvm_t, kproc: &libc::kinfo_proc, mut proc_: Process, + refresh_kind: ProcessRefreshKind, ) { { 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 refresh_kind.cmd() { + proc_inner.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); + } + self.system_info + .get_proc_missing_info(kproc, proc_inner, refresh_kind); if !proc_inner.cmd.is_empty() { // First, we try to retrieve the name from the command line. let p = Path::new(&proc_inner.cmd[0]); @@ -320,7 +324,9 @@ impl SystemInner { // possible. proc_inner.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); } - proc_inner.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); + 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,7 +571,22 @@ 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) { + 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 refresh_kind.root() { + done += 1; + } + if done == 0 { + return; + } if self.procstat.is_null() { self.procstat = libc::procstat_open_sysctl(); } @@ -577,22 +598,21 @@ impl SystemInfo { 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..ee3b181aa 100644 --- a/src/unix/linux/process.rs +++ b/src/unix/linux/process.rs @@ -366,7 +366,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 { @@ -386,26 +386,37 @@ fn retrieve_all_new_process_info( get_status(&mut p, parts[2]); if refresh_kind.user() { - refresh_user_group_ids(&mut p, &mut tmp); + refresh_user_group_ids(&mut p, &mut proc_path); } 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() + if refresh_kind.exe() { + match proc_path.join("exe").read_link() { + Ok(exe_path) => { + p.exe = exe_path; + } + Err(_) => { + sysinfo_debug!("Failed to retrieve exe for {}", 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() + } } } - 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")); + if refresh_kind.cmd() { + 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( path, @@ -525,18 +536,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, diff --git a/src/windows/process.rs b/src/windows/process.rs index f2f8b148a..74a3bc4ab 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -323,7 +323,10 @@ unsafe fn get_process_name(pid: Pid) -> Option { name } -unsafe fn get_exe(process_handler: &HandleWrapper) -> PathBuf { +unsafe fn get_exe(process_handler: &HandleWrapper, refresh_kind: ProcessRefreshKind) -> PathBuf { + if !refresh_kind.exe() { + return PathBuf::new(); + } let mut exe_buf = [0u16; MAX_PATH as usize + 1]; GetModuleFileNameExW( **process_handler, @@ -358,9 +361,14 @@ impl ProcessInner { 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) { + let root = if refresh_kind.root() { + let mut root = exe.clone(); + root.pop(); + root + } else { + PathBuf::new(); + }; + let (cmd, environ, cwd) = match get_process_params(&process_handler, refresh_kind) { Ok(args) => args, Err(_e) => { sysinfo_debug!("Failed to get process parameters: {}", _e); @@ -415,7 +423,7 @@ impl ProcessInner { let exe = get_exe(&handle); let mut root = exe.clone(); root.pop(); - let (cmd, environ, cwd) = match get_process_params(&handle) { + let (cmd, environ, cwd) = match get_process_params(&handle, refresh_kind) { Ok(args) => args, Err(_e) => { sysinfo_debug!("Failed to get process parameters: {}", _e); @@ -458,7 +466,7 @@ impl ProcessInner { parent, cmd: Vec::new(), environ: Vec::new(), - exe: get_executable_path(pid), + exe: get_executable_path(pid, refresh_kind), cwd: PathBuf::new(), root: PathBuf::new(), status: ProcessStatus::Run, @@ -489,7 +497,9 @@ impl ProcessInner { if refresh_kind.disk_usage() { update_disk_usage(self); } - update_memory(self); + if refresh_kind.memory() { + update_memory(self); + } self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } @@ -811,10 +821,14 @@ impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); unsafe fn get_process_params( handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, ) -> Result<(Vec, Vec, PathBuf), &'static str> { if !cfg!(target_pointer_width = "64") { return Err("Non 64 bit targets are not supported"); } + if !refresh_kind.cmd() && !refresh_kind.environ() && !refresh_kind.cwd() { + return Ok((Vec::new(), Vec::new(), PathBuf::new())); + } // First check if target process is running in wow64 compatibility emulator let mut pwow32info = MaybeUninit::<*const c_void>::uninit(); @@ -878,9 +892,9 @@ unsafe fn get_process_params( 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), + get_proc_env(&proc_params, handle, refresh_kind), + get_cwd(&proc_params, handle, refresh_kind), )); } // target is a 32 bit process in wow64 mode @@ -913,13 +927,20 @@ unsafe fn get_process_params( } 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), + get_proc_env(&proc_params, handle, refresh_kind), + get_cwd(&proc_params, handle, refresh_kind), )) } -fn get_cwd(params: &T, handle: &HandleWrapper) -> PathBuf { +fn get_cwd( + params: &T, + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> PathBuf { + if !refresh_kind.cwd() { + return PathBuf::new(); + } match params.get_cwd(handle) { Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, Err(_e) => { @@ -965,7 +986,14 @@ fn get_cmd_line_new(handle: &HandleWrapper) -> Vec { } } -fn get_cmd_line(params: &T, handle: &HandleWrapper) -> Vec { +fn get_cmd_line( + params: &T, + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> Vec { + if !refresh_kind.cmd() { + return Vec::new(); + } if *WINDOWS_8_1_OR_NEWER { get_cmd_line_new(handle) } else { @@ -973,7 +1001,14 @@ fn get_cmd_line(params: &T, handle: &HandleWrapper) } } -fn get_proc_env(params: &T, handle: &HandleWrapper) -> Vec { +fn get_proc_env( + params: &T, + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> Vec { + if !refresh_kind.environ() { + return Vec::new(); + } match params.get_environ(handle) { Ok(buffer) => { let equals = "=".encode_utf16().next().unwrap(); @@ -1002,7 +1037,10 @@ fn get_proc_env(params: &T, handle: &HandleWrapper) } } -pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { +pub(crate) fn get_executable_path(_pid: Pid, refresh_kind: ProcessRefreshKind) -> PathBuf { + if !refresh_kind.exe() { + return PathBuf::new(); + } /*let where_req = format!("ProcessId={}", pid); if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { diff --git a/src/windows/system.rs b/src/windows/system.rs index 3a7c6566b..6529e18e3 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.witout_memory(), nb_cpus, now); Some(Process { inner: p }) }) .collect::>(); diff --git a/tests/process.rs b/tests/process.rs index 47d95acde..1a37d87ac 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -651,3 +651,79 @@ fn test_process_memory_refresh() { assert!(proc.memory() > 0); assert!(proc.virtual_memory() > 0); } + +// This test ensures that only the requested information is retrieved. +#[test] +fn test_process_specific_refresh() { + use std::path::Path; + use sysinfo::{DiskUsage, ProcessRefreshKind}; + + if !sysinfo::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + + 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.); + } + + let mut s = System::new(); + let pid = Pid::from_u32(std::process::id()); + + 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); + assert_eq!(p.virtual_memory(), 0); + } + s.refresh_process_specifics(pid, ProcessRefreshKind::new().with_memory()); + { + let p = s.process(pid).unwrap(); + assert_ne!(p.memory(), 0); + assert_ne!(p.virtual_memory(), 0); + } + }; + ($name:ident, $method:ident, $($extra:tt)+) => { + s.refresh_process_specifics(pid, ProcessRefreshKind::new()); + { + let p = s.process(pid).unwrap(); + assert_eq!(p.$name()$($extra)+); + } + s.refresh_process_specifics(pid, ProcessRefreshKind::new().$method()); + { + let p = s.process(pid).unwrap(); + assert_ne!(p.$name()$($extra)+); + } + } + } + + 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); + update_specific_and_check!(exe, with_exe, , Path::new("")); + update_specific_and_check!(cwd, with_cwd, , Path::new("")); + update_specific_and_check!(root, with_root, , Path::new("")); +} From 57be175c153d7f9fb2d287bd5da0969d182d4a82 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 9 Nov 2023 23:46:05 +0100 Subject: [PATCH 2/7] Finish extension of `ProcessRefreshKind` for Linux --- src/common.rs | 9 +-- src/unix/linux/process.rs | 155 +++++++++++++++++++++----------------- 2 files changed, 85 insertions(+), 79 deletions(-) diff --git a/src/common.rs b/src/common.rs index 18bb82120..23b1a9805 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1455,14 +1455,7 @@ 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); diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs index ee3b181aa..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, @@ -383,54 +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 proc_path); - } - p.name = name.into(); - if refresh_kind.exe() { - match proc_path.join("exe").read_link() { - Ok(exe_path) => { - p.exe = exe_path; - } - Err(_) => { - sysinfo_debug!("Failed to retrieve exe for {}", 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 = 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( - 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 } } @@ -452,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; @@ -481,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)); } @@ -526,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, @@ -560,7 +578,7 @@ fn update_time_and_memory( } refresh_procs( entry, - &path.join("task"), + path.join("task"), entry.pid, uptime, info, @@ -646,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 } From 4ee2081f5a867f97ea25eac00943f5f2f9839b66 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 12 Nov 2023 17:43:03 +0100 Subject: [PATCH 3/7] Finish extension of `ProcessRefreshKind` for Windows --- src/lib.rs | 15 --- src/windows/process.rs | 237 ++++++++++++++++++++++------------------- src/windows/system.rs | 2 +- tests/process.rs | 35 ++---- 4 files changed, 138 insertions(+), 151 deletions(-) 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/windows/process.rs b/src/windows/process.rs index 74a3bc4ab..e3c245d25 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -323,10 +323,7 @@ unsafe fn get_process_name(pid: Pid) -> Option { name } -unsafe fn get_exe(process_handler: &HandleWrapper, refresh_kind: ProcessRefreshKind) -> PathBuf { - if !refresh_kind.exe() { - return PathBuf::new(); - } +unsafe fn get_exe(process_handler: &HandleWrapper) -> PathBuf { let mut exe_buf = [0u16; MAX_PATH as usize + 1]; GetModuleFileNameExW( **process_handler, @@ -360,20 +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 root = if refresh_kind.root() { - let mut root = exe.clone(); - root.pop(); - root + let exe = if refresh_kind.exe() { + get_exe(&process_handler) } else { - PathBuf::new(); - }; - let (cmd, environ, cwd) = match get_process_params(&process_handler, refresh_kind) { - Ok(args) => args, - Err(_e) => { - sysinfo_debug!("Failed to get process parameters: {}", _e); - (Vec::new(), Vec::new(), PathBuf::new()) - } + PathBuf::new() }; let (start_time, run_time) = get_start_and_run_time(*process_handler, now); let parent = if info.InheritedFromUniqueProcessId != 0 { @@ -382,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, @@ -405,7 +392,10 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - }) + }; + get_process_params(&mut p, refresh_kind); + update_root(&mut p, refresh_kind); + Some(p) } } @@ -420,29 +410,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, refresh_kind) { - 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, @@ -455,10 +440,19 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - } + }; + + get_process_params(&mut p, refresh_kind); + update_root(&mut p, refresh_kind); + p } } else { - Self { + let exe = if refresh_kind.exe() { + get_executable_path(pid) + } else { + PathBuf::new() + }; + let mut p = Self { handle: None, name, pid, @@ -466,7 +460,7 @@ impl ProcessInner { parent, cmd: Vec::new(), environ: Vec::new(), - exe: get_executable_path(pid, refresh_kind), + exe, cwd: PathBuf::new(), root: PathBuf::new(), status: ProcessStatus::Run, @@ -481,7 +475,9 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - } + }; + update_root(&mut p, refresh_kind); + p } } @@ -500,6 +496,19 @@ impl ProcessInner { if refresh_kind.memory() { update_memory(self); } + unsafe { + get_process_params(self, refresh_kind); + } + if refresh_kind.exe() { + unsafe { + let exe = match self.handle.as_ref() { + Some(handle) => get_exe(handle), + None => get_executable_path(self.pid), + }; + self.exe = exe; + } + } + update_root(self, refresh_kind); self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } @@ -649,6 +658,13 @@ unsafe fn get_process_times(handle: HANDLE) -> u64 { super::utils::filetime_to_u64(fstart) } +fn update_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { + if refresh_kind.root() && process.exe.parent().is_some() { + process.root = process.exe.clone(); + process.root.pop(); + } +} + #[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 @@ -674,13 +690,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, @@ -703,7 +719,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, @@ -737,13 +753,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::(), @@ -756,7 +769,7 @@ unsafe fn get_region_size( } unsafe fn get_process_data( - handle: &HandleWrapper, + handle: HANDLE, ptr: *const c_void, size: usize, ) -> Result, &'static str> { @@ -764,7 +777,7 @@ unsafe fn get_process_data( let mut bytes_read = 0; if ReadProcessMemory( - **handle, + handle, ptr, buffer.as_mut_ptr().cast(), size, @@ -787,25 +800,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 _)?; @@ -819,21 +832,23 @@ macro_rules! impl_RtlUserProcessParameters { impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); -unsafe fn get_process_params( - handle: &HandleWrapper, - refresh_kind: ProcessRefreshKind, -) -> 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() { - return Ok((Vec::new(), Vec::new(), PathBuf::new())); + 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, @@ -841,7 +856,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(); @@ -850,7 +866,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, @@ -858,13 +874,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::(), @@ -872,14 +889,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::(), @@ -887,21 +905,20 @@ 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, refresh_kind), - get_proc_env(&proc_params, handle, refresh_kind), - get_cwd(&proc_params, handle, refresh_kind), - )); + get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); + get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); + get_cwd(&proc_params, handle, refresh_kind, &mut process.cwd); } // 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::(), @@ -909,13 +926,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::(), @@ -923,29 +941,31 @@ 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, refresh_kind), - get_proc_env(&proc_params, handle, refresh_kind), - get_cwd(&proc_params, handle, refresh_kind), - )) + get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); + get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); + get_cwd(&proc_params, handle, refresh_kind, &mut process.cwd); } fn get_cwd( params: &T, - handle: &HandleWrapper, + handle: HANDLE, refresh_kind: ProcessRefreshKind, -) -> PathBuf { + cwd: &mut PathBuf, +) { if !refresh_kind.cwd() { - return PathBuf::new(); + return; } match params.get_cwd(handle) { - Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Ok(buffer) => unsafe { + *cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())); + }, Err(_e) => { sysinfo_debug!("get_cwd failed to get data: {}", _e); - PathBuf::new() + *cwd = PathBuf::new(); } } } @@ -959,10 +979,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) => { @@ -973,7 +990,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) { @@ -988,37 +1005,39 @@ fn get_cmd_line_new(handle: &HandleWrapper) -> Vec { fn get_cmd_line( params: &T, - handle: &HandleWrapper, + handle: HANDLE, refresh_kind: ProcessRefreshKind, -) -> Vec { + cmd_line: &mut Vec, +) { if !refresh_kind.cmd() { - return Vec::new(); + 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, + handle: HANDLE, refresh_kind: ProcessRefreshKind, -) -> Vec { + environ: &mut Vec, +) { if !refresh_kind.environ() { - return Vec::new(); + 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(), @@ -1028,19 +1047,15 @@ fn get_proc_env( break; } } - result } Err(_e) => { sysinfo_debug!("get_proc_env failed to get data: {}", _e); - Vec::new() + *environ = Vec::new(); } } } -pub(crate) fn get_executable_path(_pid: Pid, refresh_kind: ProcessRefreshKind) -> PathBuf { - if !refresh_kind.exe() { - return PathBuf::new(); - } +pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { /*let where_req = format!("ProcessId={}", pid); if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { diff --git a/src/windows/system.rs b/src/windows/system.rs index 6529e18e3..685e75272 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -325,7 +325,7 @@ impl SystemInner { now, refresh_kind, ); - p.update(refresh_kind.witout_memory(), 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 1a37d87ac..5171dcd60 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -632,26 +632,6 @@ fn test_process_creds() { })); } -// Regression test for -#[test] -fn test_process_memory_refresh() { - if !sysinfo::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { - return; - } - - // Ensure the process memory is available on the first refresh. - let mut s = System::new(); - - // Refresh our own process - 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); -} - // This test ensures that only the requested information is retrieved. #[test] fn test_process_specific_refresh() { @@ -704,12 +684,17 @@ fn test_process_specific_refresh() { s.refresh_process_specifics(pid, ProcessRefreshKind::new()); { let p = s.process(pid).unwrap(); - assert_eq!(p.$name()$($extra)+); + 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)+); + assert_ne!( + p.$name()$($extra)+, + concat!("failed non-0 check check for ", stringify!($name)),); } } } @@ -723,7 +708,9 @@ fn test_process_specific_refresh() { update_specific_and_check!(memory); update_specific_and_check!(environ, with_environ, .len(), 0); update_specific_and_check!(cmd, with_cmd, .len(), 0); - update_specific_and_check!(exe, with_exe, , Path::new("")); + if cfg!(not(target_os = "windows")) { + update_specific_and_check!(exe, with_exe, , Path::new("")); + update_specific_and_check!(root, with_root, , Path::new("")); + } update_specific_and_check!(cwd, with_cwd, , Path::new("")); - update_specific_and_check!(root, with_root, , Path::new("")); } From a1c283d60cc0f6910c27537e3a2207835692665a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 12 Nov 2023 21:12:45 +0100 Subject: [PATCH 4/7] Finish extension of `ProcessRefreshKind` for mac --- src/unix/apple/macos/process.rs | 286 ++++++++++++++------------------ tests/process.rs | 37 ++++- 2 files changed, 153 insertions(+), 170 deletions(-) diff --git a/src/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs index 90769b0e9..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,33 +333,19 @@ 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 (cwd, root) = get_cwd_root(pid, refresh_kind); - let info = match info { Some(info) => info, None => { - if let Some((exe, name)) = get_exe_and_name_backup(pid, refresh_kind) { - 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(()); @@ -389,24 +359,12 @@ unsafe fn create_new_process( 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, cwd, root); - match get_process_infos(pid, refresh_kind) { - Some((exe, name, cmd, environ)) => { - p.exe = exe; - p.name = name; - p.cmd = cmd; - p.environ = environ; - } - None => { - if let Some((exe, name)) = get_exe_and_name_backup(pid, refresh_kind) { - p.exe = exe; - p.name = name; - } else { - // If we can't even have the name, no point in keeping it. - return Err(()); - } - } + 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); @@ -426,68 +384,79 @@ unsafe fn create_new_process( } /// Less efficient way to retrieve `exe` and `name`. -fn get_exe_and_name_backup( - pid: Pid, +unsafe fn get_exe_and_name_backup( + process: &mut ProcessInner, refresh_kind: ProcessRefreshKind, -) -> Option<(PathBuf, String)> { +) -> 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( - pid.0, + 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 mut exe = PathBuf::from(tmp); - let name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); - if !refresh_kind.exe() { - exe = PathBuf::new(); + 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; } - Some((exe, name)) + true } - _ => Err(()), + _ => 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) } -/// Returns `cwd` and `root`. -fn get_cwd_root(pid: Pid, refresh_kind: ProcessRefreshKind) -> (PathBuf, PathBuf) { +unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { if !refresh_kind.cwd() && !refresh_kind.root() { - return (PathBuf::new(), PathBuf::new()); + return; } let mut vnodepathinfo = mem::zeroed::(); let result = libc::proc_pidinfo( - pid.0, + process.pid.0, libc::PROC_PIDVNODEPATHINFO, 0, &mut vnodepathinfo as *mut _ as *mut _, mem::size_of::() as _, ); if result < 1 { - return (PathBuf::new(), PathBuf::new()); + 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; + } } - let cwd = if refresh_kind.cwd() { - convert_node_path_info(&vnodepathinfo.pvi_cdir) - } else { - PathBuf::new() - }; - let root = if refresh_kind.root() { - convert_node_path_info(&vnodepathinfo.pvi_rdir) - } else { - PathBuf::new() - }; - (cwd, root) } -/// Returns (exe, name, cmd, environ) -fn get_process_infos( - pid: Pid, - refresh_kind: ProcessRefreshKind, -) -> Option<(PathBuf, String, Vec, Vec)> { +unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) -> bool { /* * /---------------\ 0x00000000 * | ::::::::::::: | @@ -518,7 +487,7 @@ fn get_process_infos( * : : * \---------------/ 0xffffffff */ - let mut mib: [libc::c_int; 3] = [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( @@ -532,9 +501,9 @@ fn get_process_infos( { sysinfo_debug!( "couldn't get arguments and environment size for PID {}", - pid.0 + process.pid.0 ); - return None; // not enough rights I assume? + return false; // not enough rights I assume? } let mut proc_args: Vec = Vec::with_capacity(arg_max as _); @@ -547,14 +516,17 @@ fn get_process_infos( 0, ) == -1 { - sysinfo_debug!("couldn't get arguments and environment for PID {}", pid.0); - return None; // 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); if proc_args.is_empty() { - return None; + return false; } // We copy the number of arguments (`argc`) to `n_args`. let mut n_args: c_int = 0; @@ -567,28 +539,28 @@ fn get_process_infos( // We skip `argc`. let proc_args = &proc_args[mem::size_of::()..]; - let (mut exe, proc_args) = get_exe(proc_args); - let name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); + 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(); + } - if !refresh_kind.exe() { - exe = PathBuf::new(); + if refresh_kind.exe() { + process.exe = exe; } - let (cmd, proc_args) = if refresh_kind.environ() || refresh_kind.cmd() { - get_arguments(proc_args, n_args); - } else { - (Vec::new(), &[]) - }; - let environ = if refresh_kind.environ() { - get_environ(proc_args) - } else { - Vec::new() - }; - Some((exe, name, parse_command_line(&cmd), environ)) + if !refresh_kind.environ() && !refresh_kind.cmd() { + // Nothing else to be done! + return true; + } + 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]) { @@ -601,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..]; @@ -623,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..]; @@ -645,7 +627,6 @@ fn get_environ(mut data: &[u8]) -> Vec { data = &data[1..]; } } - environ } } @@ -660,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. @@ -677,6 +650,16 @@ pub(crate) fn update_process( return create_new_process(pid, now, refresh_kind, Some(info)); } } + + 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, @@ -712,14 +695,11 @@ pub(crate) fn update_process( p.virtual_memory = task_info.pti_virtual_size; } } - - if refresh_kind.disk_usage() { - update_proc_disk_activity(p); - } 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)) } } @@ -766,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/tests/process.rs b/tests/process.rs index 5171dcd60..26b7761e0 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -670,14 +670,22 @@ fn test_process_specific_refresh() { s.refresh_process_specifics(pid, ProcessRefreshKind::new()); { let p = s.process(pid).unwrap(); - assert_eq!(p.memory(), 0); - assert_eq!(p.virtual_memory(), 0); + 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); - assert_ne!(p.virtual_memory(), 0); + 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)+) => { @@ -696,6 +704,15 @@ fn test_process_specific_refresh() { 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)),); + } } } @@ -708,9 +725,15 @@ fn test_process_specific_refresh() { 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!(not(target_os = "windows")) { - update_specific_and_check!(exe, with_exe, , Path::new("")); + // if cfg!(not(target_os = "windows")) { + update_specific_and_check!(exe, with_exe, , Path::new("")); + // } + update_specific_and_check!(cwd, with_cwd, , Path::new("")); + 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!(cwd, with_cwd, , Path::new("")); } From ac3a04205cf899574a551957fdda42e9934f5d3e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 12 Nov 2023 23:16:19 +0100 Subject: [PATCH 5/7] Correctly handle root path on windows --- src/windows/process.rs | 20 +++++++++++++++++--- tests/process.rs | 4 +--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/windows/process.rs b/src/windows/process.rs index e3c245d25..173b93980 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -394,6 +394,7 @@ impl ProcessInner { written_bytes: 0, }; get_process_params(&mut p, refresh_kind); + // Should always be called after we refreshed `cwd`. update_root(&mut p, refresh_kind); Some(p) } @@ -443,6 +444,7 @@ impl ProcessInner { }; get_process_params(&mut p, refresh_kind); + // Should always be called after we refreshed `cwd`. update_root(&mut p, refresh_kind); p } @@ -476,6 +478,7 @@ impl ProcessInner { read_bytes: 0, written_bytes: 0, }; + // Should always be called after we refreshed `cwd`. update_root(&mut p, refresh_kind); p } @@ -508,6 +511,7 @@ impl ProcessInner { self.exe = exe; } } + // Should always be called after we refreshed `cwd`. update_root(self, refresh_kind); self.run_time = now.saturating_sub(self.start_time()); self.updated = true; @@ -658,10 +662,20 @@ 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(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { - if refresh_kind.root() && process.exe.parent().is_some() { - process.root = process.exe.clone(); - process.root.pop(); + if refresh_kind.root() && process.cwd.parent().is_some() { + if !process.cwd.has_root() { + process.root = PathBuf::new(); + return; + } + let mut ancestors = process.cwd.ancestors().peekable(); + while let Some(path) = ancestors.next() { + if ancestors.peek().is_none() { + process.root = path.into(); + break; + } + } } } diff --git a/tests/process.rs b/tests/process.rs index 26b7761e0..f54472f0e 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -725,14 +725,12 @@ fn test_process_specific_refresh() { 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!(not(target_os = "windows")) { update_specific_and_check!(exe, with_exe, , Path::new("")); - // } update_specific_and_check!(cwd, with_cwd, , Path::new("")); if !cfg!(any( target_os = "macos", target_os = "ios", - feature = "apple-sandbox" + feature = "apple-sandbox", )) { update_specific_and_check!(root, with_root, , Path::new("")); } From b70562dccaf404b7acebfb024f7ce7ab042452a8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 13 Nov 2023 21:28:26 +0100 Subject: [PATCH 6/7] Finish extension of `ProcessRefreshKind` for FreeBSD --- src/unix/freebsd/process.rs | 42 +++++++------ src/unix/freebsd/system.rs | 122 ++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 73 deletions(-) diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs index 567b409fa..42289dc61 100644 --- a/src/unix/freebsd/process.rs +++ b/src/unix/freebsd/process.rs @@ -248,26 +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 = if refresh_kind.exe() { - get_sys_value_str( - &[ - libc::CTL_KERN, - libc::KERN_PROC, - libc::KERN_PROC_PATHNAME, - kproc.ki_pid, - ], - &mut buffer, - ) - .map(|exe| PathBuf::from(exe)) - .unwrap_or_else(|| { - sysinfo_debug!("Failed to get `exe` for {}", kproc.ki_pid); - PathBuf::new() - }) - } else { - PathBuf::new() - }; // 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( @@ -297,7 +278,7 @@ pub(crate) unsafe fn get_process_data( memory, // procstat_getfiles cwd: PathBuf::new(), - exe, + 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. @@ -315,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 f0a8883bc..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,48 +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 _); - unsafe fn add_missing_proc_info( - &mut self, - kd: *mut libc::kvm_t, - kproc: &libc::kinfo_proc, - mut proc_: Process, - refresh_kind: ProcessRefreshKind, - ) { - { - let proc_inner = &mut proc_.inner; - if refresh_kind.cmd() { - proc_inner.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) 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); } - self.system_info - .get_proc_missing_info(kproc, proc_inner, refresh_kind); - 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.environ() { - proc_inner.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); + + if refresh_kind.cmd() { + proc_inner.cmd = cmd; } } - self.process_list.insert(proc_.inner.pid, proc_); + 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 _); + } } } @@ -589,9 +604,10 @@ impl SystemInfo { } if self.procstat.is_null() { self.procstat = libc::procstat_open_sysctl(); - } - if self.procstat.is_null() { - return; + 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() { From 3fc0b9ee7b727b1cfa590e948ca53cbbb78a3ffc Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 13 Nov 2023 21:51:01 +0100 Subject: [PATCH 7/7] Improve Process root information retrieval --- src/windows/process.rs | 74 ++++++++++++++++++++++++------------------ tests/process.rs | 4 +-- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/windows/process.rs b/src/windows/process.rs index 173b93980..9ea4f8ed0 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -394,8 +394,6 @@ impl ProcessInner { written_bytes: 0, }; get_process_params(&mut p, refresh_kind); - // Should always be called after we refreshed `cwd`. - update_root(&mut p, refresh_kind); Some(p) } } @@ -444,8 +442,6 @@ impl ProcessInner { }; get_process_params(&mut p, refresh_kind); - // Should always be called after we refreshed `cwd`. - update_root(&mut p, refresh_kind); p } } else { @@ -454,7 +450,7 @@ impl ProcessInner { } else { PathBuf::new() }; - let mut p = Self { + Self { handle: None, name, pid, @@ -477,10 +473,7 @@ impl ProcessInner { old_written_bytes: 0, read_bytes: 0, written_bytes: 0, - }; - // Should always be called after we refreshed `cwd`. - update_root(&mut p, refresh_kind); - p + } } } @@ -504,15 +497,12 @@ impl ProcessInner { } if refresh_kind.exe() { unsafe { - let exe = match self.handle.as_ref() { + self.exe = match self.handle.as_ref() { Some(handle) => get_exe(handle), None => get_executable_path(self.pid), }; - self.exe = exe; } } - // Should always be called after we refreshed `cwd`. - update_root(self, refresh_kind); self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } @@ -663,18 +653,19 @@ unsafe fn get_process_times(handle: HANDLE) -> u64 { } // On Windows, the root folder is always the current drive. So we get it from its `cwd`. -fn update_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { - if refresh_kind.root() && process.cwd.parent().is_some() { - if !process.cwd.has_root() { - process.root = PathBuf::new(); - return; - } - let mut ancestors = process.cwd.ancestors().peekable(); - while let Some(path) = ancestors.next() { - if ancestors.peek().is_none() { - process.root = path.into(); - break; - } +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; } } } @@ -851,7 +842,8 @@ unsafe fn get_process_params(process: &mut ProcessInner, refresh_kind: ProcessRe sysinfo_debug!("Non 64 bit targets are not supported"); return; } - if !refresh_kind.cmd() && !refresh_kind.environ() && !refresh_kind.cwd() { + 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) { @@ -926,7 +918,13 @@ unsafe fn get_process_params(process: &mut ProcessInner, refresh_kind: ProcessRe let proc_params = proc_params.assume_init(); get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); - get_cwd(&proc_params, handle, refresh_kind, &mut process.cwd); + get_cwd_and_root( + &proc_params, + handle, + refresh_kind, + &mut process.cwd, + &mut process.root, + ); } // target is a 32 bit process in wow64 mode @@ -961,24 +959,36 @@ unsafe fn get_process_params(process: &mut ProcessInner, refresh_kind: ProcessRe let proc_params = proc_params.assume_init(); get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); - get_cwd(&proc_params, handle, refresh_kind, &mut process.cwd); + get_cwd_and_root( + &proc_params, + handle, + refresh_kind, + &mut process.cwd, + &mut process.root, + ); } -fn get_cwd( +fn get_cwd_and_root( params: &T, handle: HANDLE, refresh_kind: ProcessRefreshKind, cwd: &mut PathBuf, + root: &mut PathBuf, ) { - if !refresh_kind.cwd() { + if !refresh_kind.cwd() && !refresh_kind.root() { return; } match params.get_cwd(handle) { Ok(buffer) => unsafe { - *cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())); + 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); + sysinfo_debug!("get_cwd_and_root failed to get data: {:?}", _e); *cwd = PathBuf::new(); } } diff --git a/tests/process.rs b/tests/process.rs index f54472f0e..32c12f810 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -725,8 +725,6 @@ fn test_process_specific_refresh() { update_specific_and_check!(memory); update_specific_and_check!(environ, with_environ, .len(), 0); update_specific_and_check!(cmd, with_cmd, .len(), 0); - update_specific_and_check!(exe, with_exe, , Path::new("")); - update_specific_and_check!(cwd, with_cwd, , Path::new("")); if !cfg!(any( target_os = "macos", target_os = "ios", @@ -734,4 +732,6 @@ fn test_process_specific_refresh() { )) { 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("")); }