1use std::io::{Read, Write};
7use std::process::{Command, Stdio};
8
9use crate::binary::resolve_binary;
10use crate::error::{Error, Result};
11
12#[derive(Debug, Clone)]
14pub struct RawResult {
15 pub stdout: String,
16 pub stderr: String,
17 pub exit_code: i32,
18}
19
20fn with_globals(args: &[String], log_level: u32) -> Vec<String> {
21 let mut out = vec!["--log-level".to_string(), log_level.to_string()];
22 out.extend(args.iter().cloned());
23 out
24}
25
26pub(crate) fn run_full(
33 args: &[String],
34 env: &[(String, String)],
35 stdin: Option<&[u8]>,
36 log_level: u32,
37) -> Result<RawResult> {
38 run_full_stream(args, env, stdin, log_level, None, None)
39}
40
41pub(crate) fn run_full_stream(
42 args: &[String],
43 env: &[(String, String)],
44 stdin: Option<&[u8]>,
45 log_level: u32,
46 on_stdout: Option<Box<dyn Write + Send>>,
47 on_stderr: Option<Box<dyn Write + Send>>,
48) -> Result<RawResult> {
49 let (stdout, stderr, exit_code) = run_raw(args, env, stdin, log_level, on_stdout, on_stderr)?;
50 Ok(RawResult {
51 stdout: String::from_utf8_lossy(&stdout).into_owned(),
52 stderr: String::from_utf8_lossy(&stderr).into_owned(),
53 exit_code,
54 })
55}
56
57fn run_raw(
59 args: &[String],
60 env: &[(String, String)],
61 stdin: Option<&[u8]>,
62 log_level: u32,
63 mut on_stdout: Option<Box<dyn Write + Send>>,
64 mut on_stderr: Option<Box<dyn Write + Send>>,
65) -> Result<(Vec<u8>, Vec<u8>, i32)> {
66 let binary = resolve_binary()?;
67 let mut cmd = Command::new(binary);
68 cmd.args(with_globals(args, log_level));
69 for (key, value) in env {
70 cmd.env(key, value);
71 }
72 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
73 if stdin.is_some() {
74 cmd.stdin(Stdio::piped());
75 }
76
77 let mut child = cmd.spawn()?;
78
79 let stdin_thread = stdin.map(|bytes| {
83 let mut handle = child.stdin.take().expect("stdin was piped");
84 let owned = bytes.to_vec();
85 std::thread::spawn(move || {
86 let _ = handle.write_all(&owned);
87 })
88 });
89 let stderr_thread = {
90 let mut handle = child.stderr.take().expect("stderr was piped");
91 std::thread::spawn(move || {
92 let mut buf = Vec::new();
93 let mut chunk = [0_u8; 8192];
94 loop {
95 match handle.read(&mut chunk) {
96 Ok(0) | Err(_) => break,
97 Ok(n) => {
98 buf.extend_from_slice(&chunk[..n]);
99 if let Some(w) = on_stderr.as_mut() {
100 let _ = w.write_all(&chunk[..n]);
101 }
102 }
103 }
104 }
105 buf
106 })
107 };
108
109 let mut stdout_buf = Vec::new();
110 if let Some(mut out) = child.stdout.take() {
111 let mut chunk = [0_u8; 8192];
112 loop {
113 let n = out.read(&mut chunk)?;
114 if n == 0 {
115 break;
116 }
117 stdout_buf.extend_from_slice(&chunk[..n]);
118 if let Some(w) = on_stdout.as_mut() {
119 w.write_all(&chunk[..n])?;
120 }
121 }
122 }
123 let stderr_buf = stderr_thread.join().unwrap_or_default();
124 if let Some(t) = stdin_thread {
125 let _ = t.join();
126 }
127 let status = child.wait()?;
128
129 Ok((stdout_buf, stderr_buf, status.code().unwrap_or(-1)))
130}
131
132#[derive(Debug, Clone)]
134pub struct BinaryResult {
135 pub stdout: Vec<u8>,
136 pub stderr: String,
137 pub exit_code: i32,
138}
139
140pub fn run_binary<I, S>(args: I, stdin: Option<&[u8]>) -> Result<BinaryResult>
146where
147 I: IntoIterator<Item = S>,
148 S: Into<String>,
149{
150 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
151 let (stdout, stderr, exit_code) = run_raw(&argv, &[], stdin, 0, None, None)?;
152 Ok(BinaryResult {
153 stdout,
154 stderr: String::from_utf8_lossy(&stderr).into_owned(),
155 exit_code,
156 })
157}
158
159pub fn run<I, S>(args: I) -> Result<RawResult>
161where
162 I: IntoIterator<Item = S>,
163 S: Into<String>,
164{
165 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
166 run_full(&argv, &[], None, 0)
167}
168
169pub fn run_checked<I, S>(args: I, label: &str) -> Result<RawResult>
172where
173 I: IntoIterator<Item = S>,
174 S: Into<String>,
175{
176 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
177 checked(&argv, label)
178}
179
180pub(crate) fn checked(args: &[String], label: &str) -> Result<RawResult> {
181 let result = run_full(args, &[], None, 0)?;
182 if result.exit_code != 0 {
183 return Err(Error::CommandFailed {
184 exit_code: result.exit_code,
185 stdout: result.stdout,
186 stderr: result.stderr,
187 command: label.to_string(),
188 });
189 }
190 Ok(result)
191}
192
193pub fn spawn<I, S>(args: I) -> Result<i32>
198where
199 I: IntoIterator<Item = S>,
200 S: Into<String>,
201{
202 let binary = resolve_binary()?;
203 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
204 let status = Command::new(binary).args(with_globals(&argv, 0)).status()?;
205 Ok(status.code().unwrap_or(-1))
206}