1use std::{
2 collections::BTreeMap,
3 fmt::Display,
4 sync::Arc,
5};
6
7use chrono::{
8 DateTime,
9 Local,
10};
11use enumflags2::BitFlags;
12use nix::{
13 errno::Errno,
14 libc::{
15 SIGRTMIN,
16 c_int,
17 },
18 unistd::User,
19};
20use tokio::sync::mpsc::UnboundedSender;
21
22use crate::{
23 cli::{
24 args::{
25 LogModeArgs,
26 ModifierArgs,
27 },
28 options::SeccompBpf,
29 },
30 event::{
31 OutputMsg,
32 TracerEventDetailsKind,
33 TracerMessage,
34 },
35 printer::{
36 Printer,
37 PrinterArgs,
38 },
39 proc::{
40 BaselineInfo,
41 Cred,
42 CredInspectError,
43 FileDescriptorInfoCollection,
44 Interpreter,
45 },
46 pty::UnixSlavePty,
47};
48
49pub type InspectError = Errno;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Signal {
53 Standard(nix::sys::signal::Signal),
54 Realtime(u8), }
56
57impl Signal {
58 pub fn from_raw(raw: c_int) -> Self {
59 match nix::sys::signal::Signal::try_from(raw) {
60 Ok(sig) => Self::Standard(sig),
61 Err(_) => Self::Realtime(raw as u8),
65 }
66 }
67
68 pub fn as_raw(self) -> i32 {
69 match self {
70 Self::Standard(signal) => signal as i32,
71 Self::Realtime(raw) => raw as i32,
72 }
73 }
74}
75
76impl From<nix::sys::signal::Signal> for Signal {
77 fn from(value: nix::sys::signal::Signal) -> Self {
78 Self::Standard(value)
79 }
80}
81
82impl Display for Signal {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 Self::Standard(signal) => signal.fmt(f),
86 Self::Realtime(sig) => {
87 let min = SIGRTMIN();
88 let delta = *sig as i32 - min;
89 match delta.signum() {
90 0 => write!(f, "SIGRTMIN"),
91 1 => write!(f, "SIGRTMIN+{delta}"),
92 -1 => write!(f, "SIGRTMIN{delta}"),
93 _ => unreachable!(),
94 }
95 }
96 }
97 }
98}
99
100#[derive(Default)]
101#[non_exhaustive]
102pub struct TracerBuilder {
103 pub user: Option<User>,
104 pub modifier: ModifierArgs,
105 pub mode: Option<TracerMode>,
106 pub filter: Option<BitFlags<TracerEventDetailsKind>>,
107 pub tx: Option<UnboundedSender<TracerMessage>>,
108 pub printer: Option<Printer>,
110 pub baseline: Option<Arc<BaselineInfo>>,
111 pub seccomp_bpf: SeccompBpf,
113 pub ptrace_polling_delay: Option<u64>,
114 pub ptrace_blocking: Option<bool>,
115}
116
117impl TracerBuilder {
118 pub fn new() -> Self {
120 Default::default()
121 }
122
123 pub fn ptrace_blocking(mut self, enable: bool) -> Self {
128 if self.ptrace_polling_delay.is_some() && enable {
129 panic!(
130 "Cannot enable blocking mode when ptrace polling delay implicitly specifys polling mode"
131 );
132 }
133 self.ptrace_blocking = Some(enable);
134 self
135 }
136
137 pub fn ptrace_polling_delay(mut self, ptrace_polling_delay: Option<u64>) -> Self {
142 if Some(true) == self.ptrace_blocking && ptrace_polling_delay.is_some() {
143 panic!("Cannot set ptrace_polling_delay when operating in blocking mode")
144 }
145 self.ptrace_polling_delay = ptrace_polling_delay;
146 self
147 }
148
149 pub fn seccomp_bpf(mut self, seccomp_bpf: SeccompBpf) -> Self {
154 self.seccomp_bpf = seccomp_bpf;
155 self
156 }
157
158 pub fn user(mut self, user: Option<User>) -> Self {
162 self.user = user;
163 self
164 }
165
166 pub fn modifier(mut self, modifier: ModifierArgs) -> Self {
167 self.modifier = modifier;
168 self
169 }
170
171 pub fn mode(mut self, mode: TracerMode) -> Self {
173 self.mode = Some(mode);
174 self
175 }
176
177 pub fn filter(mut self, filter: BitFlags<TracerEventDetailsKind>) -> Self {
179 self.filter = Some(filter);
180 self
181 }
182
183 pub fn tracer_tx(mut self, tx: UnboundedSender<TracerMessage>) -> Self {
187 self.tx = Some(tx);
188 self
189 }
190
191 pub fn printer(mut self, printer: Printer) -> Self {
192 self.printer = Some(printer);
193 self
194 }
195
196 pub fn printer_from_cli(mut self, tracing_args: &LogModeArgs) -> Self {
200 self.printer = Some(Printer::new(
201 PrinterArgs::from_cli(tracing_args, &self.modifier),
202 self.baseline.clone().unwrap(),
203 ));
204 self
205 }
206
207 pub fn baseline(mut self, baseline: Arc<BaselineInfo>) -> Self {
208 self.baseline = Some(baseline);
209 self
210 }
211}
212
213#[derive(Debug)]
214pub struct ExecData {
215 pub filename: OutputMsg,
216 pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
217 pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
218 pub has_dash_env: bool,
219 pub cred: Result<Cred, CredInspectError>,
220 pub cwd: OutputMsg,
221 pub interpreters: Option<Vec<Interpreter>>,
222 pub fdinfo: Arc<FileDescriptorInfoCollection>,
223 pub timestamp: DateTime<Local>,
224}
225
226impl ExecData {
227 #[allow(clippy::too_many_arguments)]
228 pub fn new(
229 filename: OutputMsg,
230 argv: Result<Vec<OutputMsg>, InspectError>,
231 envp: Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>,
232 has_dash_env: bool,
233 cred: Result<Cred, CredInspectError>,
234 cwd: OutputMsg,
235 interpreters: Option<Vec<Interpreter>>,
236 fdinfo: FileDescriptorInfoCollection,
237 timestamp: DateTime<Local>,
238 ) -> Self {
239 Self {
240 filename,
241 argv: Arc::new(argv),
242 envp: Arc::new(envp),
243 has_dash_env,
244 cred,
245 cwd,
246 interpreters,
247 fdinfo: Arc::new(fdinfo),
248 timestamp,
249 }
250 }
251}
252
253pub enum TracerMode {
254 Tui(Option<UnixSlavePty>),
255 Log { foreground: bool },
256}
257
258impl PartialEq for TracerMode {
259 fn eq(&self, other: &Self) -> bool {
260 #[allow(clippy::match_like_matches_macro)]
262 match (self, other) {
263 (Self::Log { foreground: a }, Self::Log { foreground: b }) => a == b,
264 _ => false,
265 }
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum ProcessExit {
271 Code(i32),
272 Signal(Signal),
273}
274
275#[cfg(test)]
276mod tests {
277 use std::{
278 collections::BTreeMap,
279 sync::Arc,
280 };
281
282 use chrono::Local;
283 use nix::sys::signal::Signal as NixSignal;
284
285 use super::*;
286 use crate::event::OutputMsg;
287
288 #[test]
291 fn signal_from_raw_standard() {
292 let sig = Signal::from_raw(NixSignal::SIGINT as i32);
293 assert_eq!(sig, Signal::Standard(NixSignal::SIGINT));
294 assert_eq!(sig.as_raw(), NixSignal::SIGINT as i32);
295 }
296
297 #[test]
298 fn signal_from_raw_realtime() {
299 let raw = SIGRTMIN() + 3;
300 let sig = Signal::from_raw(raw);
301 assert_eq!(sig, Signal::Realtime(raw as u8));
302 assert_eq!(sig.as_raw(), raw);
303 }
304
305 #[test]
306 fn signal_display_standard() {
307 let sig = Signal::Standard(NixSignal::SIGTERM);
308 assert_eq!(sig.to_string(), "SIGTERM");
309 }
310
311 #[test]
312 fn signal_display_realtime_variants() {
313 let min = SIGRTMIN();
314
315 let sig_min = Signal::Realtime(min as u8);
316 assert_eq!(sig_min.to_string(), "SIGRTMIN");
317
318 let sig_plus = Signal::Realtime((min + 2) as u8);
319 assert_eq!(sig_plus.to_string(), "SIGRTMIN+2");
320
321 let sig_minus = Signal::Realtime((min - 1) as u8);
322 assert_eq!(sig_minus.to_string(), "SIGRTMIN-1");
323 }
324
325 #[test]
327 #[should_panic(expected = "Cannot enable blocking mode")]
328 fn tracer_builder_blocking_conflict_panics() {
329 TracerBuilder::new()
330 .ptrace_polling_delay(Some(10))
331 .ptrace_blocking(true);
332 }
333
334 #[test]
335 #[should_panic(expected = "Cannot set ptrace_polling_delay")]
336 fn tracer_builder_polling_conflict_panics() {
337 TracerBuilder::new()
338 .ptrace_blocking(true)
339 .ptrace_polling_delay(Some(10));
340 }
341
342 #[test]
343 fn tracer_builder_chaining_works() {
344 let builder = TracerBuilder::new()
345 .ptrace_blocking(false)
346 .ptrace_polling_delay(None)
347 .seccomp_bpf(SeccompBpf::Auto);
348
349 assert_eq!(builder.ptrace_blocking, Some(false));
350 assert_eq!(builder.ptrace_polling_delay, None);
351 }
352
353 #[test]
356 fn exec_data_new_populates_fields() {
357 let filename = OutputMsg::Ok("bin".into());
358 let argv = Ok(vec![
359 OutputMsg::Ok("bin".into()),
360 OutputMsg::Ok("-h".into()),
361 ]);
362
363 let mut envp_map = BTreeMap::new();
364 envp_map.insert(OutputMsg::Ok("A".into()), OutputMsg::Ok("B".into()));
365 let envp = Ok(envp_map);
366
367 let cwd = OutputMsg::Ok("/".into());
368 let fdinfo = FileDescriptorInfoCollection::default();
369 let timestamp = Local::now();
370
371 let exec = ExecData::new(
372 filename.clone(),
373 argv,
374 envp,
375 false,
376 Err(CredInspectError::Inspect),
377 cwd.clone(),
378 None,
379 fdinfo,
380 timestamp,
381 );
382
383 assert_eq!(exec.filename, filename);
384 assert_eq!(exec.cwd, cwd);
385 assert!(exec.argv.is_ok());
386 assert!(exec.envp.is_ok());
387 assert!(!exec.has_dash_env);
388 assert!(exec.interpreters.is_none());
389 assert!(Arc::strong_count(&exec.argv) >= 1);
390 assert!(Arc::strong_count(&exec.envp) >= 1);
391 assert!(Arc::strong_count(&exec.fdinfo) >= 1);
392 }
393
394 #[test]
397 fn process_exit_equality() {
398 let a = ProcessExit::Code(0);
399 let b = ProcessExit::Code(0);
400 let c = ProcessExit::Code(1);
401
402 assert_eq!(a, b);
403 assert_ne!(a, c);
404
405 let s1 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
406 let s2 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
407 let s3 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGTERM));
408
409 assert_eq!(s1, s2);
410 assert_ne!(s1, s3);
411 }
412}