use core::future::poll_fn;
use core::pin::Pin;
use core::task::{Context, Poll};
use std::ffi::OsStr;
use std::io;
use std::path::Path;
use std::sync::Arc;
use futures_io::AsyncRead;
pub use std::process::{ExitStatus, Output, Stdio};
pub(crate) mod exit;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod fallback;
mod stdio;
#[cfg(test)]
mod tests;
pub use stdio::{ChildStderr, ChildStdin, ChildStdout};
use exit::Exit;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn reap_fallback() {
fallback::reap();
}
#[derive(Debug)]
pub struct Command {
std: std::process::Command,
stdin_set: bool,
kill_on_drop: bool,
}
impl Command {
pub fn new(program: impl AsRef<OsStr>) -> Self {
Self {
std: std::process::Command::new(program),
stdin_set: false,
kill_on_drop: false,
}
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.std.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.std.args(args);
self
}
pub fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
self.std.env(key, value);
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.std.envs(vars);
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.std.env_remove(key);
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.std.env_clear();
self
}
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
self.std.current_dir(dir);
self
}
pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.std.stdin(cfg);
self.stdin_set = true;
self
}
pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.std.stdout(cfg);
self
}
pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.std.stderr(cfg);
self
}
pub fn kill_on_drop(&mut self, kill: bool) -> &mut Self {
self.kill_on_drop = kill;
self
}
pub fn get_kill_on_drop(&self) -> bool {
self.kill_on_drop
}
pub fn as_std(&self) -> &std::process::Command {
&self.std
}
pub fn as_std_mut(&mut self) -> &mut std::process::Command {
&mut self.std
}
pub fn into_std(self) -> std::process::Command {
self.std
}
pub fn spawn(&mut self) -> io::Result<Child> {
self.spawn_with(std::process::Command::spawn)
}
pub fn spawn_with(
&mut self,
with: impl FnOnce(&mut std::process::Command) -> io::Result<std::process::Child>,
) -> io::Result<Child> {
let mut std = with(&mut self.std)?;
let exit = match Exit::new(&std) {
Ok(exit) => exit,
Err(e) => {
if self.kill_on_drop {
let _ = std.kill();
}
return Err(e);
}
};
let (stdin, stdout, stderr) = (std.stdin.take(), std.stdout.take(), std.stderr.take());
let mut child = Child {
stdin: None,
stdout: None,
stderr: None,
exit,
kill_on_drop: self.kill_on_drop,
_std: std,
};
child.stdin = stdin.map(ChildStdin::new).transpose()?;
child.stdout = stdout.map(ChildStdout::new).transpose()?;
child.stderr = stderr.map(ChildStderr::new).transpose()?;
Ok(child)
}
pub async fn status(&mut self) -> io::Result<ExitStatus> {
self.spawn()?.wait().await
}
pub async fn output(&mut self) -> io::Result<Output> {
if !self.stdin_set {
self.std.stdin(Stdio::null());
}
self.std.stdout(Stdio::piped()).stderr(Stdio::piped());
self.spawn()?.wait_with_output().await
}
}
impl From<std::process::Command> for Command {
fn from(std: std::process::Command) -> Self {
Self {
std,
stdin_set: false,
kill_on_drop: false,
}
}
}
#[derive(Debug)]
pub struct Child {
pub stdin: Option<ChildStdin>,
pub stdout: Option<ChildStdout>,
pub stderr: Option<ChildStderr>,
exit: Arc<Exit>,
kill_on_drop: bool,
_std: std::process::Child,
}
impl core::fmt::Debug for Exit {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Exit")
.field("pid", &self.pid())
.finish_non_exhaustive()
}
}
impl Child {
pub fn id(&self) -> u32 {
self.exit.pid()
}
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
drop(self.stdin.take());
poll_fn(|cx| self.exit.poll_wait(cx)).await
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
self.exit.try_wait()
}
pub fn start_kill(&mut self) -> io::Result<()> {
self.exit.kill()
}
pub async fn kill(&mut self) -> io::Result<()> {
self.start_kill()?;
self.wait().await.map(drop)
}
pub async fn wait_with_output(mut self) -> io::Result<Output> {
drop(self.stdin.take());
let (mut stdout, mut stderr) = (self.stdout.take(), self.stderr.take());
let (mut out, mut err) = (Vec::new(), Vec::new());
poll_fn(|cx| {
let out_done = drain(&mut stdout, &mut out, cx)?.is_ready();
let err_done = drain(&mut stderr, &mut err, cx)?.is_ready();
if out_done && err_done {
Poll::Ready(Ok::<(), io::Error>(()))
} else {
Poll::Pending
}
})
.await?;
let status = self.wait().await?;
Ok(Output {
status,
stdout: out,
stderr: err,
})
}
}
impl Drop for Child {
fn drop(&mut self) {
if self.kill_on_drop {
let _ = self.exit.kill();
}
self.exit.went();
}
}
fn drain<R: AsyncRead + Unpin>(
pipe: &mut Option<R>,
into: &mut Vec<u8>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
const STEP: usize = 16 * 1024;
while let Some(reader) = pipe.as_mut() {
let at = into.len();
into.resize(at + STEP, 0);
let read = Pin::new(reader).poll_read(cx, &mut into[at..]);
match read {
Poll::Ready(Ok(0)) => {
into.truncate(at);
*pipe = None;
}
Poll::Ready(Ok(n)) => into.truncate(at + n),
Poll::Ready(Err(err)) => {
into.truncate(at);
return Poll::Ready(Err(err));
}
Poll::Pending => {
into.truncate(at);
return Poll::Pending;
}
}
}
Poll::Ready(Ok(()))
}