diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/sysinfo/src/apple/macos/process.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo/src/apple/macos/process.rs')
-rw-r--r-- | vendor/sysinfo/src/apple/macos/process.rs | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/vendor/sysinfo/src/apple/macos/process.rs b/vendor/sysinfo/src/apple/macos/process.rs new file mode 100644 index 000000000..f146126cf --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/process.rs @@ -0,0 +1,643 @@ +// 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, + 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 + } +} + +#[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); + 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::<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) -> 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<f64>, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Result<Option<Process>, ()> { + 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::<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... + 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::<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 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 _ + { + 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 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 / 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::<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(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")); + } +} |