mirror of
https://github.com/nushell/nushell.git
synced 2025-01-22 06:08:47 +01:00
Add support for the ps
command on FreeBSD, NetBSD, and OpenBSD (#12892)
# Description I feel like it's a little sad that BSDs get to enjoy almost everything other than the `ps` command, and there are some tests that rely on this command, so I figured it would be fun to patch that and make it work. The different BSDs have diverged from each other somewhat, but generally have a similar enough API for reading process information via `sysctl()`, with some slightly different args. This supports FreeBSD with the `freebsd` module, and NetBSD and OpenBSD with the `netbsd` module. OpenBSD is a fork of NetBSD and the interface has some minor differences but many things are the same. I had wanted to try to support DragonFlyBSD too, but their Rust version in the latest release is only 1.72.0, which is too old for me to want to try to compile rustc up to 1.77.2... but I will revisit this whenever they do update it. Dragonfly is a fork of FreeBSD, so it's likely to be more or less the same - I just don't want to enable it without testing it. Fixes #6862 (partially, we probably won't be adding `zfs list`) # User-Facing Changes `ps` added for FreeBSD, NetBSD, and OpenBSD. # Tests + Formatting The CI doesn't run tests for BSDs, so I'm not entirely sure if everything was already passing before. (Frankly, it's unlikely.) But nothing appears to be broken. # After Submitting - [ ] release notes? - [ ] DragonflyBSD, whenever they do update Rust to something close enough for me to try it
This commit is contained in:
parent
d7e75c0b70
commit
758c5d447a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3264,6 +3264,7 @@ name = "nu-system"
|
||||
version = "0.93.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.12.1",
|
||||
"libc",
|
||||
"libproc",
|
||||
"log",
|
||||
|
@ -164,6 +164,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
|
@ -5,6 +5,8 @@ mod nu_check;
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
@ -23,6 +25,8 @@ pub use nu_check::NuCheck;
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
|
@ -2,20 +2,13 @@
|
||||
use itertools::Itertools;
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "freebsd"),
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "android"),
|
||||
))]
|
||||
#[cfg(target_os = "linux")]
|
||||
use procfs::WithCurrentSystemInfo;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ps;
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
impl Command for Ps {
|
||||
fn name(&self) -> &str {
|
||||
"ps"
|
||||
@ -82,7 +75,6 @@ impl Command for Ps {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
fn run_ps(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -111,12 +103,7 @@ fn run_ps(
|
||||
|
||||
if long {
|
||||
record.push("command", Value::string(proc.command(), span));
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "android"),
|
||||
))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let proc_stat = proc
|
||||
.curr_proc
|
||||
|
@ -16,6 +16,7 @@ bench = false
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { workspace = true, default-features = false, features = ["fs", "term", "process", "signal"] }
|
||||
|
305
crates/nu-system/src/freebsd.rs
Normal file
305
crates/nu-system/src/freebsd.rs
Normal file
@ -0,0 +1,305 @@
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use libc::{
|
||||
kinfo_proc, sysctl, CTL_HW, CTL_KERN, KERN_PROC, KERN_PROC_ALL, KERN_PROC_ARGS, TDF_IDLETD,
|
||||
};
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
io,
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i32,
|
||||
pub ppid: i32,
|
||||
pub name: String,
|
||||
pub argv: Vec<u8>,
|
||||
pub stat: i8,
|
||||
pub percent_cpu: f64,
|
||||
pub mem_resident: u64, // in bytes
|
||||
pub mem_virtual: u64, // in bytes
|
||||
}
|
||||
|
||||
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
|
||||
compare_procs(interval).unwrap_or_else(|err| {
|
||||
log::warn!("Failed to get processes: {}", err);
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_procs(interval: Duration) -> io::Result<Vec<ProcessInfo>> {
|
||||
let pagesize = get_pagesize()? as u64;
|
||||
|
||||
// Compare two full snapshots of all of the processes over the interval
|
||||
let now = Instant::now();
|
||||
let procs_a = get_procs()?;
|
||||
std::thread::sleep(interval);
|
||||
let procs_b = get_procs()?;
|
||||
let true_interval = Instant::now().saturating_duration_since(now);
|
||||
let true_interval_sec = true_interval.as_secs_f64();
|
||||
|
||||
// Group all of the threads in each process together
|
||||
let a_grouped = procs_a.into_iter().group_by(|proc| proc.ki_pid);
|
||||
let b_grouped = procs_b.into_iter().group_by(|proc| proc.ki_pid);
|
||||
|
||||
// Join the processes between the two snapshots
|
||||
Ok(a_grouped
|
||||
.into_iter()
|
||||
.merge_join_by(b_grouped.into_iter(), |(pid_a, _), (pid_b, _)| {
|
||||
pid_a.cmp(pid_b)
|
||||
})
|
||||
.map(|threads| {
|
||||
// Join the threads between the two snapshots for the process
|
||||
let mut threads = {
|
||||
let (left, right) = threads.left_and_right();
|
||||
left.into_iter()
|
||||
.flat_map(|(_, threads)| threads)
|
||||
.merge_join_by(
|
||||
right.into_iter().flat_map(|(_, threads)| threads),
|
||||
|thread_a, thread_b| thread_a.ki_tid.cmp(&thread_b.ki_tid),
|
||||
)
|
||||
.peekable()
|
||||
};
|
||||
|
||||
// Pick the later process entry of the first thread to use for basic process information
|
||||
let proc = match threads.peek().ok_or(io::ErrorKind::NotFound)? {
|
||||
EitherOrBoth::Both(_, b) => b,
|
||||
EitherOrBoth::Left(a) => a,
|
||||
EitherOrBoth::Right(b) => b,
|
||||
}
|
||||
.clone();
|
||||
|
||||
// Skip over the idle process. It always appears with high CPU usage when the
|
||||
// system is idle
|
||||
if proc.ki_tdflags as u64 & TDF_IDLETD as u64 != 0 {
|
||||
return Err(io::ErrorKind::NotFound.into());
|
||||
}
|
||||
|
||||
// Aggregate all of the threads that exist in both snapshots and sum their runtime.
|
||||
let (runtime_a, runtime_b) =
|
||||
threads
|
||||
.flat_map(|t| t.both())
|
||||
.fold((0., 0.), |(runtime_a, runtime_b), (a, b)| {
|
||||
let runtime_in_seconds =
|
||||
|proc: &kinfo_proc| proc.ki_runtime as f64 /* µsec */ / 1_000_000.0;
|
||||
(
|
||||
runtime_a + runtime_in_seconds(&a),
|
||||
runtime_b + runtime_in_seconds(&b),
|
||||
)
|
||||
});
|
||||
|
||||
// The percentage CPU is the ratio of how much runtime occurred for the process out of
|
||||
// the true measured interval that occurred.
|
||||
let percent_cpu = 100. * (runtime_b - runtime_a).max(0.) / true_interval_sec;
|
||||
|
||||
let info = ProcessInfo {
|
||||
pid: proc.ki_pid,
|
||||
ppid: proc.ki_ppid,
|
||||
name: read_cstr(&proc.ki_comm).to_string_lossy().into_owned(),
|
||||
argv: get_proc_args(proc.ki_pid)?,
|
||||
stat: proc.ki_stat,
|
||||
percent_cpu,
|
||||
mem_resident: proc.ki_rssize.max(0) as u64 * pagesize,
|
||||
mem_virtual: proc.ki_size.max(0) as u64,
|
||||
};
|
||||
Ok(info)
|
||||
})
|
||||
// Remove errors from the list - probably just processes that are gone now
|
||||
.flat_map(|result: io::Result<_>| result.ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn check(err: libc::c_int) -> std::io::Result<()> {
|
||||
if err < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a bounds-checked way to read a `CStr` from a slice of `c_char`
|
||||
fn read_cstr(slice: &[libc::c_char]) -> &CStr {
|
||||
unsafe {
|
||||
// SAFETY: ensure that c_char and u8 are the same size
|
||||
mem::transmute::<libc::c_char, u8>(0);
|
||||
let slice: &[u8] = mem::transmute(slice);
|
||||
CStr::from_bytes_until_nul(slice).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_procs() -> io::Result<Vec<libc::kinfo_proc>> {
|
||||
// To understand what's going on here, see the sysctl(3) manpage for FreeBSD.
|
||||
unsafe {
|
||||
const STRUCT_SIZE: usize = mem::size_of::<libc::kinfo_proc>();
|
||||
let ctl_name = [CTL_KERN, KERN_PROC, KERN_PROC_ALL];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// data_len will be set in bytes, so divide by the size of the structure
|
||||
let expected_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<libc::kinfo_proc> = Vec::with_capacity(expected_len);
|
||||
data_len = vec.capacity() * STRUCT_SIZE;
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
let true_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
vec.set_len(true_len);
|
||||
|
||||
// Sort the procs by pid and then tid before using them
|
||||
vec.sort_by_key(|p| (p.ki_pid, p.ki_tid));
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_proc_args(pid: i32) -> io::Result<Vec<u8>> {
|
||||
unsafe {
|
||||
let ctl_name = [CTL_KERN, KERN_PROC, KERN_PROC_ARGS, pid];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(data_len);
|
||||
data_len = vec.capacity();
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
vec.set_len(data_len);
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
// For getting simple values from the sysctl interface
|
||||
unsafe fn get_ctl<T>(ctl_name: &[i32]) -> io::Result<T> {
|
||||
let mut value: MaybeUninit<T> = MaybeUninit::uninit();
|
||||
let mut value_len = mem::size_of_val(&value);
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
value.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut value_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
Ok(value.assume_init())
|
||||
}
|
||||
|
||||
fn get_pagesize() -> io::Result<libc::c_int> {
|
||||
// not in libc for some reason
|
||||
const HW_PAGESIZE: i32 = 7;
|
||||
unsafe { get_ctl(&[CTL_HW, HW_PAGESIZE]) }
|
||||
}
|
||||
|
||||
impl ProcessInfo {
|
||||
/// PID of process
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.pid
|
||||
}
|
||||
|
||||
/// Parent PID of process
|
||||
pub fn ppid(&self) -> i32 {
|
||||
self.ppid
|
||||
}
|
||||
|
||||
/// Name of command
|
||||
pub fn name(&self) -> String {
|
||||
let argv_name = self
|
||||
.argv
|
||||
.split(|b| *b == 0)
|
||||
.next()
|
||||
.map(String::from_utf8_lossy)
|
||||
.unwrap_or_default()
|
||||
.into_owned();
|
||||
|
||||
if !argv_name.is_empty() {
|
||||
argv_name
|
||||
} else {
|
||||
// Just use the command name alone.
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Full name of command, with arguments
|
||||
pub fn command(&self) -> String {
|
||||
if let Some(last_nul) = self.argv.iter().rposition(|b| *b == 0) {
|
||||
// The command string is NUL separated
|
||||
// Take the string up to the last NUL, then replace the NULs with spaces
|
||||
String::from_utf8_lossy(&self.argv[0..last_nul]).replace("\0", " ")
|
||||
} else {
|
||||
// The argv is empty, so use the name instead
|
||||
self.name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of the process
|
||||
pub fn status(&self) -> String {
|
||||
match self.stat {
|
||||
libc::SIDL | libc::SRUN => "Running",
|
||||
libc::SSLEEP => "Sleeping",
|
||||
libc::SSTOP => "Stopped",
|
||||
libc::SWAIT => "Waiting",
|
||||
libc::SLOCK => "Locked",
|
||||
libc::SZOMB => "Zombie",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// CPU usage as a percent of total
|
||||
pub fn cpu_usage(&self) -> f64 {
|
||||
self.percent_cpu
|
||||
}
|
||||
|
||||
/// Memory size in number of bytes
|
||||
pub fn mem_size(&self) -> u64 {
|
||||
self.mem_resident
|
||||
}
|
||||
|
||||
/// Virtual memory size in bytes
|
||||
pub fn virtual_size(&self) -> u64 {
|
||||
self.mem_virtual
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
mod foreground;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
mod freebsd;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
|
||||
mod netbsd;
|
||||
pub mod os_info;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
@ -10,9 +15,14 @@ mod windows;
|
||||
#[cfg(unix)]
|
||||
pub use self::foreground::stdin_fd;
|
||||
pub use self::foreground::{ForegroundChild, ForegroundGuard};
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub use self::freebsd::*;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub use self::linux::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::macos::*;
|
||||
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
|
||||
pub use self::netbsd::*;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use self::windows::*;
|
||||
|
336
crates/nu-system/src/netbsd.rs
Normal file
336
crates/nu-system/src/netbsd.rs
Normal file
@ -0,0 +1,336 @@
|
||||
//! This is used for both NetBSD and OpenBSD, because they are fairly similar.
|
||||
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use libc::{sysctl, CTL_HW, CTL_KERN, KERN_PROC_ALL, KERN_PROC_ARGS, KERN_PROC_ARGV};
|
||||
use std::{
|
||||
io,
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
type KInfoProc = libc::kinfo_proc2;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
type KInfoProc = libc::kinfo_proc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i32,
|
||||
pub ppid: i32,
|
||||
pub argv: Vec<u8>,
|
||||
pub stat: i8,
|
||||
pub percent_cpu: f64,
|
||||
pub mem_resident: u64, // in bytes
|
||||
pub mem_virtual: u64, // in bytes
|
||||
}
|
||||
|
||||
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
|
||||
compare_procs(interval).unwrap_or_else(|err| {
|
||||
log::warn!("Failed to get processes: {}", err);
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_procs(interval: Duration) -> io::Result<Vec<ProcessInfo>> {
|
||||
let pagesize = get_pagesize()? as u64;
|
||||
|
||||
// Compare two full snapshots of all of the processes over the interval
|
||||
let now = Instant::now();
|
||||
let procs_a = get_procs()?;
|
||||
std::thread::sleep(interval);
|
||||
let procs_b = get_procs()?;
|
||||
let true_interval = Instant::now().saturating_duration_since(now);
|
||||
let true_interval_sec = true_interval.as_secs_f64();
|
||||
|
||||
// Join the processes between the two snapshots
|
||||
Ok(procs_a
|
||||
.into_iter()
|
||||
.merge_join_by(procs_b.into_iter(), |a, b| a.p_pid.cmp(&b.p_pid))
|
||||
.map(|proc| {
|
||||
// Take both snapshotted processes if we can, but if not then just keep the one that
|
||||
// exists and set prev_proc to None
|
||||
let (prev_proc, proc) = match proc {
|
||||
EitherOrBoth::Both(a, b) => (Some(a), b),
|
||||
EitherOrBoth::Left(a) => (None, a),
|
||||
EitherOrBoth::Right(b) => (None, b),
|
||||
};
|
||||
|
||||
// The percentage CPU is the ratio of how much runtime occurred for the process out of
|
||||
// the true measured interval that occurred.
|
||||
let percent_cpu = if let Some(prev_proc) = prev_proc {
|
||||
let prev_rtime =
|
||||
prev_proc.p_rtime_sec as f64 + prev_proc.p_rtime_usec as f64 / 1_000_000.0;
|
||||
let rtime = proc.p_rtime_sec as f64 + proc.p_rtime_usec as f64 / 1_000_000.0;
|
||||
100. * (rtime - prev_rtime).max(0.) / true_interval_sec
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
Ok(ProcessInfo {
|
||||
pid: proc.p_pid,
|
||||
ppid: proc.p_ppid,
|
||||
argv: get_proc_args(proc.p_pid, KERN_PROC_ARGV)?,
|
||||
stat: proc.p_stat,
|
||||
percent_cpu,
|
||||
mem_resident: proc.p_vm_rssize.max(0) as u64 * pagesize,
|
||||
#[cfg(target_os = "netbsd")]
|
||||
mem_virtual: proc.p_vm_msize.max(0) as u64 * pagesize,
|
||||
#[cfg(target_os = "openbsd")]
|
||||
mem_virtual: proc.p_vm_map_size.max(0) as u64 * pagesize,
|
||||
})
|
||||
})
|
||||
// Remove errors from the list - probably just processes that are gone now
|
||||
.flat_map(|result: io::Result<_>| result.ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn check(err: libc::c_int) -> std::io::Result<()> {
|
||||
if err < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Call `sysctl()` in read mode (i.e. the last two arguments are NULL and zero)
|
||||
unsafe fn sysctl_get(
|
||||
name: *const i32,
|
||||
name_len: u32,
|
||||
data: *mut libc::c_void,
|
||||
data_len: *mut usize,
|
||||
) -> i32 {
|
||||
sysctl(
|
||||
name,
|
||||
name_len,
|
||||
data,
|
||||
data_len,
|
||||
// NetBSD and OpenBSD differ in mutability for this pointer, but it's null anyway
|
||||
#[cfg(target_os = "netbsd")]
|
||||
ptr::null(),
|
||||
#[cfg(target_os = "openbsd")]
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_procs() -> io::Result<Vec<KInfoProc>> {
|
||||
// To understand what's going on here, see the sysctl(3) and sysctl(7) manpages for NetBSD.
|
||||
unsafe {
|
||||
const STRUCT_SIZE: usize = mem::size_of::<KInfoProc>();
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
const TGT_KERN_PROC: i32 = libc::KERN_PROC2;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
const TGT_KERN_PROC: i32 = libc::KERN_PROC;
|
||||
|
||||
let mut ctl_name = [
|
||||
CTL_KERN,
|
||||
TGT_KERN_PROC,
|
||||
KERN_PROC_ALL,
|
||||
0,
|
||||
STRUCT_SIZE as i32,
|
||||
0,
|
||||
];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// data_len will be set in bytes, so divide by the size of the structure
|
||||
let expected_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<KInfoProc> = Vec::with_capacity(expected_len);
|
||||
data_len = vec.capacity() * STRUCT_SIZE;
|
||||
|
||||
// We are also supposed to set ctl_name[5] to the number of structures we want
|
||||
ctl_name[5] = expected_len.try_into().expect("expected_len too big");
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
let true_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
vec.set_len(true_len);
|
||||
|
||||
// Sort the procs by pid before using them
|
||||
vec.sort_by_key(|p| p.p_pid);
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_proc_args(pid: i32, what: i32) -> io::Result<Vec<u8>> {
|
||||
unsafe {
|
||||
let ctl_name = [CTL_KERN, KERN_PROC_ARGS, pid, what];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(data_len);
|
||||
data_len = vec.capacity();
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
vec.set_len(data_len);
|
||||
|
||||
// On OpenBSD we have to do an extra step, because it fills the buffer with pointers to the
|
||||
// strings first, even though the strings are within the buffer as well.
|
||||
#[cfg(target_os = "openbsd")]
|
||||
let vec = {
|
||||
use std::ffi::CStr;
|
||||
|
||||
// Set up some bounds checking. We assume there will be some pointers at the base until
|
||||
// we reach NULL, but we want to make sure we only ever read data within the range of
|
||||
// min_ptr..max_ptr.
|
||||
let ptrs = vec.as_ptr() as *const *const u8;
|
||||
let min_ptr = vec.as_ptr() as *const u8;
|
||||
let max_ptr = vec.as_ptr().add(vec.len()) as *const u8;
|
||||
let max_index: isize = (vec.len() / mem::size_of::<*const u8>())
|
||||
.try_into()
|
||||
.expect("too big for isize");
|
||||
|
||||
let mut new_vec = Vec::with_capacity(vec.len());
|
||||
for index in 0..max_index {
|
||||
let ptr = ptrs.offset(index);
|
||||
if *ptr == ptr::null() {
|
||||
break;
|
||||
} else {
|
||||
// Make sure it's within the bounds of the buffer
|
||||
assert!(
|
||||
*ptr >= min_ptr && *ptr < max_ptr,
|
||||
"pointer out of bounds of the buffer returned by sysctl()"
|
||||
);
|
||||
// Also bounds-check the C strings, to make sure we don't overrun the buffer
|
||||
new_vec.extend(
|
||||
CStr::from_bytes_until_nul(std::slice::from_raw_parts(
|
||||
*ptr,
|
||||
max_ptr.offset_from(*ptr) as usize,
|
||||
))
|
||||
.expect("invalid C string")
|
||||
.to_bytes_with_nul(),
|
||||
);
|
||||
}
|
||||
}
|
||||
new_vec
|
||||
};
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
// For getting simple values from the sysctl interface
|
||||
unsafe fn get_ctl<T>(ctl_name: &[i32]) -> io::Result<T> {
|
||||
let mut value: MaybeUninit<T> = MaybeUninit::uninit();
|
||||
let mut value_len = mem::size_of_val(&value);
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
value.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut value_len,
|
||||
))?;
|
||||
Ok(value.assume_init())
|
||||
}
|
||||
|
||||
fn get_pagesize() -> io::Result<libc::c_int> {
|
||||
// not in libc for some reason
|
||||
const HW_PAGESIZE: i32 = 7;
|
||||
unsafe { get_ctl(&[CTL_HW, HW_PAGESIZE]) }
|
||||
}
|
||||
|
||||
impl ProcessInfo {
|
||||
/// PID of process
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.pid
|
||||
}
|
||||
|
||||
/// Parent PID of process
|
||||
pub fn ppid(&self) -> i32 {
|
||||
self.ppid
|
||||
}
|
||||
|
||||
/// Name of command
|
||||
pub fn name(&self) -> String {
|
||||
self.argv
|
||||
.split(|b| *b == 0)
|
||||
.next()
|
||||
.map(String::from_utf8_lossy)
|
||||
.unwrap_or_default()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Full name of command, with arguments
|
||||
pub fn command(&self) -> String {
|
||||
if let Some(last_nul) = self.argv.iter().rposition(|b| *b == 0) {
|
||||
// The command string is NUL separated
|
||||
// Take the string up to the last NUL, then replace the NULs with spaces
|
||||
String::from_utf8_lossy(&self.argv[0..last_nul]).replace("\0", " ")
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of the process
|
||||
pub fn status(&self) -> String {
|
||||
// see sys/proc.h (OpenBSD), sys/lwp.h (NetBSD)
|
||||
// the names given here are the NetBSD ones, starting with LS*, but the OpenBSD ones are
|
||||
// the same, just starting with S* instead
|
||||
match self.stat {
|
||||
1 /* LSIDL */ => "",
|
||||
2 /* LSRUN */ => "Waiting",
|
||||
3 /* LSSLEEP */ => "Sleeping",
|
||||
4 /* LSSTOP */ => "Stopped",
|
||||
5 /* LSZOMB */ => "Zombie",
|
||||
#[cfg(target_os = "openbsd")] // removed in NetBSD
|
||||
6 /* LSDEAD */ => "Dead",
|
||||
7 /* LSONPROC */ => "Running",
|
||||
#[cfg(target_os = "netbsd")] // doesn't exist in OpenBSD
|
||||
8 /* LSSUSPENDED */ => "Suspended",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// CPU usage as a percent of total
|
||||
pub fn cpu_usage(&self) -> f64 {
|
||||
self.percent_cpu
|
||||
}
|
||||
|
||||
/// Memory size in number of bytes
|
||||
pub fn mem_size(&self) -> u64 {
|
||||
self.mem_resident
|
||||
}
|
||||
|
||||
/// Virtual memory size in bytes
|
||||
pub fn virtual_size(&self) -> u64 {
|
||||
self.mem_virtual
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user