1use super::ExecOutcome;
5use crate::env;
6use std::ffi::OsString;
7use std::os::unix::process::ExitStatusExt;
8use std::path::PathBuf;
9use std::process::{Command, ExitStatus, Stdio};
10use std::time::Instant;
11
12pub struct SpawnSpec {
14 pub program: PathBuf,
15 pub args: Vec<OsString>,
16 pub cwd: PathBuf,
17 pub env_path: OsString,
19 pub capture: bool,
21 pub inherit_stdin: bool,
23 pub capture_limit: Option<usize>,
25}
26
27pub trait CommandRunner {
29 fn run(&self, spec: &SpawnSpec) -> std::io::Result<ExecOutcome>;
30}
31
32pub struct RealRunner;
34
35impl CommandRunner for RealRunner {
36 fn run(&self, spec: &SpawnSpec) -> std::io::Result<ExecOutcome> {
37 let mut cmd = Command::new(&spec.program);
38 cmd.args(&spec.args)
39 .current_dir(&spec.cwd)
40 .env("PATH", &spec.env_path)
41 .env(env::DISABLED, "1");
44
45 if spec.capture {
46 cmd.stdin(if spec.inherit_stdin {
47 Stdio::inherit()
48 } else {
49 Stdio::null()
50 })
51 .stdout(Stdio::piped())
52 .stderr(Stdio::piped());
53
54 let start = Instant::now();
55 let output = cmd.spawn()?.wait_with_output()?;
56 let duration = start.elapsed();
57 Ok(ExecOutcome {
58 stdout: output.stdout,
59 stderr: output.stderr,
60 exit_code: normalized_exit_code(output.status),
61 duration,
62 captured: true,
63 truncated_raw: false,
64 })
65 } else {
66 cmd.stdin(Stdio::inherit())
67 .stdout(Stdio::inherit())
68 .stderr(Stdio::inherit());
69
70 let start = Instant::now();
71 let status = cmd.spawn()?.wait()?;
72 Ok(ExecOutcome {
73 stdout: Vec::new(),
74 stderr: Vec::new(),
75 exit_code: normalized_exit_code(status),
76 duration: start.elapsed(),
77 captured: false,
78 truncated_raw: false,
79 })
80 }
81 }
82}
83
84pub fn normalized_exit_code(status: ExitStatus) -> i32 {
87 if let Some(code) = status.code() {
88 code
89 } else if let Some(signal) = status.signal() {
90 128 + signal
91 } else {
92 1
93 }
94}