tracexec_core/
printer.rs

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, // diff_env is enabled by default
112      },
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          // The default is diff fd,
124          // but if fd_in_cmdline or stdio_in_cmdline is enabled, we disable diff fd by default
125          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        // default
140        _ => 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        // We ensure a default is set in modifier_args
154        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        // Stdio
367        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    // Preconditions:
430    // 1. execve syscall exit, which leads to 2
431    // 2. state.exec_data is Some
432
433    // Defer the warnings so that they are printed after the main message
434    #[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      // CWD
487
488      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      // Interpreter
504
505      if self.args.trace_interpreter
506        && result == 0
507        && let Some(interpreters) = exec_data.interpreters.as_ref()
508      {
509        // FIXME: show interpreter for errnos other than ENOENT
510        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      // File descriptors
532
533      self.print_fd(out, &exec_data.fdinfo)?;
534
535      // Environment
536
537      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              // Now we have the tracee removed entries in env
578              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              // Avoid trailing color
594              // https://unix.stackexchange.com/questions/212933/background-color-whitespace-when-end-of-the-terminal-reached
595              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      // Command line
629
630      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              // stdin will be closed
639              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            // stdin is closed
650            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              // stdout will be closed
656              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            // stdout is closed
667            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              // stderr will be closed
673              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            // stderr is closed
684            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              // Don't show fds that will be closed upon exec
695              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              // filename warning is already handled
711              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            // envp warning is already handled
737            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              // Now we have the tracee removed entries in env
741              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      // Result
796
797      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      // It is critical to call [flush] before BufWriter<W> is dropped.
813      // Though dropping will attempt to flush the contents of the buffer, any errors that happen in the process of dropping will be ignored.
814      // Calling [flush] ensures that the buffer is empty and thus dropping will not even attempt file operations.
815      out.flush()?;
816      Ok(())
817    })
818  }
819}