use crate::cache::ProcInfo;
use crate::traits::{CacheStore, TreeStore};
use crate::tree::{ProcEvent, ProcessLink};
use crate::types::PidNode;
pub fn snapshot(tree: &impl TreeStore, cache: &impl CacheStore) {
let dir = match std::fs::read_dir("/proc") {
Ok(d) => d,
Err(e) => {
eprintln!("[WARNING] proc-tree: cannot read /proc: {e}");
return;
}
};
for entry in dir.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
let pid: u32 = match name_str.parse() {
Ok(p) => p,
Err(_) => continue,
};
if let Some((node, info)) = crate::proc::parse_proc_entry(pid) {
tree.insert_node(pid, node);
cache.insert_info(pid, info);
}
}
}
pub fn resolve(cache: &impl CacheStore, pid: u32) -> Option<ProcInfo> {
if let Some(info) = cache.get_info(pid) {
return Some(info);
}
let cmd = crate::proc::read_proc_comm(pid)?;
let (user, ppid, tgid) =
crate::proc::read_proc_status_fields(pid).unwrap_or_else(|| ("unknown".to_string(), 0, 0));
let start_time_ns = crate::proc::read_proc_start_time_ns(pid);
let info = ProcInfo {
cmd,
user,
ppid,
tgid,
start_time_ns,
};
cache.insert_info(pid, info.clone());
Some(info)
}
pub fn handle_events(tree: &impl TreeStore, cache: &impl CacheStore, events: &[ProcEvent]) {
for event in events {
handle_event(tree, cache, event);
}
}
pub fn handle_event(tree: &impl TreeStore, cache: &impl CacheStore, event: &ProcEvent) {
match event {
ProcEvent::Fork {
child_pid,
parent_pid,
..
} => {
tree.insert_node(
*child_pid,
PidNode {
ppid: *parent_pid,
cmd: String::new(),
},
);
}
ProcEvent::Exec { pid, timestamp_ns } => {
let cmd = crate::proc::read_proc_comm(*pid).unwrap_or_else(|| "unknown".to_string());
let (user, ppid, tgid) = crate::proc::read_proc_status_fields(*pid)
.unwrap_or_else(|| ("unknown".to_string(), 0, 0));
tree.insert_node(
*pid,
PidNode {
ppid,
cmd: cmd.clone(),
},
);
cache.insert_info(
*pid,
ProcInfo {
cmd,
user,
ppid,
tgid,
start_time_ns: *timestamp_ns,
},
);
}
ProcEvent::Exit { .. } => {
}
}
}
pub fn is_descendant(tree: &impl TreeStore, pid: u32, target_cmd: &str) -> bool {
let mut current = pid;
let mut visited = std::collections::HashSet::new();
while let Some(node) = tree.get_node(current) {
if !visited.insert(current) {
break;
}
if node.cmd == target_cmd {
return true;
}
if node.ppid == 0 || current == node.ppid {
break;
}
current = node.ppid;
}
false
}
pub fn build_chain_links(
tree: &impl TreeStore,
cache: &impl CacheStore,
pid: u32,
) -> Vec<ProcessLink> {
let mut parts = Vec::new();
let mut current = pid;
let mut visited = std::collections::HashSet::new();
loop {
if !visited.insert(current) {
break;
}
let (ppid, cmd, user) = if let Some(node) = tree.get_node(current) {
let user = cache
.get_info(current)
.map(|info| info.user)
.unwrap_or_else(|| "unknown".to_string());
(node.ppid, node.cmd, user)
} else {
match crate::proc::read_proc_status_fields(current) {
Some((u, p, _)) => {
let c = crate::proc::read_proc_comm(current)
.unwrap_or_else(|| "unknown".to_string());
(p, c, u)
}
None => {
parts.push(ProcessLink {
pid: current,
cmd: "unknown".to_string(),
user: "unknown".to_string(),
});
break;
}
}
};
parts.push(ProcessLink {
pid: current,
cmd,
user,
});
if ppid == 0 || current == ppid {
break;
}
current = ppid;
}
parts
}
pub fn build_chain_string(tree: &impl TreeStore, cache: &impl CacheStore, pid: u32) -> String {
build_chain_links(tree, cache, pid)
.iter()
.map(|l| l.to_string())
.collect::<Vec<_>>()
.join(";")
}
pub fn children(tree: &impl TreeStore, pid: u32) -> Vec<u32> {
tree.all_pids()
.into_iter()
.filter(|&p| tree.get_node(p).map(|n| n.ppid == pid).unwrap_or(false))
.collect()
}
pub fn descendants(tree: &impl TreeStore, pid: u32) -> Vec<u32> {
let mut result = Vec::new();
let mut queue = std::collections::VecDeque::new();
queue.push_back(pid);
while let Some(current) = queue.pop_front() {
let kids = children(tree, current);
for kid in kids {
result.push(kid);
queue.push_back(kid);
}
}
result
}
pub fn siblings(tree: &impl TreeStore, pid: u32) -> Vec<u32> {
let ppid = match tree.get_node(pid) {
Some(node) => node.ppid,
None => return Vec::new(),
};
children(tree, ppid)
.into_iter()
.filter(|&c| c != pid)
.collect()
}
pub fn find_by_cmd(tree: &impl TreeStore, target_cmd: &str) -> Vec<u32> {
tree.all_pids()
.into_iter()
.filter(|&pid| {
let cmd = tree
.get_node(pid)
.map(|n| n.cmd)
.filter(|c| !c.is_empty())
.or_else(|| crate::proc::read_proc_comm(pid));
cmd.as_deref() == Some(target_cmd)
})
.collect()
}
pub fn find_by_user(tree: &impl TreeStore, cache: &impl CacheStore, target_user: &str) -> Vec<u32> {
tree.all_pids()
.into_iter()
.filter(|&pid| {
let user = cache
.get_info(pid)
.map(|info| info.user)
.or_else(|| crate::proc::read_proc_status_fields(pid).map(|(u, _, _)| u));
user.as_deref() == Some(target_user)
})
.collect()
}
pub fn display(tree: &impl TreeStore, root_pid: u32) -> String {
display_inner(tree, root_pid, true)
}
fn display_inner(tree: &impl TreeStore, pid: u32, is_root: bool) -> String {
let cmd = tree
.get_node(pid)
.map(|n| n.cmd)
.filter(|c| !c.is_empty())
.or_else(|| crate::proc::read_proc_comm(pid))
.unwrap_or_else(|| "unknown".to_string());
let kids = children(tree, pid);
if kids.is_empty() {
return cmd;
}
let mut output = cmd;
for (i, &kid) in kids.iter().enumerate() {
let is_last = i == kids.len() - 1;
let prefix = if is_last { "└─" } else { "├─" };
let continuation = if is_last { " " } else { "│ " };
let sub = display_inner(tree, kid, false);
let lines: Vec<&str> = sub.lines().collect();
if i == 0 && is_root {
output.push_str(&format!("─{}", lines[0]));
} else {
if i > 0 || is_root {
output.push('\n');
}
output.push_str(prefix);
output.push_str(lines[0]);
}
for line in &lines[1..] {
output.push('\n');
output.push_str(continuation);
output.push_str(line);
}
}
output
}
pub fn tree_len(tree: &impl TreeStore) -> u64 {
tree.all_pids().len() as u64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::default_store::DefaultTree;
#[test]
fn display_single_node() {
let tree = DefaultTree::new(100, 0);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
assert_eq!(display(&tree, 1), "init");
}
#[test]
fn display_root_with_children() {
let tree = DefaultTree::new(100, 0);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
tree.insert_node(
100,
PidNode {
ppid: 1,
cmd: "a".into(),
},
);
tree.insert_node(
200,
PidNode {
ppid: 1,
cmd: "b".into(),
},
);
let d = display(&tree, 1);
assert!(d.starts_with("init"));
assert!(d.contains("a"));
assert!(d.contains("b"));
}
}