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 Event(TracerEvent),
68 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
92static 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 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 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 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 ExecENOENT,
300 ExecFailure,
301 ProcessRunning,
303 ProcessExitedNormally,
304 ProcessExitedAbnormally(c_int),
305 ProcessPaused,
306 ProcessDetached,
307 ProcessKilled,
309 ProcessTerminated,
310 ProcessInterrupted,
311 ProcessSegfault,
312 ProcessAborted,
313 ProcessIllegalInstruction,
314 ProcessSignaled(Signal),
315 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}