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 { cpu: 0, pid: 42, tgid: 42, timestamp_ns: 0 };
50/// assert_eq!(describe(&exec), "process 42 exec'd");
51///
52/// let exit = ProcEvent::Exit { cpu: 0, pid: 7, tgid: 7, exit_code: 0, exit_signal: 17,
53/// parent_pid: 1, parent_tgid: 1, timestamp_ns: 0 };
54/// assert_eq!(describe(&exit), "process 7 exited with code 0");
55/// ```
56///
57/// # Example: Display formatting
58///
59/// ```
60/// use proc_connector::ProcEvent;
61///
62/// let event = ProcEvent::Fork {
63/// cpu: 0,
64/// parent_pid: 100,
65/// parent_tgid: 100,
66/// child_pid: 200,
67/// child_tgid: 200,
68/// timestamp_ns: 0,
69/// };
70/// assert_eq!(event.to_string(), "FORK parent=(100,100) child=(200,200) ts=0");
71/// ```
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum ProcEvent {
74 /// A process called `execve(2)`.
75 Exec {
76 /// CPU that generated this event.
77 cpu: u32,
78 pid: u32,
79 tgid: u32,
80 /// Kernel timestamp (nanoseconds since boot).
81 timestamp_ns: u64,
82 },
83 /// A new process was created via `fork`/`clone`.
84 Fork {
85 /// CPU that generated this event.
86 cpu: u32,
87 parent_pid: u32,
88 parent_tgid: u32,
89 child_pid: u32,
90 child_tgid: u32,
91 /// Kernel timestamp (nanoseconds since boot).
92 timestamp_ns: u64,
93 },
94 /// A process exited.
95 Exit {
96 /// CPU that generated this event.
97 cpu: u32,
98 pid: u32,
99 tgid: u32,
100 exit_code: u32,
101 exit_signal: u32,
102 parent_pid: u32,
103 parent_tgid: u32,
104 /// Kernel timestamp (nanoseconds since boot).
105 timestamp_ns: u64,
106 },
107 /// Real or effective UID changed.
108 Uid {
109 /// CPU that generated this event.
110 cpu: u32,
111 pid: u32,
112 tgid: u32,
113 ruid: u32,
114 euid: u32,
115 /// Kernel timestamp (nanoseconds since boot).
116 timestamp_ns: u64,
117 },
118 /// Real or effective GID changed.
119 Gid {
120 /// CPU that generated this event.
121 cpu: u32,
122 pid: u32,
123 tgid: u32,
124 rgid: u32,
125 egid: u32,
126 /// Kernel timestamp (nanoseconds since boot).
127 timestamp_ns: u64,
128 },
129 /// Session ID changed (`setsid`).
130 Sid {
131 /// CPU that generated this event.
132 cpu: u32,
133 pid: u32,
134 tgid: u32,
135 /// Kernel timestamp (nanoseconds since boot).
136 timestamp_ns: u64,
137 },
138 /// `ptrace` attach or detach.
139 Ptrace {
140 /// CPU that generated this event.
141 cpu: u32,
142 pid: u32,
143 tgid: u32,
144 tracer_pid: u32,
145 tracer_tgid: u32,
146 /// Kernel timestamp (nanoseconds since boot).
147 timestamp_ns: u64,
148 },
149 /// Process name (`comm`) changed (max 16 bytes, may include trailing NUL).
150 Comm {
151 /// CPU that generated this event.
152 cpu: u32,
153 pid: u32,
154 tgid: u32,
155 /// The new process name (up to 16 bytes, usually NUL-terminated).
156 comm: [u8; 16],
157 /// Kernel timestamp (nanoseconds since boot).
158 timestamp_ns: u64,
159 },
160 /// A core dump occurred.
161 Coredump {
162 /// CPU that generated this event.
163 cpu: u32,
164 pid: u32,
165 tgid: u32,
166 parent_pid: u32,
167 parent_tgid: u32,
168 /// Kernel timestamp (nanoseconds since boot).
169 timestamp_ns: u64,
170 },
171 /// An unknown event type (forward-compatibility).
172 Unknown {
173 /// The raw `what` field value.
174 what: u32,
175 /// Raw bytes of the `event_data` union (may be empty).
176 raw_data: Vec<u8>,
177 },
178}
179
180impl ProcEvent {
181 /// Extract the exit status from an `Exit` event's `exit_code` field.
182 ///
183 /// Returns the value that would be returned by `WEXITSTATUS(exit_code)`,
184 /// i.e. the low 8 bits of the exit code. Returns `None` if the process
185 /// was terminated by a signal.
186 ///
187 /// # Example
188 ///
189 /// ```
190 /// use proc_connector::ProcEvent;
191 /// let e = ProcEvent::Exit { cpu: 0, pid:1, tgid:1, exit_code: (1 << 8), exit_signal:0,
192 /// parent_pid:0, parent_tgid:0, timestamp_ns:0 };
193 /// assert_eq!(e.exit_status(), Some(1));
194 /// ```
195 pub fn exit_status(&self) -> Option<i32> {
196 if let ProcEvent::Exit {
197 exit_code,
198 exit_signal,
199 ..
200 } = self
201 {
202 if *exit_signal == 0 {
203 Some(((exit_code >> 8) & 0xFF) as i32)
204 } else {
205 None
206 }
207 } else {
208 None
209 }
210 }
211
212 /// Extract the terminating signal from an `Exit` event's `exit_code` field.
213 ///
214 /// Returns the signal number that caused the process to terminate,
215 /// i.e. `WTERMSIG(exit_code)`. Returns `None` if the process exited
216 /// normally (not by signal).
217 pub fn terminating_signal(&self) -> Option<i32> {
218 if let ProcEvent::Exit {
219 exit_code,
220 exit_signal,
221 ..
222 } = self
223 {
224 if *exit_signal != 0 || (*exit_code & 0x7F) != 0 {
225 Some((exit_code & 0x7F) as i32)
226 } else {
227 None
228 }
229 } else {
230 None
231 }
232 }
233}
234
235impl fmt::Display for ProcEvent {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 match self {
238 ProcEvent::Exec {
239 pid,
240 tgid,
241 timestamp_ns,
242 ..
243 } => write!(f, "EXEC pid={pid} tgid={tgid} ts={timestamp_ns}"),
244 ProcEvent::Fork {
245 parent_pid,
246 parent_tgid,
247 child_pid,
248 child_tgid,
249 timestamp_ns,
250 ..
251 } => write!(
252 f,
253 "FORK parent=({parent_pid},{parent_tgid}) child=({child_pid},{child_tgid}) ts={timestamp_ns}"
254 ),
255 ProcEvent::Exit {
256 pid,
257 tgid,
258 exit_code,
259 exit_signal,
260 parent_pid,
261 parent_tgid,
262 timestamp_ns,
263 ..
264 } => write!(
265 f,
266 "EXIT pid={pid} tgid={tgid} code={exit_code} signal={exit_signal} parent=({parent_pid},{parent_tgid}) ts={timestamp_ns}"
267 ),
268 ProcEvent::Uid {
269 pid,
270 tgid,
271 ruid,
272 euid,
273 timestamp_ns,
274 ..
275 } => write!(
276 f,
277 "UID pid={pid} tgid={tgid} ruid={ruid} euid={euid} ts={timestamp_ns}"
278 ),
279 ProcEvent::Gid {
280 pid,
281 tgid,
282 rgid,
283 egid,
284 timestamp_ns,
285 ..
286 } => write!(
287 f,
288 "GID pid={pid} tgid={tgid} rgid={rgid} egid={egid} ts={timestamp_ns}"
289 ),
290 ProcEvent::Sid {
291 pid,
292 tgid,
293 timestamp_ns,
294 ..
295 } => write!(f, "SID pid={pid} tgid={tgid} ts={timestamp_ns}"),
296 ProcEvent::Ptrace {
297 pid,
298 tgid,
299 tracer_pid,
300 tracer_tgid,
301 timestamp_ns,
302 ..
303 } => write!(
304 f,
305 "PTRACE pid={pid} tgid={tgid} tracer=({tracer_pid},{tracer_tgid}) ts={timestamp_ns}"
306 ),
307 ProcEvent::Comm {
308 pid,
309 tgid,
310 comm,
311 timestamp_ns,
312 ..
313 } => {
314 let end = comm.iter().position(|&b| b == 0).unwrap_or(16);
315 let name = std::str::from_utf8(&comm[..end]).unwrap_or("<invalid>");
316 write!(
317 f,
318 "COMM pid={pid} tgid={tgid} name=\"{name}\" ts={timestamp_ns}"
319 )
320 }
321 ProcEvent::Coredump {
322 pid,
323 tgid,
324 parent_pid,
325 parent_tgid,
326 timestamp_ns,
327 ..
328 } => {
329 write!(
330 f,
331 "COREDUMP pid={pid} tgid={tgid} parent=({parent_pid},{parent_tgid}) ts={timestamp_ns}"
332 )
333 }
334 ProcEvent::Unknown { what, raw_data } => {
335 write!(f, "UNKNOWN what=0x{what:08x} len={}", raw_data.len())
336 }
337 }
338 }
339}