use std::{
collections::VecDeque,
io::{self, Read, Write},
process::{Child, ExitStatus, Stdio},
time::{Duration, Instant},
};
#[derive(Default, Debug)]
pub(crate) struct ExecResult {
pub(crate) stdout: Vec<u8>,
pub(crate) stderr: Vec<u8>,
pub(crate) status: Option<ExitStatus>,
pub(crate) error: Option<io::Error>,
}
pub(crate) fn wait_deadline(
child: &mut Child,
deadline: Option<Instant>,
) -> io::Result<ExitStatus> {
let Some(deadline) = deadline else {
return child.wait();
};
let mut sleep_ms = 1;
let sleep_ms_max = 64;
loop {
match child.try_wait()? {
Some(status) => return Ok(status),
None => {}
}
if Instant::now() > deadline {
let _ = child.kill();
let _ = child.wait();
return Err(io::ErrorKind::TimedOut.into());
}
std::thread::sleep(Duration::from_millis(sleep_ms));
sleep_ms = std::cmp::min(sleep_ms * 2, sleep_ms_max);
}
}
pub(crate) fn exec(
mut command: std::process::Command,
stdin_contents: Option<&[u8]>,
stdout_limit: Option<usize>,
stderr_limit: Option<usize>,
deadline: Option<Instant>,
) -> ExecResult {
let mut result = ExecResult::default();
command.stdin(if stdin_contents.is_some() { Stdio::piped() } else { Stdio::null() });
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
let mut child = match command.spawn() {
Ok(it) => it,
Err(err) => {
result.error = Some(err);
return result;
}
};
let stdin = child.stdin.take();
let mut in_error = Ok(());
let mut stdout = child.stdout.take().unwrap();
let mut out_deque = VecDeque::new();
let mut out_error = Ok(());
let mut stderr = child.stderr.take().unwrap();
let mut err_deque = VecDeque::new();
let mut err_error = Ok(());
let status = std::thread::scope(|scope| {
if let Some(stdin_contents) = stdin_contents {
scope.spawn(|| in_error = stdin.unwrap().write_all(stdin_contents));
}
scope.spawn(|| {
out_error = (|| {
let mut buffer = [0u8; 4096];
loop {
let n = stdout.read(&mut buffer)?;
if n == 0 {
return Ok(());
}
out_deque.extend(buffer[0..n].iter().copied());
let excess = out_deque.len().saturating_sub(stdout_limit.unwrap_or(usize::MAX));
if excess > 0 {
out_deque.drain(..excess);
}
}
})()
});
scope.spawn(|| {
err_error = (|| {
let mut buffer = [0u8; 4096];
loop {
let n = stderr.read(&mut buffer)?;
if n == 0 {
return Ok(());
}
err_deque.extend(buffer[0..n].iter().copied());
let excess = err_deque.len().saturating_sub(stderr_limit.unwrap_or(usize::MAX));
if excess > 0 {
err_deque.drain(..excess);
}
}
})()
});
wait_deadline(&mut child, deadline)
});
if let Err(err) = err_error {
result.error = err;
}
if let Err(err) = out_error {
result.error = err;
}
if let Err(err) = in_error {
if err.kind() != io::ErrorKind::BrokenPipe {
result.error = Some(err);
}
}
match status {
Ok(status) => result.status = Some(status),
Err(err) => result.error = Some(err),
}
result.stdout = out_deque.into();
result.stderr = err_deque.into();
result
}