Skip to main content

tracexec_core/
event.rs

1use std::{
2  collections::BTreeMap,
3  fmt::Debug,
4  sync::{
5    Arc,
6    atomic::AtomicU64,
7  },
8};
9
10use chrono::{
11  DateTime,
12  Local,
13};
14use clap::ValueEnum;
15use crossterm::event::KeyEvent;
16use enumflags2::BitFlags;
17use filterable_enum::FilterableEnum;
18use itertools::Itertools;
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  proc::{
31    CgroupInfo,
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 syscall: ExecSyscall,
150  pub exec_pid: Pid,
151  pub pid: Pid,
152  pub cwd: OutputMsg,
153  pub comm: ArcStr,
154  pub filename: OutputMsg,
155  pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
156  pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
157  /// There are env var(s) whose key starts with dash
158  pub has_dash_env: bool,
159  pub cred: Result<Cred, CredInspectError>,
160  pub interpreter: Option<Vec<Interpreter>>,
161  pub env_diff: Result<EnvDiff, InspectError>,
162  pub fdinfo: Arc<FileDescriptorInfoCollection>,
163  pub result: i64,
164  pub timestamp: Timestamp,
165  pub parent: Option<ParentEventId>,
166  pub cgroup: CgroupInfo,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
170#[strum(serialize_all = "lowercase")]
171pub enum ExecSyscall {
172  Execve,
173  Execveat,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct RuntimeModifier {
178  pub show_env: bool,
179  pub show_cwd: bool,
180}
181
182impl Default for RuntimeModifier {
183  fn default() -> Self {
184    Self {
185      show_env: true,
186      show_cwd: true,
187    }
188  }
189}
190
191impl TracerEventDetails {
192  pub fn into_tracer_msg(self) -> TracerMessage {
193    TracerMessage::Event(self.into())
194  }
195
196  pub fn timestamp(&self) -> Option<Timestamp> {
197    match self {
198      Self::Info(m) | Self::Warning(m) | Self::Error(m) => m.timestamp,
199      Self::Exec(exec_event) => Some(exec_event.timestamp),
200      Self::NewChild { timestamp, .. }
201      | Self::TraceeSpawn { timestamp, .. }
202      | Self::TraceeExit { timestamp, .. } => Some(*timestamp),
203    }
204  }
205}
206
207impl TracerEventDetails {
208  pub fn argv_to_string(argv: &Result<Vec<OutputMsg>, InspectError>) -> String {
209    let Ok(argv) = argv else {
210      return "[failed to read argv]".into();
211    };
212    format!("[{}]", argv.iter().format(", "))
213  }
214
215  pub fn interpreters_to_string(interpreters: &[Interpreter]) -> String {
216    match interpreters {
217      [] => Interpreter::None.to_string(),
218      [interpreter] => interpreter.to_string(),
219      interpreters => format!("[{}]", interpreters.iter().format(", ")),
220    }
221  }
222}
223
224impl FilterableTracerEventDetails {
225  pub fn send_if_match(
226    self,
227    tx: &mpsc::UnboundedSender<TracerMessage>,
228    filter: BitFlags<TracerEventDetailsKind>,
229  ) -> Result<(), mpsc::error::SendError<TracerMessage>> {
230    if let Some(evt) = self.filter_and_take(filter) {
231      tx.send(TracerMessage::from(TracerEvent::from(evt)))?;
232    }
233    Ok(())
234  }
235}
236
237#[macro_export]
238macro_rules! filterable_event {
239    ($($t:tt)*) => {
240      tracexec_core::event::FilterableTracerEventDetails::from(tracexec_core::event::TracerEventDetails::$($t)*)
241    };
242}
243
244pub use filterable_event;
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub enum ProcessStateUpdate {
248  Exit {
249    status: ProcessExit,
250    timestamp: Timestamp,
251  },
252  BreakPointHit(BreakPointHit),
253  Resumed,
254  Detached {
255    hid: u64,
256    timestamp: Timestamp,
257  },
258  ResumeError {
259    hit: BreakPointHit,
260    error: Errno,
261  },
262  DetachError {
263    hit: BreakPointHit,
264    error: Errno,
265  },
266}
267
268impl ProcessStateUpdate {
269  pub fn termination_timestamp(&self) -> Option<Timestamp> {
270    match self {
271      Self::Exit { timestamp, .. } | Self::Detached { timestamp, .. } => Some(*timestamp),
272      _ => None,
273    }
274  }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct ProcessStateUpdateEvent {
279  pub update: ProcessStateUpdate,
280  pub pid: Pid,
281  pub ids: Vec<EventId>,
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum EventStatus {
286  // exec status
287  ExecENOENT,
288  ExecFailure,
289  // process status
290  ProcessRunning,
291  ProcessExitedNormally,
292  ProcessExitedAbnormally(c_int),
293  ProcessPaused,
294  ProcessDetached,
295  // signaled
296  ProcessKilled,
297  ProcessTerminated,
298  ProcessInterrupted,
299  ProcessSegfault,
300  ProcessAborted,
301  ProcessIllegalInstruction,
302  ProcessSignaled(Signal),
303  // internal failure
304  InternalError,
305}
306
307impl From<EventStatus> for &'static str {
308  fn from(value: EventStatus) -> Self {
309    match value {
310      EventStatus::ExecENOENT => "⚠️",
311      EventStatus::ExecFailure => "❌",
312      EventStatus::ProcessRunning => "🟒",
313      EventStatus::ProcessExitedNormally => "πŸ˜‡",
314      EventStatus::ProcessExitedAbnormally(_) => "😑",
315      EventStatus::ProcessKilled => "😡",
316      EventStatus::ProcessTerminated => "🀬",
317      EventStatus::ProcessInterrupted => "πŸ₯Ί",
318      EventStatus::ProcessSegfault => "πŸ’₯",
319      EventStatus::ProcessAborted => "😱",
320      EventStatus::ProcessIllegalInstruction => "πŸ‘Ώ",
321      EventStatus::ProcessSignaled(_) => "πŸ’€",
322      EventStatus::ProcessPaused => "⏸️",
323      EventStatus::ProcessDetached => "πŸ›Έ",
324      EventStatus::InternalError => "β›”",
325    }
326  }
327}
328
329impl std::fmt::Display for EventStatus {
330  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331    let icon: &str = <&'static str>::from(*self);
332    write!(f, "{icon} ")?;
333    use EventStatus::*;
334    match self {
335      ExecENOENT | ExecFailure => write!(
336        f,
337        "Exec failed. Further process state is not available for this event."
338      )?,
339      ProcessRunning => write!(f, "Running")?,
340      ProcessTerminated => write!(f, "Terminated")?,
341      ProcessAborted => write!(f, "Aborted")?,
342      ProcessSegfault => write!(f, "Segmentation fault")?,
343      ProcessIllegalInstruction => write!(f, "Illegal instruction")?,
344      ProcessKilled => write!(f, "Killed")?,
345      ProcessInterrupted => write!(f, "Interrupted")?,
346      ProcessExitedNormally => write!(f, "Exited(0)")?,
347      ProcessExitedAbnormally(code) => write!(f, "Exited({code})")?,
348      ProcessSignaled(signal) => write!(f, "Signaled({signal})")?,
349      ProcessPaused => write!(f, "Paused due to breakpoint hit")?,
350      ProcessDetached => write!(f, "Detached from tracexec")?,
351      InternalError => write!(f, "An internal error occurred in tracexec")?,
352    }
353    Ok(())
354  }
355}
356
357#[cfg(test)]
358mod tests {
359  use std::{
360    collections::BTreeMap,
361    sync::Arc,
362  };
363
364  use chrono::Local;
365  use nix::unistd::Pid;
366  use test_that::prelude::*;
367
368  use super::*;
369  use crate::{
370    cache::ArcStr,
371    timestamp::ts_from_boot_ns,
372  };
373
374  #[test]
375  fn test_event_tracer_message_conversion() {
376    let te = TracerEvent {
377      details: TracerEventDetails::Info(TracerEventMessage {
378        pid: Some(Pid::from_raw(1)),
379        timestamp: Some(Local::now()),
380        msg: "info".into(),
381      }),
382      id: EventId::new(0),
383    };
384
385    let tm: TracerMessage = te.clone().into();
386    match tm {
387      TracerMessage::Event(ev) => assert_eq!(ev, te),
388      _ => panic!("Expected Event variant"),
389    }
390  }
391
392  #[test]
393  fn test_tracer_event_allocate_id_increments() {
394    let id1 = TracerEvent::allocate_id();
395    let id2 = TracerEvent::allocate_id();
396    assert_that!(id2.into_inner(), gt(id1.into_inner()));
397  }
398
399  #[test]
400  fn test_tracer_event_details_timestamp() {
401    let ts = ts_from_boot_ns(100000);
402    let msg = TracerEventMessage {
403      pid: Some(Pid::from_raw(1)),
404      timestamp: Some(Local::now()),
405      msg: "msg".into(),
406    };
407
408    let info_detail = TracerEventDetails::Info(msg.clone());
409    assert_eq!(info_detail.timestamp(), msg.timestamp);
410
411    let exec_event = ExecEvent {
412      syscall: ExecSyscall::Execve,
413      exec_pid: Pid::from_raw(2),
414      pid: Pid::from_raw(2),
415      cwd: OutputMsg::Ok(ArcStr::from("/")),
416      comm: ArcStr::from("comm"),
417      filename: OutputMsg::Ok(ArcStr::from("file")),
418      argv: Arc::new(Ok(vec![])),
419      envp: Arc::new(Ok(BTreeMap::new())),
420      has_dash_env: false,
421      cred: Ok(Default::default()),
422      interpreter: None,
423      env_diff: Ok(EnvDiff::empty()),
424      fdinfo: Arc::new(FileDescriptorInfoCollection::default()),
425      result: 0,
426      timestamp: ts,
427      parent: None,
428      cgroup: CgroupInfo::V2 {
429        path: "/".to_string(),
430      },
431    };
432    let exec_detail = TracerEventDetails::Exec(Box::new(exec_event));
433    assert_eq!(exec_detail.timestamp(), Some(ts));
434  }
435
436  #[test]
437  fn test_argv_to_string() {
438    let argv_ok = Ok(vec![
439      OutputMsg::Ok(ArcStr::from("arg1")),
440      OutputMsg::Ok(ArcStr::from("arg2")),
441    ]);
442    let argv_err: Result<Vec<OutputMsg>, InspectError> = Err(InspectError::EPERM);
443
444    let s = TracerEventDetails::argv_to_string(&argv_ok);
445    assert_that!(s, contains_substring("arg1"));
446    assert_that!(s, contains_substring("arg2"));
447
448    let s_err = TracerEventDetails::argv_to_string(&argv_err);
449    assert_eq!(s_err, "[failed to read argv]");
450  }
451
452  #[test]
453  fn test_interpreters_to_string() {
454    let none: Vec<Interpreter> = vec![];
455    let one: Vec<Interpreter> = vec![Interpreter::None];
456    let many: Vec<Interpreter> = vec![Interpreter::None, Interpreter::None];
457
458    owo_colors::control::set_should_colorize(false);
459
460    let s_none = TracerEventDetails::interpreters_to_string(&none);
461    assert_eq!(s_none, "none");
462
463    let s_one = TracerEventDetails::interpreters_to_string(&one);
464    assert_eq!(s_one, "none");
465
466    let s_many = TracerEventDetails::interpreters_to_string(&many);
467    assert_that!(s_many, contains_substring("none"));
468    assert_that!(s_many, contains_substring(","));
469  }
470
471  #[test]
472  fn test_process_state_update_termination_timestamp() {
473    let ts = ts_from_boot_ns(1000000);
474    let exit = ProcessStateUpdate::Exit {
475      status: ProcessExit::Code(0),
476      timestamp: ts,
477    };
478    let detached = ProcessStateUpdate::Detached {
479      hid: 1,
480      timestamp: ts,
481    };
482    let resumed = ProcessStateUpdate::Resumed;
483
484    assert_eq!(exit.termination_timestamp(), Some(ts));
485    assert_eq!(detached.termination_timestamp(), Some(ts));
486    assert_eq!(resumed.termination_timestamp(), None);
487  }
488
489  #[test]
490  fn test_exec_syscall_display() {
491    assert_eq!(ExecSyscall::Execve.to_string(), "execve");
492    assert_eq!(ExecSyscall::Execveat.to_string(), "execveat");
493  }
494
495  #[test]
496  fn test_event_status_display() {
497    let cases = [
498      (EventStatus::ExecENOENT, "⚠️ Exec failed"),
499      (EventStatus::ProcessRunning, "🟒 Running"),
500      (EventStatus::ProcessExitedNormally, "πŸ˜‡ Exited(0)"),
501      (EventStatus::ProcessSegfault, "πŸ’₯ Segmentation fault"),
502    ];
503
504    for (status, prefix) in cases {
505      let s = format!("{}", status);
506      assert!(s.starts_with(prefix.split_whitespace().next().unwrap()));
507    }
508  }
509}