#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(
all(feature = "linux_pidfd", target_os = "linux"),
feature(linux_pidfd)
)]
#![warn(missing_docs)]
cfg_if::cfg_if! {
if #[cfg(windows)] {
#[path = "windows.rs"]
mod sys;
} else if #[cfg(target_os = "linux")] {
#[path = "linux.rs"]
mod sys;
} else {
#[path = "unix.rs"]
mod sys;
}
}
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::{ffi::OsStr, io, path::Path, process};
use compio_buf::{BufResult, IntoInner};
use compio_io::AsyncReadExt;
use compio_runtime::Attacher;
use futures_util::future::Either;
#[derive(Debug)]
pub struct Command(process::Command);
impl Command {
pub fn new(program: impl AsRef<OsStr>) -> Self {
Self(process::Command::new(program))
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.0.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.0.args(args);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.0.env(key, val);
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.0.envs(vars);
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.0.env_remove(key);
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.0.env_clear();
self
}
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
self.0.current_dir(dir);
self
}
pub fn stdin<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
self.0.stdin(cfg.try_into()?);
Ok(self)
}
pub fn stdout<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
self.0.stdout(cfg.try_into()?);
Ok(self)
}
pub fn stderr<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
self.0.stderr(cfg.try_into()?);
Ok(self)
}
pub fn get_program(&self) -> &OsStr {
self.0.get_program()
}
pub fn get_args(&self) -> process::CommandArgs {
self.0.get_args()
}
pub fn get_envs(&self) -> process::CommandEnvs {
self.0.get_envs()
}
pub fn get_current_dir(&self) -> Option<&Path> {
self.0.get_current_dir()
}
pub fn spawn(&mut self) -> io::Result<Child> {
#[cfg(all(target_os = "linux", feature = "linux_pidfd"))]
{
use std::os::linux::process::CommandExt;
self.0.create_pidfd(true);
}
let mut child = self.0.spawn()?;
let stdin = if let Some(stdin) = child.stdin.take() {
Some(ChildStdin::new(stdin)?)
} else {
None
};
let stdout = if let Some(stdout) = child.stdout.take() {
Some(ChildStdout::new(stdout)?)
} else {
None
};
let stderr = if let Some(stderr) = child.stderr.take() {
Some(ChildStderr::new(stderr)?)
} else {
None
};
Ok(Child {
child,
stdin,
stdout,
stderr,
})
}
pub async fn status(&mut self) -> io::Result<process::ExitStatus> {
let child = self.spawn()?;
child.wait().await
}
pub async fn output(&mut self) -> io::Result<process::Output> {
let child = self.spawn()?;
child.wait_with_output().await
}
}
#[cfg(windows)]
impl Command {
pub fn creation_flags(&mut self, flags: u32) -> &mut Self {
self.0.creation_flags(flags);
self
}
pub fn raw_arg(&mut self, text_to_append_as_is: impl AsRef<OsStr>) -> &mut Self {
self.0.raw_arg(text_to_append_as_is);
self
}
}
#[cfg(unix)]
impl Command {
pub fn uid(&mut self, id: u32) -> &mut Self {
self.0.uid(id);
self
}
pub fn gid(&mut self, id: u32) -> &mut Self {
self.0.gid(id);
self
}
pub unsafe fn pre_exec(
&mut self,
f: impl FnMut() -> io::Result<()> + Send + Sync + 'static,
) -> &mut Self {
self.0.pre_exec(f);
self
}
pub fn exec(&mut self) -> io::Error {
self.0.exec()
}
pub fn arg0(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.0.arg0(arg);
self
}
pub fn process_group(&mut self, pgroup: i32) -> &mut Self {
self.0.process_group(pgroup);
self
}
}
pub struct Child {
child: process::Child,
pub stdin: Option<ChildStdin>,
pub stdout: Option<ChildStdout>,
pub stderr: Option<ChildStderr>,
}
impl Child {
pub fn kill(&mut self) -> io::Result<()> {
self.child.kill()
}
pub fn id(&self) -> u32 {
self.child.id()
}
pub async fn wait(self) -> io::Result<process::ExitStatus> {
sys::child_wait(self.child).await
}
pub async fn wait_with_output(mut self) -> io::Result<process::Output> {
let status = sys::child_wait(self.child);
let stdout = if let Some(stdout) = &mut self.stdout {
Either::Left(stdout.read_to_end(vec![]))
} else {
Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
};
let stderr = if let Some(stderr) = &mut self.stderr {
Either::Left(stderr.read_to_end(vec![]))
} else {
Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
};
let (status, BufResult(out_res, stdout), BufResult(err_res, stderr)) =
futures_util::future::join3(status, stdout, stderr).await;
let status = status?;
out_res?;
err_res?;
Ok(process::Output {
status,
stdout,
stderr,
})
}
}
pub struct ChildStdout(Attacher<process::ChildStdout>);
impl ChildStdout {
fn new(stdout: process::ChildStdout) -> io::Result<Self> {
Attacher::new(stdout).map(Self)
}
}
impl TryFrom<ChildStdout> for process::Stdio {
type Error = ChildStdout;
fn try_from(value: ChildStdout) -> Result<Self, ChildStdout> {
value
.0
.into_inner()
.try_unwrap()
.map(Self::from)
.map_err(|fd| ChildStdout(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
}
}
pub struct ChildStderr(Attacher<process::ChildStderr>);
impl ChildStderr {
fn new(stderr: process::ChildStderr) -> io::Result<Self> {
Attacher::new(stderr).map(Self)
}
}
impl TryFrom<ChildStderr> for process::Stdio {
type Error = ChildStderr;
fn try_from(value: ChildStderr) -> Result<Self, ChildStderr> {
value
.0
.into_inner()
.try_unwrap()
.map(Self::from)
.map_err(|fd| ChildStderr(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
}
}
pub struct ChildStdin(Attacher<process::ChildStdin>);
impl ChildStdin {
fn new(stdin: process::ChildStdin) -> io::Result<Self> {
Attacher::new(stdin).map(Self)
}
}
impl TryFrom<ChildStdin> for process::Stdio {
type Error = ChildStdin;
fn try_from(value: ChildStdin) -> Result<Self, ChildStdin> {
value
.0
.into_inner()
.try_unwrap()
.map(Self::from)
.map_err(|fd| ChildStdin(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
}
}