Skip to main content

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}