#![allow(non_snake_case)]
#![allow(dead_code)]
use core::ffi::CStr;
use std::os::unix::io::FromRawFd;
use std::sync::atomic::{AtomicU64, Ordering};
use libc::ssize_t;
use crate::ported::linux::cgrouputils::{CGroup_filterContainer, CGroup_filterName};
use crate::ported::linux::compat::{
openat_arg_t, Compat_faccessat, Compat_openat, Compat_readfile, Compat_readfileat,
};
use crate::ported::linux::linuxmachine::LinuxMachine;
use crate::ported::linux::linuxprocess::LinuxProcess;
use crate::ported::machine::Machine;
use crate::ported::process::{
Process, ProcessField, ProcessState, Process_getPid, Process_setParent, Process_updateCmdline,
Process_updateComm, Process_updateExe, Tristate,
};
use crate::ported::processtable::{ProcessTable, ProcessTable_init};
use crate::ported::row::{spaceship_number, Row_updateFieldWidth};
use crate::ported::settings::RowField;
use crate::ported::xutils::{
saturatingSub, String_eq, String_safeStrncpy, String_startsWith, String_strchrnul,
};
const PROCDIR: &str = "/proc";
const PROCTTYDRIVERSFILE: &CStr = c"/proc/tty/drivers";
const PROC_LINE_LENGTH: usize = 4096;
const MAX_READ: usize = 2048;
const MAX_NAME: usize = 128;
const PATH_MAX: usize = 4096;
const PF_KTHREAD: u64 = 0x0020_0000;
const MAX_CMDLINE_BUFFER_SIZE: usize = 2 * 1024 * 1024 + 512;
#[allow(non_upper_case_globals)] static rootPidNs: AtomicU64 = AtomicU64::new(u64::MAX);
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TtyDriver {
pub path: Option<String>,
pub major: u32,
pub minorFrom: u32,
pub minorTo: u32,
}
pub struct LinuxProcessTable {
pub super_: ProcessTable,
pub ttyDrivers: Option<Vec<TtyDriver>>,
pub haveSmapsRollup: bool,
pub haveAutogroup: bool,
}
fn fopenat(openatArg: openat_arg_t, pathname: &CStr, mode: &str) -> Option<std::fs::File> {
debug_assert!(mode == "r");
let fd = Compat_openat(openatArg, pathname, libc::O_RDONLY);
if fd < 0 {
return None;
}
Some(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub fn strtopid(str: &str) -> i32 {
let bytes = str.as_bytes();
let mut i = 0;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
let mut neg = false;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
neg = bytes[i] == b'-';
i += 1;
}
let mut parsed: u64 = 0;
while i < bytes.len() && bytes[i].is_ascii_digit() {
parsed = parsed
.wrapping_mul(10)
.wrapping_add((bytes[i] - b'0') as u64);
i += 1;
}
let endptr_is_nul = i == bytes.len();
let parsed_pid = if neg { parsed.wrapping_neg() } else { parsed };
if parsed_pid == 0 || parsed_pid >= i32::MAX as u64 || !endptr_is_nul {
return 0; }
parsed_pid as i32
}
pub fn fast_strtoull_dec(str: &mut &[u8], mut maxlen: usize) -> u64 {
let mut result: u64 = 0;
if maxlen == 0 {
maxlen = 20; }
while maxlen > 0 {
match str.first() {
Some(&c) if c.is_ascii_digit() => {
result = result.wrapping_mul(10);
result = result.wrapping_add((c - b'0') as u64);
*str = &str[1..];
}
_ => break,
}
maxlen -= 1;
}
result
}
pub fn fast_strtoll_dec(str: &mut &[u8], maxlen: usize) -> i64 {
let mut neg = false;
if str.first() == Some(&b'-') {
neg = true;
*str = &str[1..];
}
let res = fast_strtoull_dec(str, maxlen);
debug_assert!(res <= i64::MAX as u64);
let result = res as i64;
if neg {
-result
} else {
result
}
}
pub fn fast_strtoi_dec(str: &mut &[u8], mut maxlen: usize) -> i32 {
if maxlen == 0 {
maxlen = 10; }
let result = fast_strtoll_dec(str, maxlen);
debug_assert!(result <= i32::MAX as i64);
debug_assert!(result >= i32::MIN as i64);
result as i32
}
pub fn fast_strtol_dec(str: &mut &[u8], maxlen: usize) -> i64 {
fast_strtoll_dec(str, maxlen)
}
pub fn fast_strtoul_dec(str: &mut &[u8], maxlen: usize) -> u64 {
fast_strtoull_dec(str, maxlen)
}
pub fn fast_strtoull_hex(str: &mut &[u8], mut maxlen: usize) -> u64 {
let mut result: u64 = 0;
let valid_mask: i64 = 0x03FF007E;
if maxlen == 0 {
maxlen = 18; }
while maxlen > 0 {
maxlen -= 1;
let mut nibble: i32 = match str.first() {
Some(&c) => c as i32,
None => 0,
};
if (valid_mask & (1i64 << (nibble & 0x1F))) == 0 {
break;
}
if nibble < b'0' as i32 || (nibble & !0x20) > b'F' as i32 {
break;
}
let letter = if (nibble & 0x40) != 0 {
b'A' as i32 - b'9' as i32 - 1
} else {
0
};
nibble &= !0x20; nibble ^= 0x10; nibble -= letter;
nibble &= 0x0f;
result <<= 4;
result += nibble as u64;
*str = &str[1..];
}
result
}
pub fn sortTtyDrivers(va: &TtyDriver, vb: &TtyDriver) -> i32 {
let a = va;
let b = vb;
let r = spaceship_number!(a.major, b.major);
if r != 0 {
return r;
}
spaceship_number!(a.minorFrom, b.minorFrom)
}
fn LinuxProcessTable_initTtyDrivers(this: &mut LinuxProcessTable) {
let mut buf = [0u8; 16384];
let r = Compat_readfile(PROCTTYDRIVERSFILE, &mut buf);
if r < 0 {
return;
}
let atoi = |s: &str| -> u32 {
let mut v: u32 = 0;
for c in s.bytes() {
if !c.is_ascii_digit() {
break;
}
v = v.wrapping_mul(10).wrapping_add((c - b'0') as u32);
}
v
};
let text = &buf[..r as usize];
let mut ttyDrivers: Vec<TtyDriver> = Vec::new();
for line in text.split(|&c| c == b'\n') {
let line = match std::str::from_utf8(line) {
Ok(s) => s,
Err(_) => continue,
};
let mut it = line.split_whitespace();
if it.next().is_none() {
continue;
}
let path = match it.next() {
Some(p) => p,
None => continue, };
let major = match it.next() {
Some(m) => atoi(m),
None => continue, };
let minor = match it.next() {
Some(m) => m,
None => continue, };
let (minorFrom, minorTo) = match minor.split_once('-') {
Some((from, to)) => (atoi(from), atoi(to)),
None => (atoi(minor), atoi(minor)),
};
ttyDrivers.push(TtyDriver {
path: Some(path.to_string()),
major,
minorFrom,
minorTo,
});
}
ttyDrivers.sort_by(|a, b| match sortTtyDrivers(a, b) {
n if n < 0 => std::cmp::Ordering::Less,
0 => std::cmp::Ordering::Equal,
_ => std::cmp::Ordering::Greater,
});
ttyDrivers.push(TtyDriver::default());
this.ttyDrivers = Some(ttyDrivers);
}
pub fn ProcessTable_new(host: *const Machine, pidMatchList: Option<usize>) -> LinuxProcessTable {
let mut this = LinuxProcessTable {
super_: ProcessTable::empty(),
ttyDrivers: None,
haveSmapsRollup: false,
haveAutogroup: false,
};
ProcessTable_init(&mut this.super_, host, pidMatchList);
LinuxProcessTable_initTtyDrivers(&mut this);
this.haveSmapsRollup =
unsafe { libc::access(c"/proc/self/smaps_rollup".as_ptr(), libc::R_OK) } == 0;
let mut sb: libc::stat = unsafe { std::mem::zeroed() };
let r = unsafe { libc::stat(c"/proc/self/ns/pid".as_ptr(), &mut sb) };
if r == 0 {
rootPidNs.store(sb.st_ino as u64, Ordering::Relaxed);
} else {
rootPidNs.store(u64::MAX, Ordering::Relaxed);
}
this
}
pub fn ProcessTable_delete() {
todo!("port of LinuxProcessTable.c:287 — pure free() teardown; Rust Drop handles it")
}
fn LinuxProcessTable_adjustTime(lhost: &LinuxMachine, t: u64) -> u64 {
t * 100 / lhost.jiffies as u64
}
pub fn LinuxProcessTable_getProcessState(state: u8) -> ProcessState {
match state {
b'S' => ProcessState::SLEEPING,
b'X' => ProcessState::DEFUNCT,
b'Z' => ProcessState::ZOMBIE,
b't' => ProcessState::TRACED,
b'T' => ProcessState::STOPPED,
b'D' => ProcessState::UNINTERRUPTIBLE_WAIT,
b'R' => ProcessState::RUNNING,
b'P' => ProcessState::BLOCKED,
b'I' => ProcessState::IDLE,
_ => ProcessState::UNKNOWN,
}
}
fn LinuxProcessTable_readStatFile(
lp: &mut LinuxProcess,
procFd: openat_arg_t,
lhost: &LinuxMachine,
scanMainThread: bool,
command: &mut [u8],
commLen: usize,
) -> bool {
let mut buf = [0u8; MAX_READ + 1];
let path = if scanMainThread {
std::ffi::CString::new(format!("task/{}/stat", Process_getPid(&lp.super_))).unwrap()
} else {
std::ffi::CString::new("stat").unwrap()
};
let r = Compat_readfileat(procFd, &path, &mut buf);
if r < 0 {
return false;
}
let byte = |i: usize| -> u8 { buf.get(i).copied().unwrap_or(0) };
let find = |from: usize, ch: u8| -> Option<usize> {
let mut i = from;
loop {
let c = buf.get(i).copied().unwrap_or(0);
if c == 0 {
return None;
}
if c == ch {
return Some(i);
}
i += 1;
}
};
let read_i = |loc: usize| -> (i32, usize) {
let mut cur: &[u8] = &buf[loc..];
let v = fast_strtoi_dec(&mut cur, 0);
(v, buf.len() - cur.len())
};
let read_ul = |loc: usize| -> (u64, usize) {
let mut cur: &[u8] = &buf[loc..];
let v = fast_strtoul_dec(&mut cur, 0);
(v, buf.len() - cur.len())
};
let read_ull = |loc: usize| -> (u64, usize) {
let mut cur: &[u8] = &buf[loc..];
let v = fast_strtoull_dec(&mut cur, 0);
(v, buf.len() - cur.len())
};
let read_l = |loc: usize| -> (i64, usize) {
let mut cur: &[u8] = &buf[loc..];
let v = fast_strtol_dec(&mut cur, 0);
(v, buf.len() - cur.len())
};
let read_ll = |loc: usize| -> (i64, usize) {
let mut cur: &[u8] = &buf[loc..];
let v = fast_strtoll_dec(&mut cur, 0);
(v, buf.len() - cur.len())
};
debug_assert_eq!(
Process_getPid(&lp.super_),
fast_strtoi_dec(&mut &buf[..], 0)
);
let mut loc = match find(0, b' ') {
Some(i) => i,
None => return false,
};
if byte(loc) == 0 || byte(loc + 1) == 0 {
return false;
}
loc += 2;
let end = {
let mut e: Option<usize> = None;
let mut i = loc;
while byte(i) != 0 {
if byte(i) == b')' {
e = Some(i);
}
i += 1;
}
match e {
Some(i) => i,
None => return false,
}
};
if end < loc {
return false;
}
let size = core::cmp::min(end - loc + 1, commLen).min(command.len());
if size > 0 {
String_safeStrncpy(&mut command[..size], &buf[loc..]);
}
if byte(end) == 0 || byte(end + 1) == 0 {
return false;
}
loc = end + 2;
lp.super_.state = LinuxProcessTable_getProcessState(byte(loc));
if byte(loc) == 0 || byte(loc + 1) == 0 {
return false;
}
loc += 2;
let (ppid, l) = read_i(loc);
Process_setParent(&mut lp.super_, ppid);
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_i(loc);
lp.super_.pgrp = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_i(loc);
lp.super_.session = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ul(loc);
lp.super_.tty_nr = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_i(loc);
lp.super_.tpgid = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ul(loc);
lp.flags = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.super_.minflt = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.cminflt = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.super_.majflt = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.cmajflt = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.utime = LinuxProcessTable_adjustTime(lhost, v);
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.stime = LinuxProcessTable_adjustTime(lhost, v);
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.cutime = LinuxProcessTable_adjustTime(lhost, v);
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_ull(loc);
lp.cstime = LinuxProcessTable_adjustTime(lhost, v);
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_l(loc);
lp.super_.priority = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_i(loc);
lp.super_.nice = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
let (v, l) = read_l(loc);
lp.super_.nlwp = v;
loc = l;
if byte(loc) == 0 {
return false;
}
loc += 1;
loc = match find(loc, b' ') {
Some(i) => i,
None => return false,
};
loc += 1;
if lp.super_.starttime_ctime == 0 {
let (v, l) = read_ll(loc);
lp.super_.starttime_ctime =
lhost.boottime + (LinuxProcessTable_adjustTime(lhost, v as u64) / 100) as i64;
loc = l;
} else {
loc = match find(loc, b' ') {
Some(i) => i,
None => return false,
};
}
loc += 1;
for _ in 0..16 {
loc = match find(loc, b' ') {
Some(i) => i,
None => return false,
};
loc += 1;
}
let (v, _l) = read_i(loc);
lp.super_.processor = v;
lp.super_.time = lp.utime + lp.stime;
true
}
fn LinuxProcessTable_readStatusFile(process: &mut LinuxProcess, procFd: openat_arg_t) -> bool {
use std::io::BufRead;
let mut ctxt: u64 = 0;
process.super_.isRunningInContainer = Tristate::TRI_OFF;
let statusfile = match fopenat(procFd, c"status", "r") {
Some(f) => f,
None => return false,
};
let reader = std::io::BufReader::new(statusfile);
for line in reader.lines() {
let buffer = match line {
Ok(l) => l,
Err(_) => break,
};
if String_startsWith(&buffer, "NSpid:") {
let bytes = buffer.as_bytes();
let mut pid_ns_count = 0;
let mut i = 0;
while i < bytes.len() && !bytes[i].is_ascii_digit() {
i += 1;
}
while i < bytes.len() {
if bytes[i].is_ascii_digit() {
pid_ns_count += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
}
while i < bytes.len() && !bytes[i].is_ascii_digit() {
i += 1;
}
}
if pid_ns_count > 1 {
process.super_.isRunningInContainer = Tristate::TRI_ON;
}
} else if String_startsWith(&buffer, "voluntary_ctxt_switches:") {
if let Some(v) = buffer
.strip_prefix("voluntary_ctxt_switches:")
.and_then(|s| s.trim().parse::<u64>().ok())
{
ctxt += v;
}
} else if String_startsWith(&buffer, "nonvoluntary_ctxt_switches:") {
if let Some(v) = buffer
.strip_prefix("nonvoluntary_ctxt_switches:")
.and_then(|s| s.trim().parse::<u64>().ok())
{
ctxt += v;
}
}
}
process.ctxt_diff = if ctxt > process.ctxt_total {
ctxt - process.ctxt_total
} else {
0
};
process.ctxt_total = ctxt;
true
}
pub fn LinuxProcessTable_updateUser() {
todo!("port of LinuxProcessTable.c:628 — needs Machine::usersTable as a real UsersTable")
}
fn LinuxProcessTable_readIoFile(
lp: &mut LinuxProcess,
procFd: openat_arg_t,
host: &LinuxMachine,
scanMainThread: bool,
) {
let realtimeMs = host.super_.realtimeMs;
let path = if scanMainThread {
std::ffi::CString::new(format!("task/{}/io", Process_getPid(&lp.super_))).unwrap()
} else {
std::ffi::CString::new("io").unwrap()
};
let mut buffer = [0u8; 1024];
let r = Compat_readfileat(procFd, &path, &mut buffer);
if r < 0 {
lp.io_rate_read_bps = f64::NAN;
lp.io_rate_write_bps = f64::NAN;
lp.io_rchar = u64::MAX;
lp.io_wchar = u64::MAX;
lp.io_syscr = u64::MAX;
lp.io_syscw = u64::MAX;
lp.io_read_bytes = u64::MAX;
lp.io_write_bytes = u64::MAX;
lp.io_cancelled_write_bytes = u64::MAX;
lp.io_last_scan_time_ms = realtimeMs;
return;
}
let last_read = lp.io_read_bytes;
let last_write = lp.io_write_bytes;
let time_delta = saturatingSub(realtimeMs, lp.io_last_scan_time_ms);
let strtoull = |s: &str| -> u64 {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() && b[i].is_ascii_whitespace() {
i += 1;
}
if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
i += 1;
}
let mut v: u64 = 0;
while i < b.len() && b[i].is_ascii_digit() {
v = v.saturating_mul(10).saturating_add((b[i] - b'0') as u64);
i += 1;
}
v
};
let text = std::str::from_utf8(&buffer[..r as usize]).unwrap_or("");
for line in text.split('\n') {
if let Some(rest) = line.strip_prefix("rchar: ") {
lp.io_rchar = strtoull(rest);
} else if let Some(rest) = line.strip_prefix("read_bytes: ") {
lp.io_read_bytes = strtoull(rest);
lp.io_rate_read_bps = if time_delta != 0 {
saturatingSub(lp.io_read_bytes, last_read) as f64 * 1000. / time_delta as f64
} else {
f64::NAN
};
} else if let Some(rest) = line.strip_prefix("wchar: ") {
lp.io_wchar = strtoull(rest);
} else if let Some(rest) = line.strip_prefix("write_bytes: ") {
lp.io_write_bytes = strtoull(rest);
lp.io_rate_write_bps = if time_delta != 0 {
saturatingSub(lp.io_write_bytes, last_write) as f64 * 1000. / time_delta as f64
} else {
f64::NAN
};
} else if let Some(rest) = line.strip_prefix("syscr: ") {
lp.io_syscr = strtoull(rest);
} else if let Some(rest) = line.strip_prefix("syscw: ") {
lp.io_syscw = strtoull(rest);
} else if let Some(rest) = line.strip_prefix("cancelled_write_bytes: ") {
lp.io_cancelled_write_bytes = strtoull(rest);
}
}
lp.io_last_scan_time_ms = realtimeMs;
}
pub fn LinuxProcessTable_calcLibSize_helper() {
todo!("port of LinuxProcessTable.c:727 — foreach-closure model; LibraryData not an Object")
}
pub fn LinuxProcessTable_readMaps() {
todo!("port of LinuxProcessTable.c:745 — Hashtable_get is immutable; LibraryData not an Object")
}
fn LinuxProcessTable_readStatmFile(
process: &mut LinuxProcess,
procFd: openat_arg_t,
host: &LinuxMachine,
mainTask: Option<&LinuxProcess>,
) -> bool {
if let Some(mt) = mainTask {
process.super_.m_virt = mt.super_.m_virt;
process.super_.m_resident = mt.super_.m_resident;
return true;
}
let mut statmdata = [0u8; 128];
if Compat_readfileat(procFd, c"statm", &mut statmdata) < 1 {
return false;
}
let nul = statmdata
.iter()
.position(|&b| b == 0)
.unwrap_or(statmdata.len());
let text = std::str::from_utf8(&statmdata[..nul]).unwrap_or("");
let mut fields = [0i64; 7];
let mut r = 0usize;
for tok in text.split_ascii_whitespace() {
if r >= 7 {
break;
}
match tok.parse::<i64>() {
Ok(v) => {
fields[r] = v;
r += 1;
}
Err(_) => break,
}
}
if r >= 1 {
process.super_.m_virt = fields[0];
}
if r >= 2 {
process.super_.m_resident = fields[1];
}
if r >= 3 {
process.m_share = fields[2];
}
if r >= 4 {
process.m_trs = fields[3];
}
if r >= 6 {
process.m_drs = fields[5];
}
if r == 7 {
process.super_.m_virt *= host.pageSizeKB as i64;
process.super_.m_resident *= host.pageSizeKB as i64;
process.m_priv = process.super_.m_resident - (process.m_share * host.pageSizeKB as i64);
}
r == 7
}
pub fn LinuxProcessTable_readSmapsFile() {
todo!("port of LinuxProcessTable.c:897 — needs skipEndOfLine ported")
}
pub fn LinuxProcessTable_readOpenVZData() {
todo!("port of LinuxProcessTable.c:934 — needs skipEndOfLine + ctid/vpid fields + HAVE_OPENVZ")
}
fn LinuxProcessTable_readCGroupFile(process: &mut LinuxProcess, procFd: openat_arg_t) {
use std::io::BufRead;
let file = match fopenat(procFd, c"cgroup", "r") {
Some(f) => f,
None => {
process.cgroup = None;
process.cgroup_short = None;
process.container_short = None;
return;
}
};
let mut output: Vec<u8> = Vec::new();
let mut left = PROC_LINE_LENGTH;
let mut reader = std::io::BufReader::new(file);
let mut line = String::new();
while left > 0 {
line.clear();
match reader.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) => {}
}
let bytes = line.as_bytes();
let mut group = 0usize;
for _ in 0..2 {
group += String_strchrnul(&line[group..], b':');
if group >= bytes.len() {
break;
}
group += 1; }
let group_end = group + String_strchrnul(&line[group..], b'\n');
let group_bytes = &bytes[group..group_end];
if !output.is_empty() {
if left == 0 {
break;
}
output.push(b';');
left -= 1;
}
let wrote = group_bytes.len();
if wrote >= left {
let n = left.saturating_sub(1);
output.extend_from_slice(&group_bytes[..n]);
break;
}
output.extend_from_slice(group_bytes);
left -= wrote;
}
drop(reader);
let output = String::from_utf8_lossy(&output).into_owned();
let changed = match &process.cgroup {
Some(c) => !String_eq(c, &output),
None => true,
};
Row_updateFieldWidth(ProcessField::CGROUP as RowField, output.len());
process.cgroup = Some(output);
if !changed {
match &process.cgroup_short {
Some(cs) => Row_updateFieldWidth(ProcessField::CCGROUP as RowField, cs.len()),
None => Row_updateFieldWidth(
ProcessField::CCGROUP as RowField,
process.cgroup.as_deref().unwrap().len(),
),
}
match &process.container_short {
Some(cs) => Row_updateFieldWidth(ProcessField::CONTAINER as RowField, cs.len()),
None => Row_updateFieldWidth(ProcessField::CONTAINER as RowField, "N/A".len()),
}
return;
}
let cgroup_short = CGroup_filterName(process.cgroup.as_deref().unwrap());
match cgroup_short {
Some(cs) => {
Row_updateFieldWidth(ProcessField::CCGROUP as RowField, cs.len());
process.cgroup_short = Some(cs);
}
None => {
Row_updateFieldWidth(
ProcessField::CCGROUP as RowField,
process.cgroup.as_deref().unwrap().len(),
);
process.cgroup_short = None;
}
}
let container_short = CGroup_filterContainer(process.cgroup.as_deref().unwrap());
match container_short {
Some(cs) => {
Row_updateFieldWidth(ProcessField::CONTAINER as RowField, cs.len());
process.container_short = Some(cs);
}
None => {
Row_updateFieldWidth(ProcessField::CONTAINER as RowField, "N/A".len());
process.container_short = None;
}
}
}
fn LinuxProcessTable_readOomData(
process: &mut LinuxProcess,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) {
if let Some(mt) = mainTask {
process.oom = mt.oom;
return;
}
let mut buffer = [0u8; PROC_LINE_LENGTH + 1];
process.oom = u32::MAX; let oomRead = Compat_readfileat(procFd, c"oom_score", &mut buffer);
if oomRead < 1 {
return;
}
let mut cur: &[u8] = &buffer[..];
let oom = fast_strtoull_dec(&mut cur, oomRead as usize);
let next = buffer.len() - cur.len();
let c = buffer.get(next).copied().unwrap_or(0);
if c != 0 && c != b'\n' && c != b' ' {
return;
}
if oom > u32::MAX as u64 {
return;
}
process.oom = oom as u32;
}
fn LinuxProcessTable_readAutogroup(
process: &mut LinuxProcess,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) {
if let Some(mt) = mainTask {
process.autogroup_id = mt.autogroup_id;
return;
}
process.autogroup_id = -1;
let mut autogroup = [0u8; 64];
let amtRead = Compat_readfileat(procFd, c"autogroup", &mut autogroup);
if amtRead < 0 {
return;
}
let nul = autogroup
.iter()
.position(|&b| b == 0)
.unwrap_or(autogroup.len());
let content = std::str::from_utf8(&autogroup[..nul]).unwrap_or("");
let parsed = (|| -> Option<(i64, i32)> {
let rest = content.strip_prefix("/autogroup-")?;
let mut it = rest.split_whitespace();
let identity: i64 = it.next()?.parse().ok()?;
if it.next()? != "nice" {
return None;
}
let nice: i32 = it.next()?.parse().ok()?;
Some((identity, nice))
})();
if let Some((identity, nice)) = parsed {
process.autogroup_id = identity;
process.autogroup_nice = nice;
}
}
fn LinuxProcessTable_readSecattrData(
process: &mut LinuxProcess,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) {
if let Some(mt) = mainTask {
process.secattr = mt.secattr.clone();
return;
}
let mut buffer = [0u8; PROC_LINE_LENGTH + 1];
let attrdata = Compat_readfileat(procFd, c"attr/current", &mut buffer);
if attrdata < 1 {
process.secattr = None;
return;
}
let end = buffer
.iter()
.position(|&b| b == b'\n' || b == 0)
.unwrap_or(buffer.len());
let text = String::from_utf8_lossy(&buffer[..end]).into_owned();
Row_updateFieldWidth(ProcessField::SECATTR as RowField, text.len());
process.secattr = Some(text);
}
fn LinuxProcessTable_readCwd(
process: &mut LinuxProcess,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) {
if let Some(mt) = mainTask {
process.super_.procCwd = mt.super_.procCwd.clone();
return;
}
let mut pathBuffer = [0u8; PATH_MAX + 1];
let r = unsafe {
libc::readlinkat(
procFd,
c"cwd".as_ptr(),
pathBuffer.as_mut_ptr() as *mut libc::c_char,
pathBuffer.len() - 1,
)
};
if r < 0 {
process.super_.procCwd = None;
return;
}
process.super_.procCwd = Some(String::from_utf8_lossy(&pathBuffer[..r as usize]).into_owned());
}
fn LinuxProcessList_readExe(
process: &mut Process,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) {
if let Some(mt) = mainTask {
Process_updateExe(process, mt.super_.procExe.as_deref());
process.procExeDeleted = mt.super_.procExeDeleted;
return;
}
let mut filename = [0u8; PATH_MAX + 1];
let amtRead = unsafe {
libc::readlinkat(
procFd,
c"exe".as_ptr(),
filename.as_mut_ptr() as *mut libc::c_char,
filename.len() - 1,
)
};
if amtRead > 0 {
let mut fbytes = filename[..amtRead as usize].to_vec();
let differs = process
.procExe
.as_deref()
.map(|e| e.as_bytes() != fbytes.as_slice())
.unwrap_or(true);
#[allow(clippy::nonminimal_bool)]
let cond = process.procExe.is_none()
|| (!process.procExeDeleted && differs)
|| process.procExeDeleted;
if cond {
const DELETED_MARKER: &[u8] = b" (deleted)";
let markerLen = DELETED_MARKER.len();
let filenameLen = fbytes.len();
if filenameLen > markerLen {
let oldExeDeleted = process.procExeDeleted;
process.procExeDeleted = &fbytes[filenameLen - markerLen..] == DELETED_MARKER;
if process.procExeDeleted {
fbytes.truncate(filenameLen - markerLen);
}
if oldExeDeleted != process.procExeDeleted {
process.mergedCommand.lastUpdate = 0;
}
}
let s = String::from_utf8_lossy(&fbytes).into_owned();
Process_updateExe(process, Some(&s));
}
} else if process.procExe.is_some() {
Process_updateExe(process, None);
process.procExeDeleted = false;
}
}
fn readFileDynamic(procFd: openat_arg_t, filename: &CStr) -> Option<(Vec<u8>, ssize_t)> {
let mut bufferSize: usize = 512;
let mut buffer = vec![0u8; bufferSize];
let mut amtRead = Compat_readfileat(procFd, filename, &mut buffer);
while amtRead > 0 && amtRead as usize == bufferSize - 1 && bufferSize < MAX_CMDLINE_BUFFER_SIZE
{
bufferSize *= 2;
buffer.resize(bufferSize, 0);
amtRead = Compat_readfileat(procFd, filename, &mut buffer);
}
if amtRead <= 0 {
return None;
}
Some((buffer, amtRead))
}
fn LinuxProcessTable_readCmdlineFile(
process: &mut Process,
procFd: openat_arg_t,
mainTask: Option<&LinuxProcess>,
) -> bool {
LinuxProcessList_readExe(process, procFd, mainTask);
let (mut command, amtRead) = match readFileDynamic(procFd, c"cmdline") {
Some(v) => v,
None => return false,
};
let amtRead = amtRead as usize;
const NPOS: usize = usize::MAX;
let mut tokenEnd = NPOS;
let mut tokenStart = NPOS;
let mut lastChar = 0usize;
let mut argSepNUL = false;
let mut argSepSpace = false;
for i in 0..amtRead {
let argChar = command[i];
if argChar == b'\n' {
command[i] = b'\r';
continue;
}
if argChar == b'\0' {
command[i] = b'\n';
if tokenEnd == NPOS {
tokenEnd = i;
}
continue;
}
if tokenEnd != NPOS {
argSepNUL = true;
}
if argChar <= b' ' {
argSepSpace = true;
}
if argChar == b'/' && tokenEnd == NPOS {
tokenStart = i + 1;
}
lastChar = i;
}
command[lastChar + 1] = b'\0';
let faccess = |bytes: &[u8]| -> i32 {
match std::ffi::CString::new(bytes) {
Ok(cs) => Compat_faccessat(libc::AT_FDCWD, &cs, libc::F_OK, libc::AT_SYMLINK_NOFOLLOW),
Err(_) => -1,
}
};
if !argSepNUL && argSepSpace {
tokenStart = NPOS;
tokenEnd = NPOS;
let exeLen = process.procExe.as_ref().map(|s| s.len()).unwrap_or(0);
let starts_with_exe = process
.procExe
.as_deref()
.map(|e| command[..=lastChar].starts_with(e.as_bytes()))
.unwrap_or(false);
if process.procExe.is_some()
&& starts_with_exe
&& exeLen < lastChar
&& command[exeLen] <= b' '
{
tokenStart = process.procExeBasenameOffset;
tokenEnd = exeLen;
}
else if faccess(&command[..=lastChar]) != 0 {
let mut tokenArg0Start = NPOS;
for i in 0..=lastChar {
let cmdChar = command[i];
if cmdChar <= b' ' {
if tokenEnd != NPOS {
command[i] = b'\n';
continue;
}
command[i] = b'\0';
let found = faccess(&command[..i]) == 0;
command[i] = if found { b'\n' } else { cmdChar };
if found {
tokenEnd = i;
}
if tokenArg0Start == NPOS {
tokenArg0Start = if tokenStart == NPOS { 0 } else { tokenStart };
}
continue;
}
if tokenEnd != NPOS {
continue;
}
if cmdChar == b'/' {
tokenStart = i + 1;
} else if cmdChar == b'\\'
&& (tokenStart == NPOS || tokenStart == 0 || command[tokenStart - 1] == b'\\')
{
tokenStart = i + 1;
} else if cmdChar == b':' && (command[i + 1] != b'/' && command[i + 1] != b'\\') {
tokenEnd = i;
} else if tokenStart == NPOS {
tokenStart = i;
}
}
if tokenEnd == NPOS {
tokenStart = tokenArg0Start;
for i in 0..=lastChar {
if command[i] <= b' ' {
command[i] = b'\n';
if tokenEnd == NPOS {
tokenEnd = i;
}
}
}
}
}
if tokenStart >= tokenEnd {
tokenStart = NPOS;
tokenEnd = NPOS;
}
}
if tokenStart == NPOS {
tokenStart = 0;
}
if tokenEnd == NPOS {
tokenEnd = lastChar + 1;
}
let s = String::from_utf8_lossy(&command[..=lastChar]).into_owned();
Process_updateCmdline(process, Some(&s), tokenStart, tokenEnd);
true
}
fn LinuxProcessList_readComm(process: &mut Process, procFd: openat_arg_t) {
match readFileDynamic(procFd, c"comm") {
Some((command, amtRead)) => {
let end = (amtRead as usize).saturating_sub(1);
let s = String::from_utf8_lossy(&command[..end]).into_owned();
Process_updateComm(process, Some(&s));
}
None => Process_updateComm(process, None),
}
}
pub fn LinuxProcessTable_updateTtyDevice() {
todo!("port of LinuxProcessTable.c:1514 — needs major()/minor() device-number macros")
}
fn isOlderThan(proc: &Process, seconds: u32) -> bool {
let host = proc.super_.host as *const Machine;
let realtimeMs = unsafe { (*host).realtimeMs };
debug_assert!(realtimeMs > 0);
if proc.starttime_ctime <= 0 {
return false;
}
let realtime = realtimeMs / 1000;
if realtime < proc.starttime_ctime as u64 {
return false;
}
realtime - proc.starttime_ctime as u64 > seconds as u64
}
pub fn LinuxProcessTable_recurseProcTree() {
todo!("port of LinuxProcessTable.c:1588 — needs ProcessTable_getProcess/_add (process-typed table)")
}
pub fn ProcessTable_goThroughEntries() {
todo!("port of LinuxProcessTable.c:1951 — delegates to the stubbed recurseProcTree")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fast_strtoull_dec_parses_and_advances() {
let s = b"12345rest";
let mut cur: &[u8] = s;
assert_eq!(fast_strtoull_dec(&mut cur, 0), 12345);
assert_eq!(cur, b"rest");
let mut cur2: &[u8] = b"12345";
assert_eq!(fast_strtoull_dec(&mut cur2, 3), 123);
assert_eq!(cur2, b"45");
let mut cur3: &[u8] = b"abc";
assert_eq!(fast_strtoull_dec(&mut cur3, 0), 0);
assert_eq!(cur3, b"abc");
}
#[test]
fn fast_strtoll_dec_handles_sign() {
let mut cur: &[u8] = b"-42 ";
assert_eq!(fast_strtoll_dec(&mut cur, 0), -42);
assert_eq!(cur, b" ");
let mut cur2: &[u8] = b"7";
assert_eq!(fast_strtoll_dec(&mut cur2, 0), 7);
}
#[test]
fn fast_strtoi_and_strtol_and_strtoul() {
let mut cur: &[u8] = b"-2147483648";
assert_eq!(fast_strtoi_dec(&mut cur, 0), i32::MIN);
let mut cur2: &[u8] = b"-5";
assert_eq!(fast_strtol_dec(&mut cur2, 0), -5);
let mut cur3: &[u8] = b"100";
assert_eq!(fast_strtoul_dec(&mut cur3, 0), 100);
}
#[test]
fn fast_strtoull_hex_parses_mixed_case() {
let mut cur: &[u8] = b"deadBEEF!";
assert_eq!(fast_strtoull_hex(&mut cur, 0), 0xdeadbeef);
assert_eq!(cur, b"!");
let mut cur2: &[u8] = b"ff";
assert_eq!(fast_strtoull_hex(&mut cur2, 0), 0xff);
let mut cur3: &[u8] = b"zzz";
assert_eq!(fast_strtoull_hex(&mut cur3, 0), 0);
assert_eq!(cur3, b"zzz");
}
#[test]
fn strtopid_accepts_valid_and_rejects_junk() {
assert_eq!(strtopid("1234"), 1234);
assert_eq!(strtopid("0"), 0); assert_eq!(strtopid("12a"), 0); assert_eq!(strtopid(""), 0);
assert_eq!(strtopid("-5"), 0); }
#[test]
fn sortTtyDrivers_orders_by_major_then_minor() {
let a = TtyDriver {
path: None,
major: 4,
minorFrom: 64,
minorTo: 95,
};
let b = TtyDriver {
path: None,
major: 4,
minorFrom: 0,
minorTo: 63,
};
let c = TtyDriver {
path: None,
major: 5,
minorFrom: 0,
minorTo: 1,
};
assert_eq!(sortTtyDrivers(&a, &b), 1);
assert_eq!(sortTtyDrivers(&b, &a), -1);
assert_eq!(sortTtyDrivers(&a, &c), -1);
assert_eq!(sortTtyDrivers(&a, &a), 0);
}
#[test]
fn isOlderThan_compares_against_host_realtime() {
use crate::ported::machine::Machine;
use crate::ported::process::Process;
use core::ffi::c_void;
let mut host = Machine::default();
host.realtimeMs = 100_000;
let mut proc = Process::default();
proc.super_.host = &host as *const Machine as *const c_void;
proc.starttime_ctime = 50;
assert!(isOlderThan(&proc, 10));
assert!(!isOlderThan(&proc, 60));
proc.starttime_ctime = 0;
assert!(!isOlderThan(&proc, 0));
}
#[test]
fn getProcessState_maps_known_and_unknown() {
assert_eq!(
LinuxProcessTable_getProcessState(b'R'),
ProcessState::RUNNING
);
assert_eq!(
LinuxProcessTable_getProcessState(b'S'),
ProcessState::SLEEPING
);
assert_eq!(
LinuxProcessTable_getProcessState(b'Z'),
ProcessState::ZOMBIE
);
assert_eq!(
LinuxProcessTable_getProcessState(b'D'),
ProcessState::UNINTERRUPTIBLE_WAIT
);
assert_eq!(
LinuxProcessTable_getProcessState(b'?'),
ProcessState::UNKNOWN
);
}
}