| // Take a look at the license at the top of the repository in the LICENSE file. |
| |
| use std::ffi::CStr; |
| 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, size_t}; |
| |
| use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; |
| |
| use crate::sys::process::ThreadStatus; |
| use crate::sys::system::Wrap; |
| |
| #[doc = include_str!("../../../md_doc/process.md")] |
| pub struct Process { |
| pub(crate) name: String, |
| pub(crate) cmd: Vec<String>, |
| pub(crate) exe: PathBuf, |
| pid: Pid, |
| parent: Option<Pid>, |
| pub(crate) environ: Vec<String>, |
| cwd: PathBuf, |
| pub(crate) root: PathBuf, |
| pub(crate) memory: u64, |
| pub(crate) virtual_memory: u64, |
| old_utime: u64, |
| old_stime: u64, |
| start_time: u64, |
| run_time: u64, |
| pub(crate) updated: bool, |
| cpu_usage: f32, |
| user_id: Option<Uid>, |
| group_id: Option<Gid>, |
| pub(crate) process_status: ProcessStatus, |
| /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have |
| /// enough rights to get this information. |
| /// |
| /// This is very likely this one that you want instead of `process_status`. |
| pub status: Option<ThreadStatus>, |
| pub(crate) old_read_bytes: u64, |
| pub(crate) old_written_bytes: u64, |
| pub(crate) read_bytes: u64, |
| pub(crate) written_bytes: u64, |
| } |
| |
| impl Process { |
| pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process { |
| Process { |
| name, |
| pid, |
| parent: None, |
| cmd: Vec::new(), |
| environ: Vec::new(), |
| exe, |
| cwd, |
| root: PathBuf::new(), |
| memory: 0, |
| virtual_memory: 0, |
| cpu_usage: 0., |
| old_utime: 0, |
| old_stime: 0, |
| updated: true, |
| start_time: 0, |
| run_time: 0, |
| user_id: None, |
| group_id: None, |
| process_status: ProcessStatus::Unknown(0), |
| status: None, |
| old_read_bytes: 0, |
| old_written_bytes: 0, |
| read_bytes: 0, |
| written_bytes: 0, |
| } |
| } |
| |
| pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Process { |
| Process { |
| name: String::new(), |
| pid, |
| parent, |
| cmd: Vec::new(), |
| environ: Vec::new(), |
| exe: PathBuf::new(), |
| cwd: PathBuf::new(), |
| root: PathBuf::new(), |
| memory: 0, |
| virtual_memory: 0, |
| cpu_usage: 0., |
| old_utime: 0, |
| old_stime: 0, |
| updated: true, |
| start_time, |
| run_time, |
| user_id: None, |
| group_id: None, |
| process_status: ProcessStatus::Unknown(0), |
| status: None, |
| old_read_bytes: 0, |
| old_written_bytes: 0, |
| read_bytes: 0, |
| written_bytes: 0, |
| } |
| } |
| } |
| |
| impl ProcessExt for Process { |
| fn kill_with(&self, signal: Signal) -> Option<bool> { |
| let c_signal = crate::sys::system::convert_signal(signal)?; |
| unsafe { Some(kill(self.pid.0, c_signal) == 0) } |
| } |
| |
| fn name(&self) -> &str { |
| &self.name |
| } |
| |
| fn cmd(&self) -> &[String] { |
| &self.cmd |
| } |
| |
| fn exe(&self) -> &Path { |
| self.exe.as_path() |
| } |
| |
| fn pid(&self) -> Pid { |
| self.pid |
| } |
| |
| fn environ(&self) -> &[String] { |
| &self.environ |
| } |
| |
| fn cwd(&self) -> &Path { |
| self.cwd.as_path() |
| } |
| |
| fn root(&self) -> &Path { |
| self.root.as_path() |
| } |
| |
| fn memory(&self) -> u64 { |
| self.memory |
| } |
| |
| fn virtual_memory(&self) -> u64 { |
| self.virtual_memory |
| } |
| |
| fn parent(&self) -> Option<Pid> { |
| self.parent |
| } |
| |
| fn status(&self) -> ProcessStatus { |
| self.process_status |
| } |
| |
| fn start_time(&self) -> u64 { |
| self.start_time |
| } |
| |
| fn run_time(&self) -> u64 { |
| self.run_time |
| } |
| |
| fn cpu_usage(&self) -> f32 { |
| self.cpu_usage |
| } |
| |
| fn disk_usage(&self) -> DiskUsage { |
| DiskUsage { |
| read_bytes: self.read_bytes - self.old_read_bytes, |
| total_read_bytes: self.read_bytes, |
| written_bytes: self.written_bytes - self.old_written_bytes, |
| total_written_bytes: self.written_bytes, |
| } |
| } |
| |
| fn user_id(&self) -> Option<&Uid> { |
| self.user_id.as_ref() |
| } |
| |
| fn group_id(&self) -> Option<Gid> { |
| self.group_id |
| } |
| |
| fn wait(&self) { |
| let mut status = 0; |
| // attempt waiting |
| unsafe { |
| if libc::waitpid(self.pid.0, &mut status, 0) < 0 { |
| // attempt failed (non-child process) so loop until process ends |
| let duration = std::time::Duration::from_millis(10); |
| while kill(self.pid.0, 0) == 0 { |
| std::thread::sleep(duration); |
| } |
| } |
| } |
| } |
| } |
| |
| #[allow(deprecated)] // Because of libc::mach_absolute_time. |
| pub(crate) fn compute_cpu_usage( |
| p: &mut Process, |
| task_info: libc::proc_taskinfo, |
| system_time: u64, |
| user_time: u64, |
| time_interval: Option<f64>, |
| ) { |
| if let Some(time_interval) = time_interval { |
| let total_existing_time = p.old_stime.saturating_add(p.old_utime); |
| if time_interval > 0.000001 && total_existing_time > 0 { |
| let total_current_time = task_info |
| .pti_total_system |
| .saturating_add(task_info.pti_total_user); |
| |
| let total_time_diff = total_current_time.saturating_sub(total_existing_time); |
| if total_time_diff > 0 { |
| p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; |
| } |
| } else { |
| p.cpu_usage = 0.; |
| } |
| p.old_stime = task_info.pti_total_system; |
| p.old_utime = task_info.pti_total_user; |
| } else { |
| unsafe { |
| // This is the "backup way" of CPU computation. |
| let time = libc::mach_absolute_time(); |
| let task_time = user_time |
| .saturating_add(system_time) |
| .saturating_add(task_info.pti_total_user) |
| .saturating_add(task_info.pti_total_system); |
| |
| let system_time_delta = if task_time < p.old_utime { |
| task_time |
| } else { |
| task_time.saturating_sub(p.old_utime) |
| }; |
| let time_delta = if time < p.old_stime { |
| time |
| } else { |
| time.saturating_sub(p.old_stime) |
| }; |
| p.old_utime = task_time; |
| p.old_stime = time; |
| p.cpu_usage = if time_delta == 0 { |
| 0f32 |
| } else { |
| (system_time_delta as f64 * 100f64 / time_delta as f64) as f32 |
| }; |
| } |
| } |
| } |
| |
| /*pub fn set_time(p: &mut Process, utime: u64, stime: u64) { |
| p.old_utime = p.utime; |
| p.old_stime = p.stime; |
| p.utime = utime; |
| p.stime = stime; |
| p.updated = true; |
| }*/ |
| |
| unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { |
| let mut task_info = mem::zeroed::<libc::proc_taskinfo>(); |
| // If it doesn't work, we just don't have memory information for this process |
| // so it's "fine". |
| libc::proc_pidinfo( |
| pid.0, |
| libc::PROC_PIDTASKINFO, |
| 0, |
| &mut task_info as *mut libc::proc_taskinfo as *mut c_void, |
| mem::size_of::<libc::proc_taskinfo>() as _, |
| ); |
| task_info |
| } |
| |
| #[inline] |
| fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool { |
| // In case we are iterating all pids we got from `proc_listallpids`, then |
| // there is no point checking if the process is alive since it was returned |
| // from this function. |
| if !check_if_alive { |
| return true; |
| } |
| unsafe { |
| if kill(pid.0, 0) == 0 { |
| return true; |
| } |
| // `kill` failed but it might not be because the process is dead. |
| let errno = libc::__error(); |
| // If errno is equal to ESCHR, it means the process is dead. |
| !errno.is_null() && *errno != libc::ESRCH |
| } |
| } |
| |
| #[inline] |
| fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {} |
| |
| #[inline] |
| fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { |
| if *check && env.starts_with("PATH=") { |
| *check = false; |
| *root = Path::new(&env[5..]).to_path_buf(); |
| } |
| } |
| |
| unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> { |
| let mut info = mem::zeroed::<libc::proc_bsdinfo>(); |
| |
| if libc::proc_pidinfo( |
| pid.0, |
| libc::PROC_PIDTBSDINFO, |
| 0, |
| &mut info as *mut _ as *mut _, |
| mem::size_of::<libc::proc_bsdinfo>() as _, |
| ) != mem::size_of::<libc::proc_bsdinfo>() as _ |
| { |
| None |
| } else { |
| Some(info) |
| } |
| } |
| |
| unsafe fn create_new_process( |
| pid: Pid, |
| mut size: size_t, |
| now: u64, |
| refresh_kind: ProcessRefreshKind, |
| info: Option<libc::proc_bsdinfo>, |
| ) -> Result<Option<Process>, ()> { |
| let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>(); |
| let result = libc::proc_pidinfo( |
| pid.0, |
| libc::PROC_PIDVNODEPATHINFO, |
| 0, |
| &mut vnodepathinfo as *mut _ as *mut _, |
| mem::size_of::<libc::proc_vnodepathinfo>() as _, |
| ); |
| let cwd = if result > 0 { |
| let buffer = vnodepathinfo.pvi_cdir.vip_path; |
| let buffer = CStr::from_ptr(buffer.as_ptr() as _); |
| buffer |
| .to_str() |
| .map(PathBuf::from) |
| .unwrap_or_else(|_| PathBuf::new()) |
| } else { |
| PathBuf::new() |
| }; |
| |
| let info = match info { |
| Some(info) => info, |
| None => { |
| let mut buffer: Vec<u8> = 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::new_empty(pid, exe, name, cwd))); |
| } |
| _ => {} |
| } |
| return Err(()); |
| } |
| }; |
| let parent = match info.pbi_ppid as i32 { |
| 0 => None, |
| p => Some(Pid(p)), |
| }; |
| |
| let mut proc_args = Vec::with_capacity(size as _); |
| let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); |
| let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _]; |
| /* |
| * /---------------\ 0x00000000 |
| * | ::::::::::::: | |
| * |---------------| <-- Beginning of data returned by sysctl() is here. |
| * | argc | |
| * |---------------| |
| * | exec_path | |
| * |---------------| |
| * | 0 | |
| * |---------------| |
| * | arg[0] | |
| * |---------------| |
| * | 0 | |
| * |---------------| |
| * | arg[n] | |
| * |---------------| |
| * | 0 | |
| * |---------------| |
| * | env[0] | |
| * |---------------| |
| * | 0 | |
| * |---------------| |
| * | env[n] | |
| * |---------------| |
| * | ::::::::::::: | |
| * |---------------| <-- Top of stack. |
| * : : |
| * : : |
| * \---------------/ 0xffffffff |
| */ |
| if libc::sysctl( |
| mib.as_mut_ptr(), |
| mib.len() as _, |
| ptr as *mut c_void, |
| &mut size, |
| std::ptr::null_mut(), |
| 0, |
| ) == -1 |
| { |
| return Err(()); // not enough rights I assume? |
| } |
| let mut n_args: c_int = 0; |
| libc::memcpy( |
| (&mut n_args) as *mut c_int as *mut c_void, |
| ptr as *const c_void, |
| mem::size_of::<c_int>(), |
| ); |
| |
| let mut cp = ptr.add(mem::size_of::<c_int>()); |
| let mut start = cp; |
| |
| let start_time = info.pbi_start_tvsec; |
| let run_time = now.saturating_sub(start_time); |
| |
| let mut p = if cp < ptr.add(size) { |
| while cp < ptr.add(size) && *cp != 0 { |
| cp = cp.offset(1); |
| } |
| let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); |
| let name = exe |
| .file_name() |
| .and_then(|x| x.to_str()) |
| .unwrap_or("") |
| .to_owned(); |
| while cp < ptr.add(size) && *cp == 0 { |
| cp = cp.offset(1); |
| } |
| start = cp; |
| let mut c = 0; |
| let mut cmd = Vec::with_capacity(n_args as usize); |
| while c < n_args && cp < ptr.add(size) { |
| if *cp == 0 { |
| c += 1; |
| cmd.push(get_unchecked_str(cp, start)); |
| start = cp.offset(1); |
| } |
| cp = cp.offset(1); |
| } |
| |
| #[inline] |
| unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>( |
| ptr: *mut u8, |
| mut cp: *mut u8, |
| size: size_t, |
| mut root: PathBuf, |
| callback: F, |
| ) -> (Vec<String>, PathBuf) { |
| let mut environ = Vec::with_capacity(10); |
| let mut start = cp; |
| let mut check = true; |
| while cp < ptr.add(size) { |
| if *cp == 0 { |
| if cp == start { |
| break; |
| } |
| let e = get_unchecked_str(cp, start); |
| callback(&e, &mut root, &mut check); |
| environ.push(e); |
| start = cp.offset(1); |
| } |
| cp = cp.offset(1); |
| } |
| (environ, root) |
| } |
| |
| let (environ, root) = if exe.is_absolute() { |
| if let Some(parent_path) = exe.parent() { |
| get_environ( |
| ptr, |
| cp, |
| size, |
| parent_path.to_path_buf(), |
| do_not_get_env_path, |
| ) |
| } else { |
| get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) |
| } |
| } else { |
| get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) |
| }; |
| let mut p = Process::new(pid, parent, start_time, run_time); |
| |
| p.exe = exe; |
| p.name = name; |
| p.cwd = cwd; |
| p.cmd = parse_command_line(&cmd); |
| p.environ = environ; |
| p.root = root; |
| p |
| } else { |
| Process::new(pid, parent, start_time, run_time) |
| }; |
| |
| 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_uid)); |
| p.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(p)) |
| } |
| |
| pub(crate) fn update_process( |
| wrap: &Wrap, |
| pid: Pid, |
| size: size_t, |
| time_interval: Option<f64>, |
| now: u64, |
| refresh_kind: ProcessRefreshKind, |
| check_if_alive: bool, |
| ) -> Result<Option<Process>, ()> { |
| unsafe { |
| if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { |
| 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. |
| p.updated = true; |
| // The owner of this PID changed. |
| return create_new_process(pid, size, now, refresh_kind, Some(info)); |
| } |
| } |
| let task_info = get_task_info(pid); |
| let mut thread_info = mem::zeroed::<libc::proc_threadinfo>(); |
| let (user_time, system_time, thread_status) = if libc::proc_pidinfo( |
| pid.0, |
| libc::PROC_PIDTHREADINFO, |
| 0, |
| &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, |
| mem::size_of::<libc::proc_threadinfo>() as _, |
| ) != 0 |
| { |
| ( |
| thread_info.pth_user_time, |
| thread_info.pth_system_time, |
| Some(ThreadStatus::from(thread_info.pth_run_state)), |
| ) |
| } else { |
| // It very likely means that the process is dead... |
| if check_if_pid_is_alive(pid, check_if_alive) { |
| (0, 0, Some(ThreadStatus::Running)) |
| } else { |
| return Err(()); |
| } |
| }; |
| p.status = thread_status; |
| |
| if refresh_kind.cpu() { |
| compute_cpu_usage(p, task_info, system_time, user_time, time_interval); |
| } |
| |
| 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); |
| } |
| p.updated = true; |
| return Ok(None); |
| } |
| create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid)) |
| } |
| } |
| |
| fn update_proc_disk_activity(p: &mut Process) { |
| p.old_read_bytes = p.read_bytes; |
| p.old_written_bytes = p.written_bytes; |
| |
| let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::uninit(); |
| |
| unsafe { |
| let retval = libc::proc_pid_rusage( |
| p.pid().0 as _, |
| libc::RUSAGE_INFO_V2, |
| pidrusage.as_mut_ptr() as _, |
| ); |
| |
| if retval < 0 { |
| sysinfo_debug!("proc_pid_rusage failed: {:?}", retval); |
| } else { |
| let pidrusage = pidrusage.assume_init(); |
| p.read_bytes = pidrusage.ri_diskio_bytesread; |
| p.written_bytes = pidrusage.ri_diskio_byteswritten; |
| } |
| } |
| } |
| |
| #[allow(unknown_lints)] |
| #[allow(clippy::uninit_vec)] |
| pub(crate) fn get_proc_list() -> Option<Vec<Pid>> { |
| unsafe { |
| let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); |
| if count < 1 { |
| return None; |
| } |
| let mut pids: Vec<Pid> = Vec::with_capacity(count as usize); |
| pids.set_len(count as usize); |
| let count = count * mem::size_of::<Pid>() as i32; |
| let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count); |
| |
| if x < 1 || x as usize >= pids.len() { |
| None |
| } else { |
| pids.set_len(x as usize); |
| Some(pids) |
| } |
| } |
| } |
| |
| unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { |
| let len = cp as usize - start as usize; |
| let part = Vec::from_raw_parts(start, len, len); |
| let tmp = String::from_utf8_unchecked(part.clone()); |
| mem::forget(part); |
| tmp |
| } |
| |
| fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<String> { |
| 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 |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn test_get_path() { |
| let mut path = PathBuf::new(); |
| let mut check = true; |
| |
| do_get_env_path("PATH=tadam", &mut path, &mut check); |
| |
| assert!(!check); |
| assert_eq!(path, PathBuf::from("tadam")); |
| } |
| } |