tracexec_core/
event.rs

1use std::{
2  collections::BTreeMap,
3  fmt::Debug,
4  io::Write,
5  sync::{
6    Arc,
7    atomic::AtomicU64,
8  },
9};
10
11use chrono::{
12  DateTime,
13  Local,
14};
15use clap::ValueEnum;
16use crossterm::event::KeyEvent;
17use enumflags2::BitFlags;
18use filterable_enum::FilterableEnum;
19use nix::{
20  errno::Errno,
21  libc::c_int,
22  unistd::Pid,
23};
24use strum::Display;
25use tokio::sync::mpsc;
26
27use crate::{
28  breakpoint::BreakPointHit,
29  cache::ArcStr,
30  printer::ListPrinter,
31  proc::{
32    Cred,
33    CredInspectError,
34    EnvDiff,
35    FileDescriptorInfoCollection,
36    Interpreter,
37  },
38  timestamp::Timestamp,
39  tracer::{
40    InspectError,
41    ProcessExit,
42    Signal,
43  },
44};
45
46mod id;
47mod message;
48mod parent;
49pub use id::*;
50pub use message::*;
51pub use parent::*;
52
53#[derive(Debug, Clone, Display, PartialEq, Eq)]
54pub enum Event {
55  ShouldQuit,
56  Key(KeyEvent),
57  Tracer(TracerMessage),
58  Render,
59  Resize { width: u16, height: u16 },
60  Init,
61  Error,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum TracerMessage {
66  /// A tracer event is an event that could show in the logs or event list
67  Event(TracerEvent),
68  /// A state update is any event that doesn't need to show in logs or having
69  /// its own line in event list.
70  StateUpdate(ProcessStateUpdateEvent),
71  FatalError(String),
72}
73
74impl From<TracerEvent> for TracerMessage {
75  fn from(event: TracerEvent) -> Self {
76    Self::Event(event)
77  }
78}
79
80impl From<ProcessStateUpdateEvent> for TracerMessage {
81  fn from(update: ProcessStateUpdateEvent) -> Self {
82    Self::StateUpdate(update)
83  }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct TracerEvent {
88  pub details: TracerEventDetails,
89  pub id: EventId,
90}
91
92/// A global counter for events, though it should only be used by the tracer thread.
93static ID: AtomicU64 = AtomicU64::new(0);
94
95impl TracerEvent {
96  pub fn allocate_id() -> EventId {
97    EventId::new(ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
98  }
99}
100
101impl From<TracerEventDetails> for TracerEvent {
102  fn from(details: TracerEventDetails) -> Self {
103    Self {
104      details,
105      id: Self::allocate_id(),
106    }
107  }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, FilterableEnum)]
111#[filterable_enum(kind_extra_derive=ValueEnum, kind_extra_derive=Display, kind_extra_attrs="strum(serialize_all = \"kebab-case\")")]
112pub enum TracerEventDetails {
113  Info(TracerEventMessage),
114  Warning(TracerEventMessage),
115  Error(TracerEventMessage),
116  NewChild {
117    timestamp: Timestamp,
118    ppid: Pid,
119    pcomm: ArcStr,
120    pid: Pid,
121  },
122  Exec(Box<ExecEvent>),
123  TraceeSpawn {
124    pid: Pid,
125    timestamp: Timestamp,
126  },
127  TraceeExit {
128    timestamp: Timestamp,
129    signal: Option<Signal>,
130    exit_code: i32,
131  },
132}
133
134impl TracerEventDetails {
135  pub fn into_event_with_id(self, id: EventId) -> TracerEvent {
136    TracerEvent { details: self, id }
137  }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct TracerEventMessage {
142  pub pid: Option<Pid>,
143  pub timestamp: Option<DateTime<Local>>,
144  pub msg: String,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct ExecEvent {
149  pub pid: Pid,
150  pub cwd: OutputMsg,
151  pub comm: ArcStr,
152  pub filename: OutputMsg,
153  pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
154  pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
155  /// There are env var(s) whose key starts with dash
156  pub has_dash_env: bool,
157  pub cred: Result<Cred, CredInspectError>,
158  pub interpreter: Option<Vec<Interpreter>>,
159  pub env_diff: Result<EnvDiff, InspectError>,
160  pub fdinfo: Arc<FileDescriptorInfoCollection>,
161  pub result: i64,
162  pub timestamp: Timestamp,
163  pub parent: Option<ParentEventId>,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub struct RuntimeModifier {
168  pub show_env: bool,
169  pub show_cwd: bool,
170}
171
172impl Default for RuntimeModifier {
173  fn default() -> Self {
174    Self {
175      show_env: true,
176      show_cwd: true,
177    }
178  }
179}
180
181impl TracerEventDetails {
182  pub fn into_tracer_msg(self) -> TracerMessage {
183    TracerMessage::Event(self.into())
184  }
185
186  pub fn timestamp(&self) -> Option<Timestamp> {
187    match self {
188      Self::Info(m) | Self::Warning(m) | Self::Error(m) => m.timestamp,
189      Self::Exec(exec_event) => Some(exec_event.timestamp),
190      Self::NewChild { timestamp, .. }
191      | Self::TraceeSpawn { timestamp, .. }
192      | Self::TraceeExit { timestamp, .. } => Some(*timestamp),
193    }
194  }
195}
196
197impl TracerEventDetails {
198  pub fn argv_to_string(argv: &Result<Vec<OutputMsg>, InspectError>) -> String {
199    let Ok(argv) = argv else {
200      return "[failed to read argv]".into();
201    };
202    let mut result =
203      Vec::with_capacity(argv.iter().map(|s| s.as_ref().len() + 3).sum::<usize>() + 2);
204    let list_printer = ListPrinter::new(crate::printer::ColorLevel::Less);
205    list_printer.print_string_list(&mut result, argv).unwrap();
206    // SAFETY: argv is printed in debug format, which is always UTF-8
207    unsafe { String::from_utf8_unchecked(result) }
208  }
209
210  pub fn interpreters_to_string(interpreters: &[Interpreter]) -> String {
211    let mut result = Vec::new();
212    let list_printer = ListPrinter::new(crate::printer::ColorLevel::Less);
213    match interpreters.len() {
214      0 => {
215        write!(result, "{}", Interpreter::None).unwrap();
216      }
217      1 => {
218        write!(result, "{}", interpreters[0]).unwrap();
219      }
220      _ => {
221        list_printer.begin(&mut result).unwrap();
222        for (idx, interpreter) in interpreters.iter().enumerate() {
223          if idx != 0 {
224            list_printer.comma(&mut result).unwrap();
225          }
226          write!(result, "{interpreter}").unwrap();
227        }
228        list_printer.end(&mut result).unwrap();
229      }
230    }
231    // SAFETY: interpreters is printed in debug format, which is always UTF-8
232    unsafe { String::from_utf8_unchecked(result) }
233  }
234}
235
236impl FilterableTracerEventDetails {
237  pub fn send_if_match(
238    self,
239    tx: &mpsc::UnboundedSender<TracerMessage>,
240    filter: BitFlags<TracerEventDetailsKind>,
241  ) -> Result<(), mpsc::error::SendError<TracerMessage>> {
242    if let Some(evt) = self.filter_and_take(filter) {
243      tx.send(TracerMessage::from(TracerEvent::from(evt)))?;
244    }
245    Ok(())
246  }
247}
248
249#[macro_export]
250macro_rules! filterable_event {
251    ($($t:tt)*) => {
252      tracexec_core::event::FilterableTracerEventDetails::from(tracexec_core::event::TracerEventDetails::$($t)*)
253    };
254}
255
256pub use filterable_event;
257
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub enum ProcessStateUpdate {
260  Exit {
261    status: ProcessExit,
262    timestamp: Timestamp,
263  },
264  BreakPointHit(BreakPointHit),
265  Resumed,
266  Detached {
267    hid: u64,
268    timestamp: Timestamp,
269  },
270  ResumeError {
271    hit: BreakPointHit,
272    error: Errno,
273  },
274  DetachError {
275    hit: BreakPointHit,
276    error: Errno,
277  },
278}
279
280impl ProcessStateUpdate {
281  pub fn termination_timestamp(&self) -> Option<Timestamp> {
282    match self {
283      Self::Exit { timestamp, .. } | Self::Detached { timestamp, .. } => Some(*timestamp),
284      _ => None,
285    }
286  }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct ProcessStateUpdateEvent {
291  pub update: ProcessStateUpdate,
292  pub pid: Pid,
293  pub ids: Vec<EventId>,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum EventStatus {
298  // exec status
299  ExecENOENT,
300  ExecFailure,
301  // process status
302  ProcessRunning,
303  ProcessExitedNormally,
304  ProcessExitedAbnormally(c_int),
305  ProcessPaused,
306  ProcessDetached,
307  // signaled
308  ProcessKilled,
309  ProcessTerminated,
310  ProcessInterrupted,
311  ProcessSegfault,
312  ProcessAborted,
313  ProcessIllegalInstruction,
314  ProcessSignaled(Signal),
315  // internal failure
316  InternalError,
317}
318
319impl From<EventStatus> for &'static str {
320  fn from(value: EventStatus) -> Self {
321    match value {
322      EventStatus::ExecENOENT => "⚠️",
323      EventStatus::ExecFailure => "❌",
324      EventStatus::ProcessRunning => "🟒",
325      EventStatus::ProcessExitedNormally => "πŸ˜‡",
326      EventStatus::ProcessExitedAbnormally(_) => "😑",
327      EventStatus::ProcessKilled => "😡",
328      EventStatus::ProcessTerminated => "🀬",
329      EventStatus::ProcessInterrupted => "πŸ₯Ί",
330      EventStatus::ProcessSegfault => "πŸ’₯",
331      EventStatus::ProcessAborted => "😱",
332      EventStatus::ProcessIllegalInstruction => "πŸ‘Ώ",
333      EventStatus::ProcessSignaled(_) => "πŸ’€",
334      EventStatus::ProcessPaused => "⏸️",
335      EventStatus::ProcessDetached => "πŸ›Έ",
336      EventStatus::InternalError => "β›”",
337    }
338  }
339}
340
341impl std::fmt::Display for EventStatus {
342  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343    let icon: &str = <&'static str>::from(*self);
344    write!(f, "{icon} ")?;
345    use EventStatus::*;
346    match self {
347      ExecENOENT | ExecFailure => write!(
348        f,
349        "Exec failed. Further process state is not available for this event."
350      )?,
351      ProcessRunning => write!(f, "Running")?,
352      ProcessTerminated => write!(f, "Terminated")?,
353      ProcessAborted => write!(f, "Aborted")?,
354      ProcessSegfault => write!(f, "Segmentation fault")?,
355      ProcessIllegalInstruction => write!(f, "Illegal instruction")?,
356      ProcessKilled => write!(f, "Killed")?,
357      ProcessInterrupted => write!(f, "Interrupted")?,
358      ProcessExitedNormally => write!(f, "Exited(0)")?,
359      ProcessExitedAbnormally(code) => write!(f, "Exited({code})")?,
360      ProcessSignaled(signal) => write!(f, "Signaled({signal})")?,
361      ProcessPaused => write!(f, "Paused due to breakpoint hit")?,
362      ProcessDetached => write!(f, "Detached from tracexec")?,
363      InternalError => write!(f, "An internal error occurred in tracexec")?,
364    }
365    Ok(())
366  }
367}
368
369#[cfg(test)]
370mod tests {
371  use std::{
372    collections::BTreeMap,
373    sync::Arc,
374  };
375
376  use chrono::Local;
377  use nix::unistd::Pid;
378
379  use super::*;
380  use crate::{
381    cache::ArcStr,
382    timestamp::ts_from_boot_ns,
383  };
384
385  #[test]
386  fn test_event_tracer_message_conversion() {
387    let te = TracerEvent {
388      details: TracerEventDetails::Info(TracerEventMessage {
389        pid: Some(Pid::from_raw(1)),
390        timestamp: Some(Local::now()),
391        msg: "info".into(),
392      }),
393      id: EventId::new(0),
394    };
395
396    let tm: TracerMessage = te.clone().into();
397    match tm {
398      TracerMessage::Event(ev) => assert_eq!(ev, te),
399      _ => panic!("Expected Event variant"),
400    }
401  }
402
403  #[test]
404  fn test_tracer_event_allocate_id_increments() {
405    let id1 = TracerEvent::allocate_id();
406    let id2 = TracerEvent::allocate_id();
407    assert!(id2.into_inner() > id1.into_inner());
408  }
409
410  #[test]
411  fn test_tracer_event_details_timestamp() {
412    let ts = ts_from_boot_ns(100000);
413    let msg = TracerEventMessage {
414      pid: Some(Pid::from_raw(1)),
415      timestamp: Some(Local::now()),
416      msg: "msg".into(),
417    };
418
419    let info_detail = TracerEventDetails::Info(msg.clone());
420    assert_eq!(info_detail.timestamp(), msg.timestamp);
421
422    let exec_event = ExecEvent {
423      pid: Pid::from_raw(2),
424      cwd: OutputMsg::Ok(ArcStr::from("/")),
425      comm: ArcStr::from("comm"),
426      filename: OutputMsg::Ok(ArcStr::from("file")),
427      argv: Arc::new(Ok(vec![])),
428      envp: Arc::new(Ok(BTreeMap::new())),
429      has_dash_env: false,
430      cred: Ok(Default::default()),
431      interpreter: None,
432      env_diff: Ok(EnvDiff::empty()),
433      fdinfo: Arc::new(FileDescriptorInfoCollection::default()),
434      result: 0,
435      timestamp: ts,
436      parent: None,
437    };
438    let exec_detail = TracerEventDetails::Exec(Box::new(exec_event.clone()));
439    assert_eq!(exec_detail.timestamp(), Some(ts));
440  }
441
442  #[test]
443  fn test_argv_to_string() {
444    let argv_ok = Ok(vec![
445      OutputMsg::Ok(ArcStr::from("arg1")),
446      OutputMsg::Ok(ArcStr::from("arg2")),
447    ]);
448    let argv_err: Result<Vec<OutputMsg>, InspectError> = Err(InspectError::EPERM);
449
450    let s = TracerEventDetails::argv_to_string(&argv_ok);
451    assert!(s.contains("arg1") && s.contains("arg2"));
452
453    let s_err = TracerEventDetails::argv_to_string(&argv_err);
454    assert_eq!(s_err, "[failed to read argv]");
455  }
456
457  #[test]
458  fn test_interpreters_to_string() {
459    let none: Vec<Interpreter> = vec![];
460    let one: Vec<Interpreter> = vec![Interpreter::None];
461    let many: Vec<Interpreter> = vec![Interpreter::None, Interpreter::None];
462
463    owo_colors::control::set_should_colorize(false);
464
465    let s_none = TracerEventDetails::interpreters_to_string(&none);
466    assert_eq!(s_none, "none");
467
468    let s_one = TracerEventDetails::interpreters_to_string(&one);
469    assert_eq!(s_one, "none");
470
471    let s_many = TracerEventDetails::interpreters_to_string(&many);
472    assert!(s_many.contains("none") && s_many.contains(","));
473  }
474
475  #[test]
476  fn test_process_state_update_termination_timestamp() {
477    let ts = ts_from_boot_ns(1000000);
478    let exit = ProcessStateUpdate::Exit {
479      status: ProcessExit::Code(0),
480      timestamp: ts,
481    };
482    let detached = ProcessStateUpdate::Detached {
483      hid: 1,
484      timestamp: ts,
485    };
486    let resumed = ProcessStateUpdate::Resumed;
487
488    assert_eq!(exit.termination_timestamp(), Some(ts));
489    assert_eq!(detached.termination_timestamp(), Some(ts));
490    assert_eq!(resumed.termination_timestamp(), None);
491  }
492
493  #[test]
494  fn test_event_status_display() {
495    let cases = [
496      (EventStatus::ExecENOENT, "⚠️ Exec failed"),
497      (EventStatus::ProcessRunning, "🟒 Running"),
498      (EventStatus::ProcessExitedNormally, "πŸ˜‡ Exited(0)"),
499      (EventStatus::ProcessSegfault, "πŸ’₯ Segmentation fault"),
500    ];
501
502    for (status, prefix) in cases {
503      let s = format!("{}", status);
504      assert!(s.starts_with(prefix.split_whitespace().next().unwrap()));
505    }
506  }
507}