diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2022-10-11 19:49:39 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-11 19:49:39 -0400 |
commit | 1e5f0ea2d9dafa49279151b565154d9acf3b59d7 (patch) | |
tree | 7bb6a5326c808704eb1d098e4a76f2471360803c /src | |
parent | e7b31dfb96c95664049b45d297f604bfb6865d72 (diff) |
bug: add bindings to grab ppid in some cases on macos (#825)
Diffstat (limited to 'src')
-rw-r--r-- | src/app/data_farmer.rs | 18 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos.rs | 13 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos/sysctl_bindings.rs | 322 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos_freebsd.rs | 30 |
4 files changed, 362 insertions, 21 deletions
diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 3b6f6dc1..a6670712 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -110,21 +110,17 @@ impl ProcessData { .collect(); self.process_harvest = process_pid_map; - // This also needs a quick sort + reverse to be in the correct order. + // We collect all processes that either: + // - Do not have a parent PID (that is, they are orphan processes) + // - Have a parent PID but we don't have the parent (we promote them as orphans) + // Note this also needs a quick sort + reverse to be in the correct order. self.orphan_pids = { let mut res: Vec<Pid> = self .process_harvest .iter() - .filter_map(|(pid, process_harvest)| { - if let Some(parent_pid) = process_harvest.parent_pid { - if self.process_harvest.contains_key(&parent_pid) { - None - } else { - Some(*pid) - } - } else { - Some(*pid) - } + .filter_map(|(pid, process_harvest)| match process_harvest.parent_pid { + Some(parent_pid) if self.process_harvest.contains_key(&parent_pid) => None, + _ => Some(*pid), }) .sorted() .collect(); diff --git a/src/app/data_harvester/processes/macos.rs b/src/app/data_harvester/processes/macos.rs index c9a3f601..2431bf9b 100644 --- a/src/app/data_harvester/processes/macos.rs +++ b/src/app/data_harvester/processes/macos.rs @@ -1,9 +1,10 @@ -//! Process data collection for macOS. Uses sysinfo. +//! Process data collection for macOS. Uses sysinfo and custom bindings. use super::ProcessHarvest; use sysinfo::System; -use crate::data_harvester::processes::UserTable; +use crate::{data_harvester::processes::UserTable, Pid}; +mod sysctl_bindings; pub fn get_process_data( sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable, @@ -17,8 +18,14 @@ pub fn get_process_data( ) } +pub(crate) fn fallback_macos_ppid(pid: Pid) -> Option<Pid> { + sysctl_bindings::kinfo_process(pid) + .map(|kinfo| kinfo.kp_eproc.e_ppid) + .ok() +} + fn get_macos_process_cpu_usage( - pids: &[i32], + pids: &[Pid], ) -> std::io::Result<std::collections::HashMap<i32, f64>> { use itertools::Itertools; let output = std::process::Command::new("ps") diff --git a/src/app/data_harvester/processes/macos/sysctl_bindings.rs b/src/app/data_harvester/processes/macos/sysctl_bindings.rs new file mode 100644 index 00000000..77b8901b --- /dev/null +++ b/src/app/data_harvester/processes/macos/sysctl_bindings.rs @@ -0,0 +1,322 @@ +//! Partial bindings from Apple's open source code for getting process information. +//! Some of this is based on [heim's binding implementation](https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs). + +use std::mem; + +use anyhow::{bail, Result}; +use libc::{ + boolean_t, c_char, c_long, c_short, c_uchar, c_ushort, c_void, dev_t, gid_t, itimerval, pid_t, + rusage, sigset_t, timeval, uid_t, xucred, CTL_KERN, KERN_PROC, KERN_PROC_PID, MAXCOMLEN, +}; +use mach2::vm_types::user_addr_t; + +use crate::Pid; + +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct kinfo_proc { + pub kp_proc: extern_proc, + pub kp_eproc: eproc, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct p_st1 { + /// Doubly-linked run/sleep queue. + p_forw: user_addr_t, + p_back: user_addr_t, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub union p_un { + pub p_st1: p_st1, + + /// process start time + pub p_starttime: timeval, +} + +/// Exported fields for kern sysctl. See +/// [`proc.h`](https://opensource.apple.com/source/xnu/xnu-201/bsd/sys/proc.h) +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct extern_proc { + pub p_un: p_un, + + /// Address space. + pub p_vmspace: *mut vmspace, + + /// Signal actions, state (PROC ONLY). Should point to + /// a `sigacts` but we don't really seem to need this. + pub p_sigacts: user_addr_t, + + /// P_* flags. + pub p_flag: i32, + + /// S* process status. + pub p_stat: c_char, + + /// Process identifier. + pub p_pid: pid_t, + + /// Save parent pid during ptrace. XXX + pub p_oppid: pid_t, + + /// Sideways return value from fdopen. XXX + pub p_dupfd: i32, + + /// where user stack was allocated + pub user_stack: caddr_t, + + /// XXX Which thread is exiting? + pub exit_thread: *mut c_void, + + /// allow to debug + pub p_debugger: i32, + + /// indication to suspend + pub sigwait: boolean_t, + + /// Time averaged value of p_cpticks. + pub p_estcpu: u32, + + /// Ticks of cpu time. + pub p_cpticks: i32, + + /// %cpu for this process during p_swtime + pub p_pctcpu: fixpt_t, + + /// Sleep address. + pub p_wchan: *mut c_void, + + /// Reason for sleep. + pub p_wmesg: *mut c_char, + + /// Time swapped in or out. + pub p_swtime: u32, + + /// Time since last blocked. + pub p_slptime: u32, + + /// Alarm timer. + pub p_realtimer: itimerval, + + /// Real time. + pub p_rtime: timeval, + + /// Statclock hit in user mode. + pub p_uticks: u64, + + /// Statclock hits in system mode. + pub p_sticks: u64, + + /// Statclock hits processing intr. + pub p_iticks: u64, + + /// Kernel trace points. + pub p_traceflag: i32, + + /// Trace to vnode. Originally a pointer to a struct of vnode. + pub p_tracep: *mut c_void, + + /// DEPRECATED. + pub p_siglist: i32, + + /// Vnode of executable. Originally a pointer to a struct of vnode. + pub p_textvp: *mut c_void, + + /// If non-zero, don't swap. + pub p_holdcnt: i32, + + /// DEPRECATED. + pub p_sigmask: sigset_t, + + /// Signals being ignored. + pub p_sigignore: sigset_t, + + /// Signals being caught by user. + pub p_sigcatch: sigset_t, + + /// Process priority. + pub p_priority: c_uchar, + + /// User-priority based on p_cpu and p_nice. + pub p_usrpri: c_uchar, + + /// Process "nice" value. + pub p_nice: c_char, + + pub p_comm: [c_char; MAXCOMLEN + 1], + + /// Pointer to process group. Originally a pointer to a `pgrp`. + pub p_pgrp: *mut c_void, + + /// Kernel virtual addr of u-area (PROC ONLY). Originally a pointer to a `user`. + pub p_addr: *mut c_void, + + /// Exit status for wait; also stop signal. + pub p_xstat: c_ushort, + + /// Accounting flags. + pub p_acflag: c_ushort, + + /// Exit information. XXX + pub p_ru: *mut rusage, +} + +const WMESGLEN: usize = 7; +const COMAPT_MAXLOGNAME: usize = 12; + +/// See `_caddr_t.h`. +#[allow(non_camel_case_types)] +type caddr_t = *const libc::c_char; + +/// See `types.h`. +#[allow(non_camel_case_types)] +type segsz_t = i32; + +/// See `types.h`. +#[allow(non_camel_case_types)] +type fixpt_t = u32; + +/// See [`proc.h`](https://opensource.apple.com/source/xnu/xnu-201/bsd/sys/proc.h) +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct pcred { + pub pc_lock: [c_char; 72], + pub pc_ucred: *mut xucred, + pub p_ruid: uid_t, + pub p_svuid: uid_t, + pub p_rgid: gid_t, + pub p_svgid: gid_t, + pub p_refcnt: i32, +} + +/// See `vm.h`. +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct vmspace { + pub dummy: i32, + pub dummy2: caddr_t, + pub dummy3: [i32; 5], + pub dummy4: [caddr_t; 3], +} + +/// See [`sysctl.h`](https://opensource.apple.com/source/xnu/xnu-344/bsd/sys/sysctl.h). +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct eproc { + /// Address of proc. We just cheat and use a c_void pointer since we aren't using this. + pub e_paddr: *mut c_void, + + /// Session pointer. We just cheat and use a c_void pointer since we aren't using this. + pub e_sess: *mut c_void, + + /// Process credentials + pub e_pcred: pcred, + + /// Current credentials + pub e_ucred: xucred, + + /// Address space + pub e_vm: vmspace, + + /// Parent process ID + pub e_ppid: pid_t, + + /// Process group ID + pub e_pgid: pid_t, + + /// Job control counter + pub e_jobc: c_short, + + /// Controlling tty dev + pub e_tdev: dev_t, + + /// tty process group id + pub e_tpgid: pid_t, + + /// tty session pointer. We just cheat and use a c_void pointer since we aren't using this. + pub e_tsess: *mut c_void, + + /// wchan message + pub e_wmesg: [c_char; WMESGLEN + 1], + + /// text size + pub e_xsize: segsz_t, + + /// text rss + pub e_xrssize: c_short, + + /// text references + pub e_xccount: c_short, + + pub e_xswrss: c_short, + + pub e_flag: c_long, + + /// short setlogin() name + pub e_login: [c_char; COMAPT_MAXLOGNAME], + + pub e_spare: [c_long; 4], +} + +/// Obtains the [`kinfo_proc`] given a process PID. +/// +/// From [heim](https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs#L235). +pub(crate) fn kinfo_process(pid: Pid) -> Result<kinfo_proc> { + let mut name: [i32; 4] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]; + let mut size = mem::size_of::<kinfo_proc>(); + let mut info = mem::MaybeUninit::<kinfo_proc>::uninit(); + + let result = unsafe { + libc::sysctl( + name.as_mut_ptr(), + 4, + info.as_mut_ptr() as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) + }; + + if result < 0 { + bail!("failed to get process for pid {pid}"); + } + + // sysctl succeeds but size is zero, happens when process has gone away + if size == 0 { + bail!("failed to get process for pid {pid}"); + } + + unsafe { Ok(info.assume_init()) } +} + +#[cfg(test)] +mod test { + use super::*; + use std::mem; + + /// A quick test to ensure that things are sized correctly. + #[test] + fn test_struct_sizes() { + assert_eq!(mem::size_of::<p_st1>(), 16); + assert_eq!(mem::align_of::<p_st1>(), 8); + + assert_eq!(mem::size_of::<pcred>(), 104); + assert_eq!(mem::align_of::<pcred>(), 8); + + assert_eq!(mem::size_of::<vmspace>(), 64); + assert_eq!(mem::align_of::<vmspace>(), 8); + + assert_eq!(mem::size_of::<extern_proc>(), 296); + assert_eq!(mem::align_of::<extern_proc>(), 8); + + assert_eq!(mem::size_of::<eproc>(), 376); + assert_eq!(mem::align_of::<eproc>(), 8); + + assert_eq!(mem::size_of::<kinfo_proc>(), 672); + assert_eq!(mem::align_of::<kinfo_proc>(), 8); + } +} diff --git a/src/app/data_harvester/processes/macos_freebsd.rs b/src/app/data_harvester/processes/macos_freebsd.rs index 39b57e05..65725732 100644 --- a/src/app/data_harvester/processes/macos_freebsd.rs +++ b/src/app/data_harvester/processes/macos_freebsd.rs @@ -6,12 +6,15 @@ use std::io; use super::ProcessHarvest; use sysinfo::{CpuExt, PidExt, ProcessExt, ProcessStatus, System, SystemExt}; -use crate::data_harvester::processes::UserTable; +use crate::{data_harvester::processes::UserTable, utils::error::Result, Pid}; -pub fn get_process_data( +pub fn get_process_data<F>( sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable, - get_process_cpu_usage: impl Fn(&[i32]) -> io::Result<HashMap<i32, f64>>, -) -> crate::utils::error::Result<Vec<ProcessHarvest>> { + get_process_cpu_usage: F, +) -> Result<Vec<ProcessHarvest>> +where + F: Fn(&[Pid]) -> io::Result<HashMap<Pid, f64>>, +{ let mut process_vector: Vec<ProcessHarvest> = Vec::new(); let process_hashmap = sys.processes(); let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0; @@ -66,9 +69,22 @@ pub fn get_process_data( (ps.to_string(), convert_process_status_to_char(ps)) }; let uid = process_val.user_id().map(|u| **u); + let pid = process_val.pid().as_u32() as Pid; process_vector.push(ProcessHarvest { - pid: process_val.pid().as_u32() as _, - parent_pid: process_val.parent().map(|p| p.as_u32() as _), + pid, + parent_pid: { + #[cfg(target_os = "macos")] + { + process_val + .parent() + .map(|p| p.as_u32() as _) + .or_else(|| super::fallback_macos_ppid(pid)) + } + #[cfg(not(target_os = "macos"))] + { + process_val.parent().map(|p| p.as_u32() as _) + } + }, name, command, mem_usage_percent: if mem_total_kb > 0 { @@ -96,7 +112,7 @@ pub fn get_process_data( } let unknown_state = ProcessStatus::Unknown(0).to_string(); - let cpu_usage_unknown_pids: Vec<i32> = process_vector + let cpu_usage_unknown_pids: Vec<Pid> = process_vector .iter() .filter(|process| process.process_state.0 == unknown_state) .map(|process| process.pid) |