use std::process::Stdio;
use anyhow::Context;
use tocat_api::StderrMode;
use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command};
use tracing::{debug, warn};
use crate::endpoint::size_if_pipe;
pub struct ChildParts {
pub child: Child,
pub stdin: ChildStdin,
pub stdout: ChildStdout,
pub stderr: Option<ChildStderr>,
}
pub fn spawn(
program: &str,
args: &[String],
shell: bool,
stderr: StderrMode,
pipe_size: usize,
) -> anyhow::Result<ChildParts> {
let mut cmd = if shell {
let sh = std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
let mut c = Command::new(sh);
c.arg("-c").arg(program);
c
} else {
let mut c = Command::new(program);
c.args(args);
c
};
cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(match stderr {
StderrMode::Inherit => Stdio::inherit(),
StderrMode::Log => Stdio::piped(),
StderrMode::Null => Stdio::null(),
})
.kill_on_drop(true);
let mut child = cmd.spawn().with_context(|| format!("spawning {program}"))?;
let stdin = child.stdin.take().expect("piped");
let stdout = child.stdout.take().expect("piped");
size_if_pipe(&stdin, "child stdin", pipe_size);
size_if_pipe(&stdout, "child stdout", pipe_size);
Ok(ChildParts {
stdin,
stdout,
stderr: child.stderr.take(),
child,
})
}
pub fn reap_in_background(mut child: Child) {
let pid = child.id();
tokio::spawn(async move {
match child.wait().await {
Ok(status) if status.success() => debug!(?pid, "child exited"),
Ok(status) => warn!(?pid, %status, "child exited non-zero"),
Err(e) => warn!(?pid, error = %e, "waiting on child failed"),
}
});
}