1use std::{
2 cell::RefCell,
3 collections::BTreeMap,
4 fmt::{Debug, Display},
5 io::{self, Write},
6 sync::Arc,
7};
8
9use crate::{
10 cli::{
11 args::{LogModeArgs, ModifierArgs},
12 theme::THEME,
13 },
14 event::{FriendlyError, OutputMsg},
15 proc::{BaselineInfo, FileDescriptorInfo, FileDescriptorInfoCollection, Interpreter, diff_env},
16 timestamp::TimestampFormat,
17 tracer::ExecData,
18};
19
20use crate::cache::ArcStr;
21use itertools::chain;
22use nix::{fcntl::OFlag, libc::ENOENT, unistd::Pid};
23use owo_colors::{OwoColorize, Style};
24
25macro_rules! escape_str_for_bash {
26 ($x:expr) => {{
27 let result: String = shell_quote::QuoteRefExt::quoted($x, shell_quote::Bash);
28 result
29 }};
30}
31
32#[derive(Debug, Clone, Copy)]
33pub enum EnvPrintFormat {
34 Diff,
35 Raw,
36 None,
37}
38
39#[derive(Debug, Clone, Copy)]
40pub enum FdPrintFormat {
41 Diff,
42 Raw,
43 None,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
47pub enum ColorLevel {
48 Less,
49 Normal,
50 More,
51}
52
53#[derive(Debug, Clone)]
54pub struct PrinterArgs {
55 pub trace_comm: bool,
56 pub trace_argv: bool,
57 pub trace_env: EnvPrintFormat,
58 pub trace_fd: FdPrintFormat,
59 pub trace_cwd: bool,
60 pub print_cmdline: bool,
61 pub successful_only: bool,
62 pub trace_interpreter: bool,
63 pub trace_filename: bool,
64 pub decode_errno: bool,
65 pub color: ColorLevel,
66 pub stdio_in_cmdline: bool,
67 pub fd_in_cmdline: bool,
68 pub hide_cloexec_fds: bool,
69 pub inline_timestamp_format: Option<TimestampFormat>,
70}
71
72impl PrinterArgs {
73 pub fn from_cli(tracing_args: &LogModeArgs, modifier_args: &ModifierArgs) -> Self {
74 Self {
75 trace_comm: !tracing_args.no_show_comm,
76 trace_argv: !tracing_args.no_show_argv && !tracing_args.show_cmdline,
77 trace_env: match (
78 tracing_args.show_cmdline,
79 tracing_args.diff_env,
80 tracing_args.no_diff_env,
81 tracing_args.show_env,
82 tracing_args.no_show_env,
83 ) {
84 (true, ..) | (.., true) => EnvPrintFormat::None,
85 (false, .., true, _) | (false, _, true, ..) => EnvPrintFormat::Raw,
86 _ => EnvPrintFormat::Diff, },
88 trace_fd: match (
89 tracing_args.diff_fd,
90 tracing_args.no_diff_fd,
91 tracing_args.show_fd,
92 tracing_args.no_show_fd,
93 ) {
94 (false, _, true, false) => FdPrintFormat::Raw,
95 (_, true, _, _) => FdPrintFormat::None,
96 (true, _, _, _) => FdPrintFormat::Diff,
97 _ => {
98 if modifier_args.fd_in_cmdline || modifier_args.stdio_in_cmdline {
101 FdPrintFormat::None
102 } else {
103 FdPrintFormat::Diff
104 }
105 }
106 },
107 trace_cwd: tracing_args.show_cwd,
108 print_cmdline: tracing_args.show_cmdline,
109 successful_only: modifier_args.successful_only,
110 trace_interpreter: tracing_args.show_interpreter,
111 trace_filename: match (tracing_args.show_filename, tracing_args.no_show_filename) {
112 (_, true) => false,
113 (true, _) => true,
114 _ => true,
116 },
117 decode_errno: !tracing_args.no_decode_errno,
118 color: match (tracing_args.more_colors, tracing_args.less_colors) {
119 (false, false) => ColorLevel::Normal,
120 (true, false) => ColorLevel::More,
121 (false, true) => ColorLevel::Less,
122 _ => unreachable!(),
123 },
124 stdio_in_cmdline: modifier_args.stdio_in_cmdline,
125 fd_in_cmdline: modifier_args.fd_in_cmdline,
126 hide_cloexec_fds: modifier_args.hide_cloexec_fds,
127 inline_timestamp_format: modifier_args.timestamp.then(||
128 modifier_args.inline_timestamp_format.clone().unwrap()),
130 }
131 }
132}
133
134pub type PrinterOut = dyn Write + Send + Sync + 'static;
135
136enum DeferredWarningKind {
137 NoArgv0,
138 FailedReadingArgv(FriendlyError),
139 FailedReadingFilename(FriendlyError),
140 FailedReadingEnvp(FriendlyError),
141}
142
143struct DeferredWarnings {
144 warning: DeferredWarningKind,
145 pid: Pid,
146}
147
148impl Drop for DeferredWarnings {
149 fn drop(&mut self) {
150 Printer::OUT.with_borrow_mut(|out| {
151 if let Some(out) = out {
152 write!(out, "{}", self.pid.bright_red()).unwrap();
153 write!(out, "[{}]: ", "warning".bright_yellow()).unwrap();
154 match self.warning {
155 DeferredWarningKind::NoArgv0 => {
156 write!(
157 out,
158 "No argv[0] provided! The printed commandline might be incorrect!"
159 )
160 .unwrap();
161 }
162 DeferredWarningKind::FailedReadingArgv(e) => {
163 write!(out, "Failed to read argv: {e}").unwrap();
164 }
165 DeferredWarningKind::FailedReadingFilename(e) => {
166 write!(out, "Failed to read filename: {e}").unwrap();
167 }
168 DeferredWarningKind::FailedReadingEnvp(e) => {
169 write!(out, "Failed to read envp: {e}").unwrap();
170 }
171 };
172 writeln!(out).unwrap();
173 };
174 })
175 }
176}
177
178pub struct ListPrinter {
179 style: owo_colors::Style,
180}
181
182impl ListPrinter {
183 pub fn new(color: ColorLevel) -> Self {
184 if color > ColorLevel::Normal {
185 Self {
186 style: Style::new().bright_white().bold(),
187 }
188 } else {
189 Self {
190 style: Style::new(),
191 }
192 }
193 }
194
195 pub fn begin(&self, out: &mut dyn Write) -> io::Result<()> {
196 write!(out, "{}", "[".style(self.style))
197 }
198
199 pub fn end(&self, out: &mut dyn Write) -> io::Result<()> {
200 write!(out, "{}", "]".style(self.style))
201 }
202
203 pub fn comma(&self, out: &mut dyn Write) -> io::Result<()> {
204 write!(out, "{}", ", ".style(self.style))
205 }
206
207 pub fn print_string_list(&self, out: &mut dyn Write, list: &[impl Display]) -> io::Result<()> {
208 self.begin(out)?;
209 if let Some((last, rest)) = list.split_last() {
210 if rest.is_empty() {
211 write!(out, "{last}")?;
212 } else {
213 for s in rest {
214 write!(out, "{s}")?;
215 self.comma(out)?;
216 }
217 write!(out, "{last}")?;
218 }
219 }
220 self.end(out)
221 }
222
223 pub fn print_env(
224 &self,
225 out: &mut dyn Write,
226 env: &BTreeMap<OutputMsg, OutputMsg>,
227 ) -> io::Result<()> {
228 self.begin(out)?;
229 let mut first_item_written = false;
230 let mut write_separator = |out: &mut dyn Write| -> io::Result<()> {
231 if first_item_written {
232 self.comma(out)?;
233 } else {
234 first_item_written = true;
235 }
236 Ok(())
237 };
238 for (k, v) in env.iter() {
239 write_separator(out)?;
240 write!(out, "{k}={v}")?;
241 }
242 self.end(out)
243 }
244}
245
246pub struct Printer {
247 pub args: PrinterArgs,
248 baseline: Arc<BaselineInfo>,
249}
250
251impl Printer {
252 pub fn new(args: PrinterArgs, baseline: Arc<BaselineInfo>) -> Self {
253 Self { args, baseline }
254 }
255
256 thread_local! {
257 pub static OUT: RefCell<Option<Box<PrinterOut>>> = RefCell::new(None);
258 }
259
260 pub fn init_thread_local(&self, output: Option<Box<PrinterOut>>) {
261 Self::OUT.with(|out| {
262 *out.borrow_mut() = output;
263 });
264 }
265
266 pub fn print_new_child(&self, parent: Pid, comm: &str, child: Pid) -> color_eyre::Result<()> {
267 Self::OUT.with_borrow_mut(|out| {
268 let Some(out) = out else {
269 return Ok(());
270 };
271 write!(out, "{}", parent.bright_green())?;
272 if self.args.trace_comm {
273 write!(out, "<{}>", comm.cyan())?;
274 }
275 writeln!(out, ": {}: {}", "new child".purple(), child.bright_green())?;
276 out.flush()?;
277 Ok(())
278 })
279 }
280
281 fn print_stdio_fd(
282 &self,
283 out: &mut dyn Write,
284 fd: i32,
285 orig_fd: &FileDescriptorInfo,
286 curr_fd: Option<&FileDescriptorInfo>,
287 list_printer: &ListPrinter,
288 ) -> io::Result<()> {
289 let desc = match fd {
290 0 => "stdin",
291 1 => "stdout",
292 2 => "stderr",
293 _ => unreachable!(),
294 };
295 if let Some(fdinfo) = curr_fd {
296 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
297 if !self.args.hide_cloexec_fds {
298 write!(
299 out,
300 "{}{}",
301 "cloexec: ".bright_red().bold(),
302 desc.bright_red().bold()
303 )?;
304 list_printer.comma(out)?;
305 } else {
306 write!(
307 out,
308 "{}{}",
309 "closed: ".bright_red().bold(),
310 desc.bright_red().bold()
311 )?;
312 list_printer.comma(out)?;
313 }
314 } else if fdinfo.not_same_file_as(orig_fd) {
315 write!(out, "{}", desc.bright_yellow().bold())?;
316 write!(out, "={}", fdinfo.path.bright_yellow())?;
317 list_printer.comma(out)?;
318 }
319 } else {
320 write!(
321 out,
322 "{}{}",
323 "closed: ".bright_red().bold(),
324 desc.bright_red().bold()
325 )?;
326 list_printer.comma(out)?;
327 }
328 Ok(())
329 }
330
331 pub fn print_fd(
332 &self,
333 out: &mut dyn Write,
334 fds: &FileDescriptorInfoCollection,
335 ) -> io::Result<()> {
336 match self.args.trace_fd {
337 FdPrintFormat::Diff => {
338 write!(out, " {} ", "fd".purple())?;
339 let list_printer = ListPrinter::new(self.args.color);
340 list_printer.begin(out)?;
341 for fd in 0..=2 {
343 let fdinfo_orig = self.baseline.fdinfo.get(fd).unwrap();
344 self.print_stdio_fd(out, fd, fdinfo_orig, fds.fdinfo.get(&fd), &list_printer)?;
345 }
346 for (&fd, fdinfo) in fds.fdinfo.iter() {
347 if fd < 3 {
348 continue;
349 }
350 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
351 if !self.args.hide_cloexec_fds {
352 write!(
353 out,
354 "{} {}",
355 "cloexec:".bright_red().bold(),
356 fd.bright_green().bold()
357 )?;
358 write!(out, "={}", fdinfo.path.bright_red())?;
359 list_printer.comma(out)?;
360 }
361 } else {
362 write!(out, "{}", fd.bright_green().bold())?;
363 write!(out, "={}", fdinfo.path.bright_green())?;
364 list_printer.comma(out)?;
365 }
366 }
367 list_printer.end(out)?;
368 }
369 FdPrintFormat::Raw => {
370 write!(out, " {} ", "fd".purple())?;
371 let list_printer = ListPrinter::new(self.args.color);
372 list_printer.begin(out)?;
373 let last = fds.fdinfo.len() - 1;
374 for (idx, (fd, fdinfo)) in fds.fdinfo.iter().enumerate() {
375 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
376 if self.args.hide_cloexec_fds {
377 continue;
378 }
379 write!(out, "{}", fd.bright_red().bold())?;
380 } else {
381 write!(out, "{}", fd.bright_cyan().bold())?;
382 }
383 write!(out, "={}", fdinfo.path)?;
384 if idx != last {
385 list_printer.comma(out)?;
386 }
387 }
388 list_printer.end(out)?;
389 }
390 FdPrintFormat::None => {}
391 }
392 Ok(())
393 }
394
395 pub fn print_exec_trace(
396 &self,
397 pid: Pid,
398 comm: ArcStr,
399 result: i64,
400 exec_data: &ExecData,
401 env: &BTreeMap<OutputMsg, OutputMsg>,
402 cwd: &OutputMsg,
403 ) -> color_eyre::Result<()> {
404 #[allow(clippy::collection_is_never_read)]
410 let mut _deferred_warnings = vec![];
411
412 Self::OUT.with_borrow_mut(|out| {
413 let Some(out) = out else {
414 return Ok(());
415 };
416 let list_printer = ListPrinter::new(self.args.color);
417 if let Some(f) = self.args.inline_timestamp_format.as_deref() {
418 write!(out, "{} ", exec_data.timestamp.format(f).bright_cyan())?;
419 }
420 if result == 0 {
421 write!(out, "{}", pid.bright_green())?;
422 } else if result == -ENOENT as i64 {
423 write!(out, "{}", pid.bright_yellow())?;
424 } else {
425 write!(out, "{}", pid.bright_red())?;
426 }
427 if self.args.trace_comm {
428 write!(out, "<{}>", comm.cyan())?;
429 }
430 write!(out, ":")?;
431
432 if self.args.trace_filename {
433 write!(
434 out,
435 " {}",
436 exec_data.filename.cli_escaped_styled(THEME.filename)
437 )?;
438 }
439 if let OutputMsg::Err(e) = exec_data.filename {
440 _deferred_warnings.push(DeferredWarnings {
441 warning: DeferredWarningKind::FailedReadingFilename(e),
442 pid,
443 });
444 }
445
446 match exec_data.argv.as_ref() {
447 Err(e) => {
448 _deferred_warnings.push(DeferredWarnings {
449 warning: DeferredWarningKind::FailedReadingArgv(FriendlyError::InspectError(*e)),
450 pid,
451 });
452 }
453 Ok(argv) => {
454 if self.args.trace_argv {
455 write!(out, " ")?;
456 list_printer.print_string_list(out, argv)?;
457 }
458 }
459 }
460
461 if self.args.trace_cwd {
464 write!(
465 out,
466 " {} {}",
467 "at".purple(),
468 exec_data
469 .cwd
470 .cli_escaped_styled(if self.args.color >= ColorLevel::Normal {
471 THEME.cwd
472 } else {
473 THEME.plain
474 })
475 )?;
476 }
477
478 if self.args.trace_interpreter
481 && result == 0
482 && let Some(interpreters) = exec_data.interpreters.as_ref()
483 {
484 write!(out, " {} ", "interpreter".purple(),)?;
486 match interpreters.len() {
487 0 => {
488 write!(out, "{}", Interpreter::None)?;
489 }
490 1 => {
491 write!(out, "{}", interpreters[0])?;
492 }
493 _ => {
494 list_printer.begin(out)?;
495 for (idx, interpreter) in interpreters.iter().enumerate() {
496 if idx != 0 {
497 list_printer.comma(out)?;
498 }
499 write!(out, "{interpreter}")?;
500 }
501 list_printer.end(out)?;
502 }
503 }
504 }
505
506 self.print_fd(out, &exec_data.fdinfo)?;
509
510 match exec_data.envp.as_ref() {
513 Ok(envp) => {
514 match self.args.trace_env {
515 EnvPrintFormat::Diff => {
516 write!(out, " {} ", "with".purple())?;
517 list_printer.begin(out)?;
518 let env = env.clone();
519 let mut first_item_written = false;
520 let mut write_separator = |out: &mut dyn Write| -> io::Result<()> {
521 if first_item_written {
522 list_printer.comma(out)?;
523 } else {
524 first_item_written = true;
525 }
526 Ok(())
527 };
528
529 let diff = diff_env(&env, envp);
530 for (k, v) in diff.added.into_iter() {
531 write_separator(out)?;
532 write!(
533 out,
534 "{}{}{}{}",
535 "+".bright_green().bold(),
536 k.cli_escaped_styled(THEME.added_env_var),
537 "=".bright_green().bold(),
538 v.cli_escaped_styled(THEME.added_env_var)
539 )?;
540 }
541 for (k, v) in diff.modified.into_iter() {
542 write_separator(out)?;
543 write!(
544 out,
545 "{}{}{}{}",
546 "M".bright_yellow().bold(),
547 k.cli_escaped_styled(THEME.modified_env_key),
548 "=".bright_yellow().bold(),
549 v.cli_escaped_styled(THEME.modified_env_val)
550 )?;
551 }
552 for k in diff.removed.into_iter() {
554 write_separator(out)?;
555 write!(
556 out,
557 "{}{}{}{}",
558 "-".bright_red().bold(),
559 k.cli_escaped_styled(THEME.removed_env_var),
560 "=".bright_red().strikethrough(),
561 env
562 .get(&k)
563 .unwrap()
564 .cli_escaped_styled(THEME.removed_env_var)
565 )?;
566 }
567 list_printer.end(out)?;
568 if owo_colors::control::should_colorize() {
571 write!(out, "\x1B[49m\x1B[K")?;
572 }
573 }
574 EnvPrintFormat::Raw => {
575 write!(out, " {} ", "with".purple())?;
576 list_printer.print_env(out, envp)?;
577 }
578 EnvPrintFormat::None => (),
579 }
580 }
581 Err(e) => {
582 match self.args.trace_env {
583 EnvPrintFormat::Diff | EnvPrintFormat::Raw => {
584 write!(
585 out,
586 " {} {}",
587 "with".purple(),
588 format!("[Failed to read envp: {e}]")
589 .bright_red()
590 .blink()
591 .bold()
592 )?;
593 }
594 EnvPrintFormat::None => {}
595 }
596 _deferred_warnings.push(DeferredWarnings {
597 warning: DeferredWarningKind::FailedReadingEnvp(FriendlyError::InspectError(*e)),
598 pid,
599 });
600 }
601 }
602
603 if self.args.print_cmdline {
606 write!(out, " {}", "cmdline".purple())?;
607 write!(out, " env")?;
608
609 if self.args.stdio_in_cmdline {
610 let fdinfo_orig = self.baseline.fdinfo.stdin().unwrap();
611 if let Some(fdinfo) = exec_data.fdinfo.stdin() {
612 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
613 write!(out, " {}", "0>&-".bright_red().bold().italic())?;
615 } else if fdinfo.not_same_file_as(fdinfo_orig) {
616 write!(
617 out,
618 " {}{}",
619 "<".bright_yellow().bold(),
620 fdinfo.path.cli_bash_escaped_with_style(THEME.modified_fd)
621 )?;
622 }
623 } else {
624 write!(out, " {}", "0>&-".bright_red().bold())?;
626 }
627 let fdinfo_orig = self.baseline.fdinfo.stdout().unwrap();
628 if let Some(fdinfo) = exec_data.fdinfo.stdout() {
629 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
630 write!(out, " {}", "1>&-".bright_red().bold().italic())?;
632 } else if fdinfo.not_same_file_as(fdinfo_orig) {
633 write!(
634 out,
635 " {}{}",
636 ">".bright_yellow().bold(),
637 fdinfo.path.cli_bash_escaped_with_style(THEME.modified_fd)
638 )?;
639 }
640 } else {
641 write!(out, " {}", "1>&-".bright_red().bold())?;
643 }
644 let fdinfo_orig = self.baseline.fdinfo.stderr().unwrap();
645 if let Some(fdinfo) = exec_data.fdinfo.stderr() {
646 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
647 write!(out, " {}", "2>&-".bright_red().bold().italic())?;
649 } else if fdinfo.not_same_file_as(fdinfo_orig) {
650 write!(
651 out,
652 " {}{}",
653 "2>".bright_yellow().bold(),
654 fdinfo.path.cli_bash_escaped_with_style(THEME.modified_fd)
655 )?;
656 }
657 } else {
658 write!(out, " {}", "2>&-".bright_red().bold())?;
660 }
661 }
662
663 if self.args.fd_in_cmdline {
664 for (&fd, fdinfo) in exec_data.fdinfo.fdinfo.iter() {
665 if fd < 3 {
666 continue;
667 }
668 if fdinfo.flags.contains(OFlag::O_CLOEXEC) {
669 continue;
671 }
672 write!(
673 out,
674 " {}{}{}",
675 fd.bright_green().bold(),
676 "<>".bright_green().bold(),
677 fdinfo.path.cli_bash_escaped_with_style(THEME.added_fd)
678 )?;
679 }
680 }
681
682 match exec_data.argv.as_ref() {
683 Ok(argv) => {
684 if let Some(arg0) = argv.first() {
685 if &exec_data.filename != arg0 {
687 write!(
688 out,
689 " {} {}",
690 "-a".bright_white().italic(),
691 escape_str_for_bash!(arg0.as_ref()).bright_white().italic()
692 )?;
693 }
694 } else {
695 _deferred_warnings.push(DeferredWarnings {
696 warning: DeferredWarningKind::NoArgv0,
697 pid,
698 });
699 }
700 if cwd != &exec_data.cwd {
701 if self.args.color >= ColorLevel::Normal {
702 write!(
703 out,
704 " -C {}",
705 &exec_data.cwd.cli_bash_escaped_with_style(THEME.cwd)
706 )?;
707 } else {
708 write!(out, " -C {}", exec_data.cwd.bash_escaped())?;
709 }
710 }
711 if let Ok(envp) = exec_data.envp.as_ref() {
713 let diff = diff_env(env, envp);
714 let need_env_argument_separator = diff.need_env_argument_separator();
715 for k in diff.removed.into_iter() {
717 if self.args.color >= ColorLevel::Normal {
718 write!(
719 out,
720 " {}{}",
721 "-u ".bright_red(),
722 k.cli_bash_escaped_with_style(THEME.removed_env_key)
723 )?;
724 } else {
725 write!(out, " -u {}", k.bash_escaped())?;
726 }
727 }
728 if need_env_argument_separator {
729 write!(out, " --")?;
730 }
731 if self.args.color >= ColorLevel::Normal {
732 for (k, v) in diff.added.into_iter() {
733 write!(
734 out,
735 " {}{}{}",
736 k.cli_bash_escaped_with_style(THEME.added_env_var),
737 "=".green().bold(),
738 v.cli_bash_escaped_with_style(THEME.added_env_var)
739 )?;
740 }
741 for (k, v) in diff.modified.into_iter() {
742 write!(
743 out,
744 " {}{}{}",
745 k.bash_escaped(),
746 "=".bright_yellow().bold(),
747 v.cli_bash_escaped_with_style(THEME.modified_env_val)
748 )?;
749 }
750 } else {
751 for (k, v) in chain!(diff.added.into_iter(), diff.modified.into_iter()) {
752 write!(out, " {}={}", k.bash_escaped(), v.bash_escaped())?;
753 }
754 }
755 }
756 write!(out, " {}", exec_data.filename.bash_escaped())?;
757 for arg in argv.iter().skip(1) {
758 write!(out, " {}", arg.bash_escaped())?;
759 }
760 }
761 Err(e) => {
762 _deferred_warnings.push(DeferredWarnings {
763 warning: DeferredWarningKind::FailedReadingArgv(FriendlyError::InspectError(*e)),
764 pid,
765 });
766 }
767 }
768 }
769
770 if result == 0 {
773 writeln!(out)?;
774 } else {
775 write!(out, " {} ", "=".purple())?;
776 if self.args.decode_errno {
777 writeln!(
778 out,
779 "{} ({})",
780 result.bright_red().bold(),
781 nix::errno::Errno::from_raw(-result as i32).red()
782 )?;
783 } else {
784 writeln!(out, "{}", result.bright_red().bold())?;
785 }
786 }
787 out.flush()?;
791 Ok(())
792 })
793 }
794}