// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use std::fmt; use std::path::{Path, PathBuf}; use libc::kill; use super::utils::{get_sys_value_str, WrapMap}; #[doc(hidden)] impl From for ProcessStatus { fn from(status: libc::c_char) -> ProcessStatus { match status { libc::SIDL => ProcessStatus::Idle, libc::SRUN => ProcessStatus::Run, libc::SSLEEP => ProcessStatus::Sleep, libc::SSTOP => ProcessStatus::Stop, libc::SZOMB => ProcessStatus::Zombie, libc::SWAIT => ProcessStatus::Dead, libc::SLOCK => ProcessStatus::LockBlocked, x => ProcessStatus::Unknown(x as _), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", ProcessStatus::Dead => "Dead", ProcessStatus::LockBlocked => "LockBlocked", _ => "Unknown", }) } } #[doc = include_str!("../../md_doc/process.md")] pub struct Process { pub(crate) name: String, pub(crate) cmd: Vec, pub(crate) exe: PathBuf, pub(crate) pid: Pid, parent: Option, pub(crate) environ: Vec, pub(crate) cwd: PathBuf, pub(crate) root: PathBuf, pub(crate) memory: u64, pub(crate) virtual_memory: u64, pub(crate) updated: bool, cpu_usage: f32, start_time: u64, run_time: u64, pub(crate) status: ProcessStatus, user_id: Uid, effective_user_id: Uid, group_id: Gid, effective_group_id: Gid, read_bytes: u64, old_read_bytes: u64, written_bytes: u64, old_written_bytes: u64, } impl ProcessExt for Process { fn kill_with(&self, signal: Signal) -> Option { let c_signal = super::system::convert_signal(signal)?; unsafe { Some(libc::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.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 { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } fn user_id(&self) -> Option<&Uid> { Some(&self.user_id) } fn effective_user_id(&self) -> Option<&Uid> { Some(&self.effective_user_id) } fn group_id(&self) -> Option { Some(self.group_id) } fn effective_group_id(&self) -> Option { Some(self.effective_group_id) } fn wait(&self) { let mut status = 0; // attempt waiting unsafe { if retry_eintr!(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); } } } } fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } } pub(crate) unsafe fn get_process_data( kproc: &libc::kinfo_proc, wrap: &WrapMap, page_size: isize, fscale: f32, now: u64, refresh_kind: ProcessRefreshKind, ) -> Result, ()> { if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 { // We filter out the kernel threads. return Err(()); } // We now get the values needed for both new and existing process. let cpu_usage = if refresh_kind.cpu() { (100 * kproc.ki_pctcpu) as f32 / fscale } else { 0. }; // Processes can be reparented apparently? let parent = if kproc.ki_ppid != 0 { Some(Pid(kproc.ki_ppid)) } else { None }; 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 _); // FIXME: This is to get the "real" run time (in micro-seconds). // let run_time = (kproc.ki_runtime + 5_000) / 10_000; let start_time = kproc.ki_start.tv_sec as u64; if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) { proc_.updated = true; // If the `start_time` we just got is different from the one stored, it means it's not the // same process. if proc_.start_time == start_time { proc_.cpu_usage = cpu_usage; proc_.parent = parent; proc_.status = status; proc_.virtual_memory = virtual_memory; proc_.memory = memory; proc_.run_time = now.saturating_sub(proc_.start_time); if refresh_kind.disk_usage() { proc_.old_read_bytes = proc_.read_bytes; proc_.read_bytes = kproc.ki_rusage.ru_inblock as _; proc_.old_written_bytes = proc_.written_bytes; proc_.written_bytes = kproc.ki_rusage.ru_oublock as _; } return Ok(None); } } // This is a new process, we need to get more information! let mut buffer = [0; libc::PATH_MAX as usize + 1]; let exe = get_sys_value_str( &[ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PATHNAME, kproc.ki_pid, ], &mut buffer, ) .unwrap_or_default(); // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use // procstat to get around this problem. // let cwd = get_sys_value_str( // &[ // libc::CTL_KERN, // libc::KERN_PROC, // libc::KERN_PROC_CWD, // kproc.ki_pid, // ], // &mut buffer, // ) // .map(|s| s.into()) // .unwrap_or_else(PathBuf::new); Ok(Some(Process { pid: Pid(kproc.ki_pid), parent, user_id: Uid(kproc.ki_ruid), effective_user_id: Uid(kproc.ki_uid), group_id: Gid(kproc.ki_rgid), effective_group_id: Gid(kproc.ki_svgid), start_time, run_time: now.saturating_sub(start_time), cpu_usage, virtual_memory, memory, // procstat_getfiles cwd: PathBuf::new(), exe: exe.into(), // 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. cmd: Vec::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. root: PathBuf::new(), // kvm_getenvv isn't thread-safe so we get it in the main thread. environ: Vec::new(), status, read_bytes: kproc.ki_rusage.ru_inblock as _, old_read_bytes: 0, written_bytes: kproc.ki_rusage.ru_oublock as _, old_written_bytes: 0, updated: false, })) }