proc_connector/proc_event.rs
1//! Process event types from the Linux Proc Connector.
2//!
3//! This module contains the `ProcEvent` enum and its `Display` implementation.
4
5use std::fmt;
6
7/// A parsed process event from the Linux Proc Connector.
8///
9/// Each variant corresponds to a `PROC_EVENT_*` constant from
10/// `<linux/cn_proc.h>`, with all relevant fields extracted into
11/// named fields.
12///
13/// The `Unknown` variant provides forward compatibility: if the kernel
14/// emits an event type this version of the library does not know about,
15/// it is returned as `Unknown` with the raw payload.
16///
17/// # Example: pattern matching
18///
19/// ```
20/// use proc_connector::ProcEvent;
21///
22/// fn describe(event: &ProcEvent) -> String {
23/// match event {
24/// ProcEvent::Exec { pid, .. } => format!("process {pid} exec'd"),
25/// ProcEvent::Fork { child_pid, .. } => format!("forked child {child_pid}"),
26/// ProcEvent::Exit { pid, exit_code, .. } => {
27/// format!("process {pid} exited with code {exit_code}")
28/// }
29/// ProcEvent::Uid { pid, ruid, euid, .. } => {
30/// format!("process {pid} uid changed {ruid}->{euid}")
31/// }
32/// ProcEvent::Gid { pid, rgid, egid, .. } => {
33/// format!("process {pid} gid changed {rgid}->{egid}")
34/// }
35/// ProcEvent::Sid { pid, .. } => format!("process {pid} session changed"),
36/// ProcEvent::Ptrace { pid, tracer_pid, .. } => {
37/// format!("process {pid} traced by {tracer_pid}")
38/// }
39/// ProcEvent::Comm { pid, comm, .. } => {
40/// let name = String::from_utf8_lossy(comm);
41/// let name = name.trim_end_matches('\0');
42/// format!("process {pid} renamed to {name}")
43/// }
44/// ProcEvent::Coredump { pid, .. } => format!("process {pid} dumped core"),
45/// ProcEvent::Unknown { what, .. } => format!("unknown event 0x{what:08x}"),
46/// }
47/// }
48///
49/// let exec = ProcEvent::Exec { pid: 42, tgid: 42, timestamp_ns: 0 };
50/// assert_eq!(describe(&exec), "process 42 exec'd");
51///
52/// let exit = ProcEvent::Exit { pid: 7, tgid: 7, exit_code: 0, exit_signal: 17, timestamp_ns: 0 };
53/// assert_eq!(describe(&exit), "process 7 exited with code 0");
54/// ```
55///
56/// # Example: Display formatting
57///
58/// ```
59/// use proc_connector::ProcEvent;
60///
61/// let event = ProcEvent::Fork {
62/// parent_pid: 100,
63/// parent_tgid: 100,
64/// child_pid: 200,
65/// child_tgid: 200,
66/// timestamp_ns: 0,
67/// };
68/// assert_eq!(event.to_string(), "FORK parent=(100,100) child=(200,200) ts=0");
69/// ```
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum ProcEvent {
72 /// A process called `execve(2)`.
73 Exec {
74 pid: u32,
75 tgid: u32,
76 /// Kernel timestamp (nanoseconds since boot).
77 timestamp_ns: u64,
78 },
79 /// A new process was created via `fork`/`clone`.
80 Fork {
81 parent_pid: u32,
82 parent_tgid: u32,
83 child_pid: u32,
84 child_tgid: u32,
85 /// Kernel timestamp (nanoseconds since boot).
86 timestamp_ns: u64,
87 },
88 /// A process exited.
89 Exit {
90 pid: u32,
91 tgid: u32,
92 exit_code: u32,
93 exit_signal: u32,
94 /// Kernel timestamp (nanoseconds since boot).
95 timestamp_ns: u64,
96 },
97 /// Real or effective UID changed.
98 Uid {
99 pid: u32,
100 tgid: u32,
101 ruid: u32,
102 euid: u32,
103 /// Kernel timestamp (nanoseconds since boot).
104 timestamp_ns: u64,
105 },
106 /// Real or effective GID changed.
107 Gid {
108 pid: u32,
109 tgid: u32,
110 rgid: u32,
111 egid: u32,
112 /// Kernel timestamp (nanoseconds since boot).
113 timestamp_ns: u64,
114 },
115 /// Session ID changed (`setsid`).
116 Sid {
117 pid: u32,
118 tgid: u32,
119 /// Kernel timestamp (nanoseconds since boot).
120 timestamp_ns: u64,
121 },
122 /// `ptrace` attach or detach.
123 Ptrace {
124 pid: u32,
125 tgid: u32,
126 tracer_pid: u32,
127 tracer_tgid: u32,
128 /// Kernel timestamp (nanoseconds since boot).
129 timestamp_ns: u64,
130 },
131 /// Process name (`comm`) changed (max 16 bytes, may include trailing NUL).
132 Comm {
133 pid: u32,
134 tgid: u32,
135 /// The new process name (up to 16 bytes, usually NUL-terminated).
136 comm: [u8; 16],
137 /// Kernel timestamp (nanoseconds since boot).
138 timestamp_ns: u64,
139 },
140 /// A core dump occurred.
141 Coredump {
142 pid: u32,
143 tgid: u32,
144 /// Kernel timestamp (nanoseconds since boot).
145 timestamp_ns: u64,
146 },
147 /// An unknown event type (forward-compatibility).
148 Unknown {
149 /// The raw `what` field value.
150 what: u32,
151 /// Raw bytes of the `event_data` union (may be empty).
152 raw_data: Vec<u8>,
153 },
154}
155
156impl fmt::Display for ProcEvent {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 ProcEvent::Exec {
160 pid,
161 tgid,
162 timestamp_ns,
163 } => write!(f, "EXEC pid={pid} tgid={tgid} ts={timestamp_ns}"),
164 ProcEvent::Fork {
165 parent_pid,
166 parent_tgid,
167 child_pid,
168 child_tgid,
169 timestamp_ns,
170 } => write!(
171 f,
172 "FORK parent=({parent_pid},{parent_tgid}) child=({child_pid},{child_tgid}) ts={timestamp_ns}"
173 ),
174 ProcEvent::Exit {
175 pid,
176 tgid,
177 exit_code,
178 exit_signal,
179 timestamp_ns,
180 } => write!(
181 f,
182 "EXIT pid={pid} tgid={tgid} code={exit_code} signal={exit_signal} ts={timestamp_ns}"
183 ),
184 ProcEvent::Uid {
185 pid,
186 tgid,
187 ruid,
188 euid,
189 timestamp_ns,
190 } => write!(
191 f,
192 "UID pid={pid} tgid={tgid} ruid={ruid} euid={euid} ts={timestamp_ns}"
193 ),
194 ProcEvent::Gid {
195 pid,
196 tgid,
197 rgid,
198 egid,
199 timestamp_ns,
200 } => write!(
201 f,
202 "GID pid={pid} tgid={tgid} rgid={rgid} egid={egid} ts={timestamp_ns}"
203 ),
204 ProcEvent::Sid {
205 pid,
206 tgid,
207 timestamp_ns,
208 } => write!(f, "SID pid={pid} tgid={tgid} ts={timestamp_ns}"),
209 ProcEvent::Ptrace {
210 pid,
211 tgid,
212 tracer_pid,
213 tracer_tgid,
214 timestamp_ns,
215 } => write!(
216 f,
217 "PTRACE pid={pid} tgid={tgid} tracer=({tracer_pid},{tracer_tgid}) ts={timestamp_ns}"
218 ),
219 ProcEvent::Comm {
220 pid,
221 tgid,
222 comm,
223 timestamp_ns,
224 } => {
225 // Find NUL terminator for clean display
226 let end = comm.iter().position(|&b| b == 0).unwrap_or(16);
227 let name = std::str::from_utf8(&comm[..end]).unwrap_or("<invalid>");
228 write!(
229 f,
230 "COMM pid={pid} tgid={tgid} name=\"{name}\" ts={timestamp_ns}"
231 )
232 }
233 ProcEvent::Coredump {
234 pid,
235 tgid,
236 timestamp_ns,
237 } => {
238 write!(f, "COREDUMP pid={pid} tgid={tgid} ts={timestamp_ns}")
239 }
240 ProcEvent::Unknown { what, raw_data } => {
241 write!(f, "UNKNOWN what=0x{what:08x} len={}", raw_data.len())
242 }
243 }
244 }
245}