#![allow(non_snake_case)]
#![allow(dead_code)]
use core::any::Any;
use std::mem::{size_of, size_of_val, zeroed};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use crate::ported::crt::{ColorElements as CE, ColorScheme};
use crate::ported::darwin::darwinmachine::DarwinMachine;
use crate::ported::darwin::darwinprocesstable::{kinfo_proc, DarwinProcessTable};
use crate::ported::darwin::platform::Platform_machTicksToNanoseconds;
use crate::ported::machine::Machine;
use crate::ported::object::{Object, ObjectClass};
use crate::ported::process::{
Process, ProcessClass, ProcessField, ProcessFieldData, ProcessState, Process_compareByKey_Base,
Process_done, Process_fillStarttimeBuffer, Process_getPid, Process_init, Process_setParent,
Process_setPid, Process_setThreadGroup, Process_updateCPUFieldWidths, Process_updateCmdline,
Process_updateComm, Process_updateExe, Process_writeField, PROCESS_FLAG_CWD,
};
use crate::ported::processtable::ProcessTable_getProcess;
use crate::ported::richstring::{RichString, RichString_appendWide};
use crate::ported::row::{spaceship_number, Row, RowClass};
use crate::ported::settings::RowField;
extern "C" {
fn mach_port_deallocate(
task: libc::mach_port_t,
name: libc::mach_port_t,
) -> libc::kern_return_t;
}
#[repr(C)]
pub struct DarwinProcess {
pub super_: Process,
pub utime: u64,
pub stime: u64,
pub taskAccess: bool,
pub translated: bool,
}
impl Object for DarwinProcess {
fn klass(&self) -> &'static ObjectClass {
self.super_.klass()
}
fn display(&self, out: &mut RichString) {
self.super_.display(out)
}
fn compare(&self, other: &dyn Object) -> i32 {
self.super_.compare(other)
}
fn row_class(&self) -> Option<&'static RowClass> {
self.super_.row_class()
}
fn process_class(&self) -> Option<&'static ProcessClass> {
self.super_.process_class()
}
fn as_row(&self) -> Option<&Row> {
Some(&self.super_.super_)
}
fn as_row_mut(&mut self) -> Option<&mut Row> {
Some(&mut self.super_.super_)
}
fn as_process(&self) -> Option<&Process> {
Some(&self.super_)
}
fn as_process_mut(&mut self) -> Option<&mut Process> {
Some(&mut self.super_)
}
}
pub fn DarwinProcess_new(host: *const Machine) -> Box<DarwinProcess> {
let mut this = Box::new(DarwinProcess {
super_: Process::default(),
utime: 0,
stime: 0,
taskAccess: true,
translated: false,
});
Process_init(&mut this.super_, host as *const c_void);
this.super_.state = ProcessState::UNKNOWN;
this
}
pub fn Process_delete(this: DarwinProcess) {
let DarwinProcess { super_, .. } = this;
Process_done(super_);
}
const TRANSLATED: RowField = 100;
pub const LAST_PROCESSFIELD: usize = 127;
#[allow(clippy::too_many_arguments)]
const fn pfd(
name: &'static str,
title: &'static str,
description: &'static str,
flags: u32,
pidColumn: bool,
defaultSortDesc: bool,
autoWidth: bool,
autoTitleRightAlign: bool,
) -> ProcessFieldData {
ProcessFieldData {
name,
title: Some(title),
description: Some(description),
flags,
pidColumn,
defaultSortDesc,
autoWidth,
autoTitleRightAlign,
}
}
const EMPTY_FIELD: ProcessFieldData = ProcessFieldData {
name: "",
title: None,
description: None,
flags: 0,
pidColumn: false,
defaultSortDesc: false,
autoWidth: false,
autoTitleRightAlign: false,
};
#[allow(non_upper_case_globals)] pub static Process_fields: [ProcessFieldData; LAST_PROCESSFIELD] = build_process_fields();
const fn build_process_fields() -> [ProcessFieldData; LAST_PROCESSFIELD] {
use ProcessField as PF;
let mut t = [EMPTY_FIELD; LAST_PROCESSFIELD];
t[PF::PID as usize] = pfd(
"PID",
"PID",
"Process/thread ID",
0,
true,
false,
false,
false,
);
t[PF::COMM as usize] = pfd(
"Command",
"Command ",
"Command line (insert as last column only)",
0,
false,
false,
false,
false,
);
t[PF::STATE as usize] = pfd(
"STATE",
"S ",
"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)",
0,
false,
false,
false,
false,
);
t[PF::PPID as usize] = pfd(
"PPID",
"PPID",
"Parent process ID",
0,
true,
false,
false,
false,
);
t[PF::PGRP as usize] = pfd(
"PGRP",
"PGRP",
"Process group ID",
0,
true,
false,
false,
false,
);
t[PF::SESSION as usize] = pfd(
"SESSION",
"SID",
"Process's session ID",
0,
true,
false,
false,
false,
);
t[PF::TTY as usize] = pfd(
"TTY",
"TTY ",
"Controlling terminal",
PROCESS_FLAG_TTY,
false,
false,
false,
false,
);
t[PF::TPGID as usize] = pfd(
"TPGID",
"TPGID",
"Process ID of the fg process group of the controlling terminal",
0,
true,
false,
false,
false,
);
t[PF::MINFLT as usize] = pfd(
"MINFLT",
" MINFLT ",
"Number of minor faults which have not required loading a memory page from disk",
0,
false,
true,
false,
false,
);
t[PF::MAJFLT as usize] = pfd(
"MAJFLT",
" MAJFLT ",
"Number of major faults which have required loading a memory page from disk",
0,
false,
true,
false,
false,
);
t[PF::PRIORITY as usize] = pfd(
"PRIORITY",
"PRI ",
"Kernel's internal priority for the process",
0,
false,
false,
false,
false,
);
t[PF::NICE as usize] = pfd(
"NICE",
" NI ",
"Nice value (the higher the value, the more it lets other processes take priority)",
0,
false,
false,
false,
false,
);
t[PF::STARTTIME as usize] = pfd(
"STARTTIME",
"START ",
"Time the process was started",
0,
false,
false,
false,
false,
);
t[PF::ELAPSED as usize] = pfd(
"ELAPSED",
"ELAPSED ",
"Time since the process was started",
0,
false,
false,
false,
false,
);
t[PF::PROCESSOR as usize] = pfd(
"PROCESSOR",
"CPU ",
"Id of the CPU the process last executed on",
0,
false,
false,
false,
false,
);
t[PF::M_VIRT as usize] = pfd(
"M_VIRT",
" VIRT ",
"Total program size in virtual memory",
0,
false,
true,
false,
false,
);
t[PF::M_RESIDENT as usize] = pfd(
"M_RESIDENT",
" RES ",
"Resident set size, size of the text and data sections, plus stack usage",
0,
false,
true,
false,
false,
);
t[PF::ST_UID as usize] = pfd(
"ST_UID",
"UID",
"User ID of the process owner",
0,
false,
false,
false,
false,
);
t[PF::PERCENT_CPU as usize] = pfd(
"PERCENT_CPU",
" CPU%",
"Percentage of the CPU time the process used in the last sampling",
0,
false,
true,
true,
true,
);
t[PF::PERCENT_NORM_CPU as usize] = pfd(
"PERCENT_NORM_CPU",
"NCPU%",
"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
0, false, true, true, false,
);
t[PF::PERCENT_MEM as usize] = pfd(
"PERCENT_MEM",
"MEM% ",
"Percentage of the memory the process is using, based on resident memory size",
0,
false,
true,
false,
false,
);
t[PF::USER as usize] = pfd(
"USER",
"USER ",
"Username of the process owner (or user ID if name cannot be determined)",
0,
false,
false,
false,
false,
);
t[PF::TIME as usize] = pfd(
"TIME",
" TIME+ ",
"Total time the process has spent in user and system time",
0,
false,
true,
false,
false,
);
t[PF::NLWP as usize] = pfd(
"NLWP",
"NLWP ",
"Number of threads in the process",
0,
false,
false,
false,
false,
);
t[PF::TGID as usize] = pfd(
"TGID",
"TGID",
"Thread group ID (i.e. process ID)",
0,
true,
false,
false,
false,
);
t[PF::PROC_EXE as usize] = pfd(
"EXE",
"EXE ",
"Basename of exe of the process from /proc/[pid]/exe",
0,
false,
false,
false,
false,
);
t[PF::CWD as usize] = pfd(
"CWD",
"CWD ",
"The current working directory of the process",
PROCESS_FLAG_CWD,
false,
false,
false,
false,
);
t[TRANSLATED as usize] = pfd(
"TRANSLATED",
"T ",
"Translation info (T translated, N native)",
0,
false,
false,
false,
false,
);
t
}
pub fn DarwinProcess_rowWriteField(super_: &dyn Object, str: &mut RichString, field: RowField) {
let dp = (super_ as &dyn Any)
.downcast_ref::<DarwinProcess>()
.expect("DarwinProcess_rowWriteField: row is not a DarwinProcess");
let scheme = ColorScheme::active();
let attr = CE::DEFAULT_COLOR.packed(scheme);
let buffer: String;
match field {
f if f == TRANSLATED => {
buffer = format!("{} ", if dp.translated { 'T' } else { 'N' });
}
_ => {
Process_writeField(&dp.super_, str, field);
return;
}
}
RichString_appendWide(str, attr, buffer.as_bytes());
}
pub fn DarwinProcess_compareByKey(v1: &dyn Object, v2: &dyn Object, key: RowField) -> i32 {
let p1 = (v1 as &dyn Any)
.downcast_ref::<DarwinProcess>()
.expect("DarwinProcess_compareByKey: v1 is not a DarwinProcess");
let p2 = (v2 as &dyn Any)
.downcast_ref::<DarwinProcess>()
.expect("DarwinProcess_compareByKey: v2 is not a DarwinProcess");
match key {
k if k == TRANSLATED => spaceship_number!(p1.translated as i32, p2.translated as i32),
_ => Process_compareByKey_Base(&p1.super_, &p2.super_, key),
}
}
pub fn DarwinProcess_updateExe(pid: libc::pid_t, proc: &mut Process) {
let mut path = [0u8; libc::PROC_PIDPATHINFO_MAXSIZE as usize];
let r = unsafe { libc::proc_pidpath(pid, path.as_mut_ptr() as *mut c_void, path.len() as u32) };
if r <= 0 {
return;
}
let exe = String::from_utf8_lossy(&path[..r as usize]);
Process_updateExe(proc, Some(&exe));
}
pub fn DarwinProcess_updateCwd(pid: libc::pid_t, proc: &mut Process) {
let mut vpi: libc::proc_vnodepathinfo = unsafe { zeroed() };
let r = unsafe {
libc::proc_pidinfo(
pid,
libc::PROC_PIDVNODEPATHINFO,
0,
&mut vpi as *mut libc::proc_vnodepathinfo as *mut c_void,
size_of::<libc::proc_vnodepathinfo>() as c_int,
)
};
if r <= 0 {
proc.procCwd = None;
return;
}
let path: &[u8] = unsafe {
core::slice::from_raw_parts(
vpi.pvi_cdir.vip_path.as_ptr() as *const u8,
size_of_val(&vpi.pvi_cdir.vip_path),
)
};
if path[0] == 0 {
proc.procCwd = None;
return;
}
let end = path.iter().position(|&b| b == 0).unwrap_or(path.len());
proc.procCwd = Some(String::from_utf8_lossy(&path[..end]).into_owned());
}
pub fn DarwinProcess_updateCmdLine(k: &kinfo_proc, proc: &mut Process) {
let pid = k.kp_proc.p_pid;
let comm_field: &[u8] = unsafe {
core::slice::from_raw_parts(
k.kp_proc.p_comm.as_ptr() as *const u8,
k.kp_proc.p_comm.len(),
)
};
let comm_len = comm_field
.iter()
.position(|&b| b == 0)
.unwrap_or(comm_field.len());
let comm = String::from_utf8_lossy(&comm_field[..comm_len]).into_owned();
Process_updateComm(proc, Some(&comm));
let parsed = (|| -> Option<(String, usize)> {
let mut argmax: c_int = 0;
let mut sz = size_of::<c_int>();
let mut mib_max = [libc::CTL_KERN, libc::KERN_ARGMAX];
if unsafe {
libc::sysctl(
mib_max.as_mut_ptr(),
2,
&mut argmax as *mut c_int as *mut c_void,
&mut sz,
ptr::null_mut(),
0,
)
} == -1
|| argmax <= 0
{
return None;
}
let mut procargs = vec![0u8; argmax as usize];
let mut size = argmax as usize;
let mut mib_args = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid];
if unsafe {
libc::sysctl(
mib_args.as_mut_ptr(),
3,
procargs.as_mut_ptr() as *mut c_void,
&mut size,
ptr::null_mut(),
0,
)
} == -1
{
return None;
}
procargs.truncate(size);
if procargs.len() < size_of::<c_int>() {
return None;
}
let nargs = i32::from_ne_bytes([procargs[0], procargs[1], procargs[2], procargs[3]]);
let n = procargs.len();
let mut cp = size_of::<c_int>();
while cp < n && procargs[cp] != 0 {
cp += 1;
}
if cp == n {
return None;
}
while cp < n && procargs[cp] == 0 {
cp += 1;
}
if cp == n {
return None;
}
let sp = cp;
let mut c = 0i32;
let mut np: Option<usize> = None;
let mut end = 0usize;
while c < nargs && cp < n {
if procargs[cp] == 0 {
c += 1;
if let Some(prev) = np {
procargs[prev] = b' ';
}
np = Some(cp);
if end == 0 {
end = cp - sp;
}
}
cp += 1;
}
let np = np?;
if np == sp {
return None;
}
if end == 0 {
end = np - sp;
}
let cmdline = String::from_utf8_lossy(&procargs[sp..np]).into_owned();
let end = end.min(cmdline.len());
Some((cmdline, end))
})();
match parsed {
Some((cmdline, end)) => Process_updateCmdline(proc, Some(&cmdline), 0, end),
None => {
let end = comm.len();
let arg = if comm.is_empty() {
None
} else {
Some(comm.as_str())
};
Process_updateCmdline(proc, arg, 0, end);
}
}
}
const NODEV: libc::dev_t = -1;
const MAXNAMLEN: c_int = 255;
extern "C" {
fn devname_r(dev: libc::dev_t, mode: libc::mode_t, buf: *mut c_char, len: c_int)
-> *mut c_char;
}
pub fn DarwinProcess_getDevname(dev: libc::dev_t) -> Option<String> {
if dev == NODEV {
return None;
}
let mut buf = [0 as c_char; 6 + MAXNAMLEN as usize];
let name = unsafe { devname_r(dev, libc::S_IFCHR, buf.as_mut_ptr(), MAXNAMLEN) };
if name.is_null() {
return None;
}
let s = unsafe { std::ffi::CStr::from_ptr(name) }
.to_string_lossy()
.into_owned();
Some(s)
}
const P_TRANSLATED: c_int = 0x00020000;
const PROCESS_FLAG_TTY: u32 = 0x00000100;
pub fn DarwinProcess_setFromKInfoProc(proc: &mut Process, ps: &kinfo_proc, exists: bool) {
let ep = &ps.kp_proc;
let flags: u32 = unsafe {
let host = proc.super_.host as *const Machine;
host.as_ref()
.and_then(|m| m.settings.as_ref())
.and_then(|s| s.screens.get(s.ssIndex as usize))
.map_or(0, |ss| ss.flags)
};
if !exists {
Process_setPid(proc, ep.p_pid);
Process_setThreadGroup(proc, ep.p_pid);
Process_setParent(proc, ps.kp_eproc.e_ppid);
proc.pgrp = ps.kp_eproc.e_pgid;
proc.session = 0;
proc.tpgid = ps.kp_eproc.e_tpgid;
proc.isKernelThread = false;
proc.isUserlandThread = false;
let translated = (ep.p_flag & P_TRANSLATED) != 0;
unsafe {
(*(proc as *mut Process as *mut DarwinProcess)).translated = translated;
}
proc.tty_nr = ps.kp_eproc.e_tdev as u64;
proc.tty_name = None;
proc.starttime_ctime = ep.p_starttime.tv_sec;
Process_fillStarttimeBuffer(proc);
DarwinProcess_updateExe(ep.p_pid, proc);
DarwinProcess_updateCmdLine(ps, proc);
if flags & PROCESS_FLAG_CWD != 0 {
DarwinProcess_updateCwd(ep.p_pid, proc);
}
}
if proc.tty_name.is_none() && proc.tty_nr as libc::dev_t != NODEV {
if flags & PROCESS_FLAG_TTY != 0 {
proc.tty_name = DarwinProcess_getDevname(proc.tty_nr as libc::dev_t);
if proc.tty_name.is_none() {
proc.tty_nr = NODEV as u64;
}
}
}
proc.nice = ep.p_nice as i32;
proc.priority = ep.p_priority as i64;
proc.state = if ep.p_stat as u32 == libc::SZOMB {
ProcessState::ZOMBIE
} else {
ProcessState::UNKNOWN
};
proc.super_.updated = true;
}
const ONE_K: u64 = 1024;
pub fn DarwinProcess_setFromLibprocPidinfo(
proc: &mut DarwinProcess,
dpt: &mut DarwinProcessTable,
timeIntervalNS: f64,
) {
let mut pti: libc::proc_taskinfo = unsafe { zeroed() };
let size = size_of::<libc::proc_taskinfo>() as c_int;
let got = unsafe {
libc::proc_pidinfo(
Process_getPid(&proc.super_),
libc::PROC_PIDTASKINFO,
0,
&mut pti as *mut libc::proc_taskinfo as *mut c_void,
size,
)
};
if got != size {
proc.taskAccess = false;
return;
}
let dhost = proc.super_.super_.host as *const DarwinMachine;
let max_mem = unsafe { (*dhost).host_info.max_mem };
let total_existing_time_ns = proc.stime + proc.utime;
let user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);
let system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system);
let total_current_time_ns = user_time_ns + system_time_ns;
if total_existing_time_ns < total_current_time_ns {
let total_time_diff_ns = total_current_time_ns - total_existing_time_ns;
proc.super_.percent_cpu = ((total_time_diff_ns as f64 / timeIntervalNS) * 100.0) as f32;
} else {
proc.super_.percent_cpu = 0.0;
}
Process_updateCPUFieldWidths(proc.super_.percent_cpu);
proc.super_.state = if pti.pti_numrunning > 0 {
ProcessState::RUNNING
} else {
ProcessState::SLEEPING
};
proc.super_.time = total_current_time_ns / 10_000_000;
proc.super_.nlwp = pti.pti_threadnum as i64;
proc.super_.m_virt = (pti.pti_virtual_size / ONE_K) as i64;
proc.super_.m_resident = (pti.pti_resident_size / ONE_K) as i64;
proc.super_.majflt = pti.pti_faults as u64;
proc.super_.percent_mem = (pti.pti_resident_size as f64 * 100.0 / max_mem as f64) as f32;
proc.stime = system_time_ns;
proc.utime = user_time_ns;
dpt.super_.userlandThreads += pti.pti_threadnum as u32;
dpt.super_.totalTasks += pti.pti_threadnum as u32;
dpt.super_.runningTasks += pti.pti_numrunning as u32;
}
#[allow(deprecated)]
pub unsafe fn DarwinProcess_scanThreads(dp: *mut DarwinProcess, dpt: *mut DarwinProcessTable) {
let proc_: *mut Process = &mut (*dp).super_;
if !(*dp).taskAccess {
return;
}
if (*proc_).state == ProcessState::ZOMBIE {
return;
}
let pid = Process_getPid(&*proc_);
let mut task: libc::task_t = 0;
let ret = libc::task_for_pid(libc::mach_task_self(), pid, &mut task);
if ret != libc::KERN_SUCCESS {
(*dp).taskAccess = false;
return;
}
let mut thread_list: libc::thread_act_array_t = ptr::null_mut();
let mut thread_count: libc::mach_msg_type_number_t = 0;
let ret = libc::task_threads(task, &mut thread_list, &mut thread_count);
if ret != libc::KERN_SUCCESS {
(*dp).taskAccess = false;
mach_port_deallocate(libc::mach_task_self(), task);
return;
}
let host = (*dpt).super_.super_.host;
let hideUserlandThreads = (*host)
.settings
.as_ref()
.map(|s| s.hideUserlandThreads)
.unwrap_or(false);
let mut isProcessStuck = false;
for i in 0..thread_count as usize {
let thread = *thread_list.add(i);
let mut identifer_info: libc::thread_identifier_info_data_t = zeroed();
let mut identifer_info_count = libc::THREAD_IDENTIFIER_INFO_COUNT;
let ret = libc::thread_info(
thread,
libc::THREAD_IDENTIFIER_INFO as libc::thread_flavor_t,
&mut identifer_info as *mut libc::thread_identifier_info_data_t as libc::thread_info_t,
&mut identifer_info_count,
);
if ret != libc::KERN_SUCCESS {
continue;
}
let tid = identifer_info.thread_id;
let (pre_existing, idx) =
ProcessTable_getProcess(&mut (*dpt).super_, tid as libc::pid_t, |h| {
DarwinProcess_new(h) as Box<dyn Object>
});
let tdproc: *mut DarwinProcess = {
let rows = &mut (*dpt).super_.super_.rows;
let obj: &mut dyn Object = rows[idx].as_mut().unwrap().as_mut();
let any: &mut dyn Any = obj;
any.downcast_mut::<DarwinProcess>().unwrap()
};
(*tdproc).super_.super_.updated = true;
(*dpt).super_.totalTasks += 1;
if hideUserlandThreads {
(*tdproc).super_.super_.show = false;
continue;
}
let tprocessPid = Process_getPid(&(*tdproc).super_);
debug_assert!(tprocessPid >= 0);
debug_assert_eq!(tprocessPid as u64, tid);
Process_setParent(&mut (*tdproc).super_, pid);
Process_setThreadGroup(&mut (*tdproc).super_, pid);
(*tdproc).super_.super_.show = true;
(*tdproc).super_.isUserlandThread = true;
(*tdproc).super_.st_uid = (*proc_).st_uid;
(*tdproc).super_.user = (*proc_).user.clone();
let mut extended_info: libc::thread_extended_info_data_t = zeroed();
let mut extended_info_count = libc::THREAD_EXTENDED_INFO_COUNT;
let ret = libc::thread_info(
thread,
libc::THREAD_EXTENDED_INFO as libc::thread_flavor_t,
&mut extended_info as *mut libc::thread_extended_info_data_t as libc::thread_info_t,
&mut extended_info_count,
);
if ret != libc::KERN_SUCCESS {
continue;
}
(*tdproc).super_.percent_cpu = extended_info.pth_cpu_usage as f32 / 10.0;
(*tdproc).stime = extended_info.pth_system_time;
(*tdproc).utime = extended_info.pth_user_time;
(*tdproc).super_.time =
(extended_info.pth_system_time + extended_info.pth_user_time) / 10_000_000;
(*tdproc).super_.priority = extended_info.pth_curpri as i64;
if extended_info.pth_run_state == libc::TH_STATE_UNINTERRUPTIBLE {
isProcessStuck = true;
(*tdproc).super_.state = ProcessState::UNINTERRUPTIBLE_WAIT;
}
let ext_name: Option<String> = if extended_info.pth_name[0] != 0 {
Some(
std::ffi::CStr::from_ptr(extended_info.pth_name.as_ptr())
.to_string_lossy()
.into_owned(),
)
} else {
None
};
let name: Option<&str> = match &ext_name {
Some(s) => Some(s.as_str()),
None => (*proc_).procComm.as_deref(),
};
let namelen = name.map(str::len).unwrap_or(0);
Process_updateCmdline(&mut (*tdproc).super_, name, 0, namelen);
let _ = pre_existing;
}
if isProcessStuck {
(*dp).super_.state = ProcessState::UNINTERRUPTIBLE_WAIT;
}
libc::vm_deallocate(
libc::mach_task_self(),
thread_list as libc::vm_address_t,
size_of::<libc::thread_act_array_t>() * thread_count as usize,
);
mach_port_deallocate(libc::mach_task_self(), task);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_sets_darwin_defaults_and_unknown_state() {
let host = 0xF00D as *const Machine;
let p = DarwinProcess_new(host);
assert_eq!(p.utime, 0);
assert_eq!(p.stime, 0);
assert!(p.taskAccess);
assert!(!p.translated);
assert_eq!(p.super_.state, ProcessState::UNKNOWN);
assert_eq!(p.super_.super_.host, host as *const c_void);
}
#[test]
fn super_is_at_offset_zero_for_sound_downcast() {
assert_eq!(core::mem::offset_of!(DarwinProcess, super_), 0);
let host = 0xF00D as *const Machine;
let dp = DarwinProcess_new(host);
let base: *const Process = &dp.super_;
let back = base as *const DarwinProcess;
assert_eq!(back, &*dp as *const DarwinProcess);
}
#[test]
fn updateExe_sets_exe_from_own_pid() {
let host = 0xF00D as *const Machine;
let mut dp = DarwinProcess_new(host);
let pid = unsafe { libc::getpid() };
DarwinProcess_updateExe(pid, &mut dp.super_);
let exe = dp.super_.procExe.as_deref().unwrap_or("");
assert!(!exe.is_empty());
assert!(exe.starts_with('/'));
}
#[test]
fn updateCwd_sets_absolute_cwd_from_own_pid() {
let host = 0xF00D as *const Machine;
let mut dp = DarwinProcess_new(host);
let pid = unsafe { libc::getpid() };
DarwinProcess_updateCwd(pid, &mut dp.super_);
let cwd = dp.super_.procCwd.as_deref().expect("own cwd resolves");
assert!(cwd.starts_with('/'));
}
#[test]
fn updateCmdLine_sets_comm_and_cmdline_from_own_procargs() {
let pid = unsafe { libc::getpid() };
let mut mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
let mut kp: kinfo_proc = unsafe { zeroed() };
let mut size = size_of::<kinfo_proc>();
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
4,
&mut kp as *mut kinfo_proc as *mut c_void,
&mut size,
ptr::null_mut(),
0,
)
};
assert_eq!(rc, 0);
let host = 0xF00D as *const Machine;
let mut dp = DarwinProcess_new(host);
DarwinProcess_updateCmdLine(&kp, &mut dp.super_);
assert!(dp.super_.procComm.is_some());
let cmdline = dp.super_.cmdline.as_deref().expect("own cmdline resolves");
assert!(!cmdline.is_empty());
assert!(dp.super_.cmdlineBasenameEnd <= cmdline.len());
}
#[test]
fn getDevname_resolves_dev_null_and_rejects_nodev() {
assert_eq!(DarwinProcess_getDevname(NODEV), None);
let mut st: libc::stat = unsafe { zeroed() };
let rc = unsafe { libc::stat(b"/dev/null\0".as_ptr() as *const c_char, &mut st) };
assert_eq!(rc, 0);
assert_eq!(
DarwinProcess_getDevname(st.st_rdev).as_deref(),
Some("null")
);
}
#[test]
fn setFromKInfoProc_fills_identity_from_own_kinfo() {
use crate::ported::process::{Process_getParent, Process_getPid};
use crate::ported::settings::{ScreenSettings, Settings};
let pid = unsafe { libc::getpid() };
let ppid = unsafe { libc::getppid() };
let mut mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
let mut kp: kinfo_proc = unsafe { zeroed() };
let mut size = size_of::<kinfo_proc>();
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
4,
&mut kp as *mut kinfo_proc as *mut c_void,
&mut size,
ptr::null_mut(),
0,
)
};
assert_eq!(rc, 0);
let mut machine = Machine::default();
machine.realtimeMs = 1_700_000_000_000;
machine.settings = Some(Settings {
screens: vec![ScreenSettings::default()],
ssIndex: 0,
..Default::default()
});
let mut dp = DarwinProcess_new(&machine as *const Machine);
DarwinProcess_setFromKInfoProc(&mut dp.super_, &kp, false);
assert_eq!(Process_getPid(&dp.super_), pid);
assert_eq!(Process_getParent(&dp.super_), ppid);
assert_eq!(dp.super_.pgrp, kp.kp_eproc.e_pgid);
assert_eq!(dp.super_.tpgid, kp.kp_eproc.e_tpgid);
assert!(dp.super_.cmdline.is_some());
assert!(dp.super_.super_.updated);
assert_eq!(dp.super_.state, ProcessState::UNKNOWN);
}
#[test]
fn setFromLibprocPidinfo_fills_stats_for_own_pid() {
use crate::ported::darwin::darwinmachine::{host_basic_info_data_t, DarwinMachine};
use crate::ported::darwin::darwinprocesstable::DarwinProcessTable;
use crate::ported::linux::linuxmachine::ZfsArcStats;
use crate::ported::processtable::ProcessTable;
let dm = Box::new(DarwinMachine {
super_: Machine::default(),
host_info: host_basic_info_data_t {
max_mem: 16u64 << 30,
..Default::default()
},
vm_stats: unsafe { zeroed() },
prev_load: ptr::null_mut(),
curr_load: ptr::null_mut(),
GPUService: 0,
zfs: ZfsArcStats::default(),
});
let mut dp = DarwinProcess_new(&dm.super_ as *const Machine);
Process_setPid(&mut dp.super_, unsafe { libc::getpid() });
let mut dpt = Box::new(DarwinProcessTable {
super_: ProcessTable::empty(),
global_diff: 0,
});
DarwinProcess_setFromLibprocPidinfo(&mut dp, &mut dpt, 1e9);
assert!(dp.taskAccess);
assert!(dp.super_.nlwp >= 1); assert!(dp.super_.m_resident > 0); assert!(dp.super_.percent_mem >= 0.0);
assert!(dpt.super_.totalTasks >= 1);
assert!(matches!(
dp.super_.state,
ProcessState::RUNNING | ProcessState::SLEEPING
));
}
#[test]
fn scanThreads_enumerates_own_threads() {
use crate::ported::darwin::darwinmachine::host_basic_info_data_t;
use crate::ported::darwin::darwinprocesstable::ProcessTable_new;
use crate::ported::linux::linuxmachine::ZfsArcStats;
use crate::ported::settings::Settings;
let mut dm = Box::new(DarwinMachine {
super_: Machine::default(),
host_info: host_basic_info_data_t::default(),
vm_stats: unsafe { zeroed() },
prev_load: ptr::null_mut(),
curr_load: ptr::null_mut(),
GPUService: 0,
zfs: ZfsArcStats::default(),
});
dm.super_.settings = Some(Settings {
hideUserlandThreads: false,
..Default::default()
});
let mut dpt = ProcessTable_new(&dm.super_ as *const Machine, None);
let mypid = unsafe { libc::getpid() };
let (_pre, idx) = ProcessTable_getProcess(&mut dpt.super_, mypid, |h| {
DarwinProcess_new(h) as Box<dyn Object>
});
let dp: *mut DarwinProcess = {
let obj: &mut dyn Object = dpt.super_.super_.rows[idx].as_mut().unwrap().as_mut();
(obj as &mut dyn Any)
.downcast_mut::<DarwinProcess>()
.unwrap()
};
unsafe {
(*dp).taskAccess = true;
(*dp).super_.state = ProcessState::RUNNING;
}
let rows_before = dpt.super_.super_.rows.len();
let dpt_ptr: *mut DarwinProcessTable = &mut *dpt;
unsafe { DarwinProcess_scanThreads(dp, dpt_ptr) };
assert!(unsafe { (*dp).taskAccess });
assert!(
dpt.super_.super_.rows.len() > rows_before,
"thread rows were added"
);
assert!(dpt.super_.totalTasks >= 1);
let thread_rows = dpt
.super_
.super_
.rows
.iter()
.flatten()
.filter(|o| o.as_process().map(|p| p.isUserlandThread).unwrap_or(false))
.count();
assert!(thread_rows >= 1, "at least one userland thread row");
}
}