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