tracexec_core/
event.rs

1use std::{
2  collections::BTreeMap,
3  fmt::Debug,
4  io::Write,
5  sync::{Arc, atomic::AtomicU64},
6};
7
8use crate::{
9  cache::ArcStr,
10  printer::ListPrinter,
11  proc::{Cred, CredInspectError},
12  timestamp::Timestamp,
13};
14use chrono::{DateTime, Local};
15use clap::ValueEnum;
16use crossterm::event::KeyEvent;
17use enumflags2::BitFlags;
18use filterable_enum::FilterableEnum;
19use nix::{errno::Errno, libc::c_int, unistd::Pid};
20use strum::Display;
21use tokio::sync::mpsc;
22
23use crate::{
24  breakpoint::BreakPointHit,
25  proc::{EnvDiff, FileDescriptorInfoCollection, Interpreter},
26  tracer::ProcessExit,
27  tracer::{InspectError, Signal},
28};
29
30mod id;
31mod message;
32mod parent;
33pub use id::*;
34pub use message::*;
35pub use parent::*;
36
37#[derive(Debug, Clone, Display, PartialEq, Eq)]
38pub enum Event {
39  ShouldQuit,
40  Key(KeyEvent),
41  Tracer(TracerMessage),
42  Render,
43  Resize { width: u16, height: u16 },
44  Init,
45  Error,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum TracerMessage {
50  /// A tracer event is an event that could show in the logs or event list
51  Event(TracerEvent),
52  /// A state update is any event that doesn't need to show in logs or having
53  /// its own line in event list.
54  StateUpdate(ProcessStateUpdateEvent),
55  FatalError(String),
56}
57
58impl From<TracerEvent> for TracerMessage {
59  fn from(event: TracerEvent) -> Self {
60    Self::Event(event)
61  }
62}
63
64impl From<ProcessStateUpdateEvent> for TracerMessage {
65  fn from(update: ProcessStateUpdateEvent) -> Self {
66    Self::StateUpdate(update)
67  }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct TracerEvent {
72  pub details: TracerEventDetails,
73  pub id: EventId,
74}
75
76/// A global counter for events, though it should only be used by the tracer thread.
77static ID: AtomicU64 = AtomicU64::new(0);
78
79impl TracerEvent {
80  pub fn allocate_id() -> EventId {
81    EventId::new(ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
82  }
83}
84
85impl From<TracerEventDetails> for TracerEvent {
86  fn from(details: TracerEventDetails) -> Self {
87    Self {
88      details,
89      id: Self::allocate_id(),
90    }
91  }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, FilterableEnum)]
95#[filterable_enum(kind_extra_derive=ValueEnum, kind_extra_derive=Display, kind_extra_attrs="strum(serialize_all = \"kebab-case\")")]
96pub enum TracerEventDetails {
97  Info(TracerEventMessage),
98  Warning(TracerEventMessage),
99  Error(TracerEventMessage),
100  NewChild {
101    timestamp: Timestamp,
102    ppid: Pid,
103    pcomm: ArcStr,
104    pid: Pid,
105  },
106  Exec(Box<ExecEvent>),
107  TraceeSpawn {
108    pid: Pid,
109    timestamp: Timestamp,
110  },
111  TraceeExit {
112    timestamp: Timestamp,
113    signal: Option<Signal>,
114    exit_code: i32,
115  },
116}
117
118impl TracerEventDetails {
119  pub fn into_event_with_id(self, id: EventId) -> TracerEvent {
120    TracerEvent { details: self, id }
121  }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct TracerEventMessage {
126  pub pid: Option<Pid>,
127  pub timestamp: Option<DateTime<Local>>,
128  pub msg: String,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct ExecEvent {
133  pub pid: Pid,
134  pub cwd: OutputMsg,
135  pub comm: ArcStr,
136  pub filename: OutputMsg,
137  pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
138  pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
139  /// There are env var(s) whose key starts with dash
140  pub has_dash_env: bool,
141  pub cred: Result<Cred, CredInspectError>,
142  pub interpreter: Option<Vec<Interpreter>>,
143  pub env_diff: Result<EnvDiff, InspectError>,
144  pub fdinfo: Arc<FileDescriptorInfoCollection>,
145  pub result: i64,
146  pub timestamp: Timestamp,
147  pub parent: Option<ParentEventId>,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct RuntimeModifier {
152  pub show_env: bool,
153  pub show_cwd: bool,
154}
155
156impl Default for RuntimeModifier {
157  fn default() -> Self {
158    Self {
159      show_env: true,
160      show_cwd: true,
161    }
162  }
163}
164
165impl TracerEventDetails {
166  pub fn into_tracer_msg(self) -> TracerMessage {
167    TracerMessage::Event(self.into())
168  }
169
170  pub fn timestamp(&self) -> Option<Timestamp> {
171    match self {
172      Self::Info(m) | Self::Warning(m) | Self::Error(m) => m.timestamp,
173      Self::Exec(exec_event) => Some(exec_event.timestamp),
174      Self::NewChild { timestamp, .. }
175      | Self::TraceeSpawn { timestamp, .. }
176      | Self::TraceeExit { timestamp, .. } => Some(*timestamp),
177    }
178  }
179}
180
181impl TracerEventDetails {
182  pub fn argv_to_string(argv: &Result<Vec<OutputMsg>, InspectError>) -> String {
183    let Ok(argv) = argv else {
184      return "[failed to read argv]".into();
185    };
186    let mut result =
187      Vec::with_capacity(argv.iter().map(|s| s.as_ref().len() + 3).sum::<usize>() + 2);
188    let list_printer = ListPrinter::new(crate::printer::ColorLevel::Less);
189    list_printer.print_string_list(&mut result, argv).unwrap();
190    // SAFETY: argv is printed in debug format, which is always UTF-8
191    unsafe { String::from_utf8_unchecked(result) }
192  }
193
194  pub fn interpreters_to_string(interpreters: &[Interpreter]) -> String {
195    let mut result = Vec::new();
196    let list_printer = ListPrinter::new(crate::printer::ColorLevel::Less);
197    match interpreters.len() {
198      0 => {
199        write!(result, "{}", Interpreter::None).unwrap();
200      }
201      1 => {
202        write!(result, "{}", interpreters[0]).unwrap();
203      }
204      _ => {
205        list_printer.begin(&mut result).unwrap();
206        for (idx, interpreter) in interpreters.iter().enumerate() {
207          if idx != 0 {
208            list_printer.comma(&mut result).unwrap();
209          }
210          write!(result, "{interpreter}").unwrap();
211        }
212        list_printer.end(&mut result).unwrap();
213      }
214    }
215    // SAFETY: interpreters is printed in debug format, which is always UTF-8
216    unsafe { String::from_utf8_unchecked(result) }
217  }
218}
219
220impl FilterableTracerEventDetails {
221  pub fn send_if_match(
222    self,
223    tx: &mpsc::UnboundedSender<TracerMessage>,
224    filter: BitFlags<TracerEventDetailsKind>,
225  ) -> Result<(), mpsc::error::SendError<TracerMessage>> {
226    if let Some(evt) = self.filter_and_take(filter) {
227      tx.send(TracerMessage::from(TracerEvent::from(evt)))?;
228    }
229    Ok(())
230  }
231}
232
233#[macro_export]
234macro_rules! filterable_event {
235    ($($t:tt)*) => {
236      tracexec_core::event::FilterableTracerEventDetails::from(tracexec_core::event::TracerEventDetails::$($t)*)
237    };
238}
239
240pub use filterable_event;
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum ProcessStateUpdate {
244  Exit {
245    status: ProcessExit,
246    timestamp: Timestamp,
247  },
248  BreakPointHit(BreakPointHit),
249  Resumed,
250  Detached {
251    hid: u64,
252    timestamp: Timestamp,
253  },
254  ResumeError {
255    hit: BreakPointHit,
256    error: Errno,
257  },
258  DetachError {
259    hit: BreakPointHit,
260    error: Errno,
261  },
262}
263
264impl ProcessStateUpdate {
265  pub fn termination_timestamp(&self) -> Option<Timestamp> {
266    match self {
267      Self::Exit { timestamp, .. } | Self::Detached { timestamp, .. } => Some(*timestamp),
268      _ => None,
269    }
270  }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct ProcessStateUpdateEvent {
275  pub update: ProcessStateUpdate,
276  pub pid: Pid,
277  pub ids: Vec<EventId>,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum EventStatus {
282  // exec status
283  ExecENOENT,
284  ExecFailure,
285  // process status
286  ProcessRunning,
287  ProcessExitedNormally,
288  ProcessExitedAbnormally(c_int),
289  ProcessPaused,
290  ProcessDetached,
291  // signaled
292  ProcessKilled,
293  ProcessTerminated,
294  ProcessInterrupted,
295  ProcessSegfault,
296  ProcessAborted,
297  ProcessIllegalInstruction,
298  ProcessSignaled(Signal),
299  // internal failure
300  InternalError,
301}
302
303impl From<EventStatus> for &'static str {
304  fn from(value: EventStatus) -> Self {
305    match value {
306      EventStatus::ExecENOENT => "⚠️",
307      EventStatus::ExecFailure => "❌",
308      EventStatus::ProcessRunning => "🟒",
309      EventStatus::ProcessExitedNormally => "πŸ˜‡",
310      EventStatus::ProcessExitedAbnormally(_) => "😑",
311      EventStatus::ProcessKilled => "😡",
312      EventStatus::ProcessTerminated => "🀬",
313      EventStatus::ProcessInterrupted => "πŸ₯Ί",
314      EventStatus::ProcessSegfault => "πŸ’₯",
315      EventStatus::ProcessAborted => "😱",
316      EventStatus::ProcessIllegalInstruction => "πŸ‘Ώ",
317      EventStatus::ProcessSignaled(_) => "πŸ’€",
318      EventStatus::ProcessPaused => "⏸️",
319      EventStatus::ProcessDetached => "πŸ›Έ",
320      EventStatus::InternalError => "β›”",
321    }
322  }
323}
324
325impl std::fmt::Display for EventStatus {
326  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327    let icon: &str = <&'static str>::from(*self);
328    write!(f, "{icon} ")?;
329    use EventStatus::*;
330    match self {
331      ExecENOENT | ExecFailure => write!(
332        f,
333        "Exec failed. Further process state is not available for this event."
334      )?,
335      ProcessRunning => write!(f, "Running")?,
336      ProcessTerminated => write!(f, "Terminated")?,
337      ProcessAborted => write!(f, "Aborted")?,
338      ProcessSegfault => write!(f, "Segmentation fault")?,
339      ProcessIllegalInstruction => write!(f, "Illegal instruction")?,
340      ProcessKilled => write!(f, "Killed")?,
341      ProcessInterrupted => write!(f, "Interrupted")?,
342      ProcessExitedNormally => write!(f, "Exited(0)")?,
343      ProcessExitedAbnormally(code) => write!(f, "Exited({code})")?,
344      ProcessSignaled(signal) => write!(f, "Signaled({signal})")?,
345      ProcessPaused => write!(f, "Paused due to breakpoint hit")?,
346      ProcessDetached => write!(f, "Detached from tracexec")?,
347      InternalError => write!(f, "An internal error occurred in tracexec")?,
348    }
349    Ok(())
350  }
351}