use bytes::Bytes;
use std::process::Stdio;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
pub struct RawResult {
pub stdout: Bytes,
pub stderr: Bytes,
pub stdout_truncated: bool,
pub stderr_truncated: bool,
pub exit_code: Option<i32>,
pub timed_out: bool,
}
pub async fn drain_with_cap<R: tokio::io::AsyncRead + Unpin>(r: R, cap: usize) -> (Bytes, bool) {
let mut buf = Vec::with_capacity(cap.min(8192));
let mut reader = r;
let mut truncated = false;
let mut tmp = [0u8; 8192];
loop {
match reader.read(&mut tmp).await {
Ok(0) => break,
Ok(n) => {
if !truncated {
let space = cap - buf.len();
if n <= space {
buf.extend_from_slice(&tmp[..n]);
} else {
buf.extend_from_slice(&tmp[..space]);
truncated = true;
}
}
}
Err(e) => {
tracing::debug!(error = %e, "drain read interrupted");
break;
}
}
}
(Bytes::from(buf), truncated)
}
pub fn spawn(
exe: &std::path::Path,
args: &[String],
env: &std::collections::HashMap<String, String>,
cwd: &std::path::Path,
) -> std::io::Result<tokio::process::Child> {
let mut cmd = Command::new(exe);
cmd.args(args)
.current_dir(cwd)
.env_clear()
.envs(env.iter())
.kill_on_drop(true)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(unix)]
{
cmd.process_group(0);
}
cmd.spawn()
}
pub fn kill_tree(child: &mut tokio::process::Child) {
let Some(pid) = child.id() else { return };
#[cfg(unix)]
{
unsafe {
libc::kill(-(pid as i32), libc::SIGKILL);
}
}
#[cfg(not(unix))]
{
let _ = child.start_kill();
}
}