// 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, pub(crate) exe: PathBuf, pid: Pid, parent: Option, pub(crate) environ: Vec, 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, updated: bool, cpu_usage: f32, user_id: Option, group_id: Option, 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, 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, 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 { 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 { 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 { self.group_id } } #[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, ) { 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); 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 }; } } p.updated = true; } /*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; }*/ #[inline] pub(crate) fn has_been_updated(p: &mut Process) -> bool { let old = p.updated; p.updated = false; old } #[inline] pub(crate) fn force_update(p: &mut Process) { p.updated = true; } unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { let mut task_info = mem::zeroed::(); // 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::() as _, ); task_info } #[inline] fn check_if_pid_is_alive(pid: Pid) -> bool { unsafe { kill(pid.0, 0) == 0 } // For the full complete check, it'd need to be (but that seems unneeded): // unsafe { // *libc::__errno_location() == 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(); } } pub(crate) fn update_process( wrap: &Wrap, pid: Pid, mut size: size_t, time_interval: Option, now: u64, refresh_kind: ProcessRefreshKind, ) -> Result, ()> { let mut proc_args = Vec::with_capacity(size as usize); 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. force_update(p); return if check_if_pid_is_alive(pid) { Ok(None) } else { Err(()) }; } 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, libc::PROC_PIDTHREADINFO, 0, &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, mem::size_of::() 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... return if check_if_pid_is_alive(pid) { Ok(None) } else { 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 / 1_000; p.virtual_memory = task_info.pti_virtual_size / 1_000; if refresh_kind.disk_usage() { update_proc_disk_activity(p); } return Ok(None); } 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 = 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 mut info = mem::zeroed::(); if libc::proc_pidinfo( pid.0, libc::PROC_PIDTBSDINFO, 0, &mut info as *mut _ as *mut _, mem::size_of::() as _, ) != mem::size_of::() as _ { 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::new_empty(pid, exe, name, cwd))); } _ => {} } return Err(()); } let parent = match info.pbi_ppid as i32 { 0 => None, p => Some(Pid(p)), }; 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::(), ); let mut cp = ptr.add(mem::size_of::()); 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( ptr: *mut u8, mut cp: *mut u8, size: size_t, mut root: PathBuf, callback: F, ) -> (Vec, 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 / 1_000; p.virtual_memory = task_info.pti_virtual_size / 1_000; 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)) } } 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::::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(clippy::uninit_vec)] pub(crate) fn get_proc_list() -> Option> { unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return None; } let mut pids: Vec = Vec::with_capacity(count as usize); pids.set_len(count as usize); let count = count * mem::size_of::() 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 + 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 } #[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")); } }