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 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 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 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 ExecENOENT,
288 ExecFailure,
289 ProcessRunning,
291 ProcessExitedNormally,
292 ProcessExitedAbnormally(c_int),
293 ProcessPaused,
294 ProcessDetached,
295 ProcessKilled,
297 ProcessTerminated,
298 ProcessInterrupted,
299 ProcessSegfault,
300 ProcessAborted,
301 ProcessIllegalInstruction,
302 ProcessSignaled(Signal),
303 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}