proc_tree/tree.rs
1//! Process tree types: events, links, and node definitions.
2
3use std::fmt;
4
5use crate::traits::ProcessStore;
6
7// ---- Process events (decoupled from proc-connector) ----
8
9/// A process lifecycle event. Decoupled from any specific event source
10/// (proc-connector, audit, etc.) so users can adapt their own events.
11#[derive(Debug, Clone)]
12pub enum ProcEvent {
13 /// A new process was created. `parent_pid` is the parent.
14 Fork {
15 child_pid: u32,
16 parent_pid: u32,
17 timestamp_ns: u64,
18 },
19 /// A process executed a new program. Its cmd/user may have changed.
20 Exec { pid: u32, timestamp_ns: u64 },
21 /// A process exited. The node is preserved for historical chain lookups.
22 Exit { pid: u32 },
23}
24
25// ---- ExitedProcess (explicit removal handle) ----
26
27/// An exited process awaiting explicit removal from the store.
28///
29/// Returned by [`handle_event`](crate::handle_event) and [`handle_events`](crate::handle_events)
30/// for Exit events. The process info **stays in the store** until
31/// [`remove`](ExitedProcess::remove) is called, allowing late-arriving events
32/// to still look up process info.
33///
34/// # Example
35///
36/// ```
37/// use proc_tree::{DefaultStore, handle_event, ProcEvent, ExitedProcess, ProcessStore};
38///
39/// let store = DefaultStore::new(0);
40/// handle_event(&store, &ProcEvent::Fork { child_pid: 100, parent_pid: 1, timestamp_ns: 0 });
41///
42/// let exited = handle_event(&store, &ProcEvent::Exit { pid: 100 }).unwrap();
43/// assert_eq!(exited.pid, 100);
44///
45/// // Process still in store — caller can still query it
46/// assert!(store.get_process(100).is_some());
47///
48/// // Explicitly remove when done
49/// exited.remove(&store);
50/// assert!(store.get_process(100).is_none());
51/// ```
52#[derive(Debug, Clone, PartialEq, Eq)]
53#[must_use = "call .remove(&store) after processing related events, or the process stays in store until TTL expires"]
54pub struct ExitedProcess {
55 /// The PID of the exited process.
56 pub pid: u32,
57}
58
59impl ExitedProcess {
60 /// Get the PID of the exited process.
61 pub fn pid(&self) -> u32 {
62 self.pid
63 }
64
65 /// Remove this process from the store.
66 ///
67 /// Call this after all related events have been processed.
68 /// The process info is no longer accessible after this call.
69 pub fn remove<S: ProcessStore>(self, store: &S) {
70 store.remove_process(self.pid);
71 }
72}
73
74// ---- ProcessLink (structured chain element) ----
75
76/// A single entry in a process ancestry chain.
77///
78/// Displayed as `"pid|cmd|user"` by the `Display` impl.
79///
80/// ```
81/// use proc_tree::ProcessLink;
82///
83/// let link = ProcessLink { pid: 102, cmd: "touch".into(), user: "root".into() };
84/// assert_eq!(link.to_string(), "102|touch|root");
85/// ```
86///
87/// A chain is a `Vec<ProcessLink>` ordered from child to ancestor.
88#[derive(Debug, Clone)]
89pub struct ProcessLink {
90 pub pid: u32,
91 pub cmd: String,
92 pub user: String,
93}
94
95impl fmt::Display for ProcessLink {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{}|{}|{}", self.pid, self.cmd, self.user)
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn process_link_display_format() {
107 let link = ProcessLink {
108 pid: 42,
109 cmd: "bash".into(),
110 user: "root".into(),
111 };
112 assert_eq!(link.to_string(), "42|bash|root");
113 }
114
115 #[test]
116 fn process_link_clone() {
117 let link = ProcessLink {
118 pid: 1,
119 cmd: "init".into(),
120 user: "root".into(),
121 };
122 let link2 = link.clone();
123 assert_eq!(link.pid, link2.pid);
124 assert_eq!(link.cmd, link2.cmd);
125 assert_eq!(link.user, link2.user);
126 }
127
128 #[test]
129 fn proc_event_clone() {
130 let e = ProcEvent::Fork {
131 child_pid: 100,
132 parent_pid: 1,
133 timestamp_ns: 42,
134 };
135 let e2 = e.clone();
136 match e2 {
137 ProcEvent::Fork {
138 child_pid,
139 parent_pid,
140 timestamp_ns,
141 } => {
142 assert_eq!(child_pid, 100);
143 assert_eq!(parent_pid, 1);
144 assert_eq!(timestamp_ns, 42);
145 }
146 _ => panic!("expected Fork"),
147 }
148 }
149}