#![cfg(target_os = "linux")]
#![allow(unsafe_code)]
use std::collections::{BTreeSet, HashMap};
use std::fs;
use crate::probes::now_monotonic_ns;
use crate::{probe_source, ProbeEvent};
#[derive(Debug, Default)]
pub struct ProcWalker {
seen: HashMap<u32, String>, }
impl ProcWalker {
pub fn new() -> Self {
Self::default()
}
pub fn poll(&mut self) -> Vec<ProbeEvent> {
let now = now_monotonic_ns();
let mut out = Vec::new();
let mut current: BTreeSet<u32> = BTreeSet::new();
if let Ok(entries) = fs::read_dir("/proc") {
for ent in entries.flatten() {
let name = ent.file_name();
let s = match name.to_str() {
Some(s) => s,
None => continue,
};
if let Ok(pid) = s.parse::<u32>() {
current.insert(pid);
if !self.seen.contains_key(&pid) {
let comm = read_comm(pid).unwrap_or_default();
self.seen.insert(pid, comm.clone());
out.push(ProbeEvent {
probe_source: probe_source::PROCESS_SPAWNED,
guest_pid: pid,
guest_comm: comm,
guest_monotonic_ns: now,
});
}
}
}
}
let exited: Vec<u32> = self
.seen
.keys()
.copied()
.filter(|pid| !current.contains(pid))
.collect();
for pid in exited {
let comm = self.seen.remove(&pid).unwrap_or_default();
out.push(ProbeEvent {
probe_source: probe_source::PROCESS_EXITED,
guest_pid: pid,
guest_comm: comm,
guest_monotonic_ns: now,
});
}
out
}
}
fn read_comm(pid: u32) -> Option<String> {
let path = format!("/proc/{pid}/comm");
let raw = fs::read_to_string(&path).ok()?;
let trimmed = raw.trim_end_matches('\n');
let mut s = String::from(trimmed);
if s.len() > 16 {
s.truncate(16);
}
Some(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_poll_discovers_self() {
let mut w = ProcWalker::new();
let events = w.poll();
assert!(
!events.is_empty(),
"first poll should yield at least one PROCESS_SPAWNED"
);
assert!(events
.iter()
.all(|e| e.probe_source == probe_source::PROCESS_SPAWNED));
}
#[test]
fn second_poll_is_quiet_modulo_churn() {
let mut w = ProcWalker::new();
let first = w.poll();
let second = w.poll();
assert!(
second.len() <= first.len(),
"second poll should not exceed first (got {} vs {})",
second.len(),
first.len()
);
}
}