blob: a236d67637c427172d473f8316b586235135f8d5 [file] [log] [blame]
use std::{
fmt,
io::{Read, Result},
process::{Child, ExitStatus, Output},
};
#[cfg(unix)]
pub(self) use unix::ChildImp;
#[cfg(windows)]
pub(self) use windows::ChildImp;
#[cfg(unix)]
use crate::UnixChildExt;
#[cfg(unix)]
use nix::sys::signal::Signal;
#[cfg(windows)]
use winapi::um::winnt::HANDLE;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
/// Representation of a running or exited child process group.
///
/// This wraps the [`Child`] type in the standard library with methods that work
/// with process groups.
///
/// # Examples
///
/// ```should_panic
/// use std::process::Command;
/// use command_group::CommandGroup;
///
/// let mut child = Command::new("/bin/cat")
/// .arg("file.txt")
/// .group_spawn()
/// .expect("failed to execute child");
///
/// let ecode = child.wait()
/// .expect("failed to wait on child");
///
/// assert!(ecode.success());
/// ```
pub struct GroupChild {
imp: ChildImp,
exitstatus: Option<ExitStatus>,
}
impl fmt::Debug for GroupChild {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("GroupChild").finish()
}
}
impl GroupChild {
#[cfg(unix)]
pub(crate) fn new(inner: Child) -> Self {
Self {
imp: ChildImp::new(inner),
exitstatus: None,
}
}
#[cfg(windows)]
pub(crate) fn new(inner: Child, j: HANDLE, c: HANDLE) -> Self {
Self {
imp: ChildImp::new(inner, j, c),
exitstatus: None,
}
}
/// Returns the stdlib [`Child`] object.
///
/// Note that the inner child may not be in the same state as this output child, due to how
/// methods like `wait` and `kill` are implemented. It is not recommended to use this method
/// _after_ using any of the other methods on this struct.
///
/// # Examples
///
/// Reading from stdout:
///
/// ```no_run
/// use std::io::Read;
/// use std::process::{Command, Stdio};
/// use command_group::CommandGroup;
///
/// let mut child = Command::new("ls").stdout(Stdio::piped()).group_spawn().expect("ls command didn't start");
/// let mut output = String::new();
/// if let Some(mut out) = child.inner().stdout.take() {
/// out.read_to_string(&mut output).expect("failed to read from child");
/// }
/// println!("output: {}", output);
/// ```
pub fn inner(&mut self) -> &mut Child {
self.imp.inner()
}
/// Consumes itself and returns the stdlib [`Child`] object.
///
/// Note that the inner child may not be in the same state as this output child, due to how
/// methods like `wait` and `kill` are implemented. It is not recommended to use this method
/// _after_ using any of the other methods on this struct.
///
#[cfg_attr(
windows,
doc = "On Windows, this unnavoidably leaves a handle unclosed. Prefer [`inner()`](Self::inner)."
)]
///
/// # Examples
///
/// Writing to input:
///
/// ```no_run
/// use std::io::Write;
/// use std::process::{Command, Stdio};
/// use command_group::CommandGroup;
///
/// let mut child = Command::new("cat").stdin(Stdio::piped()).group_spawn().expect("cat command didn't start");
/// if let Some(mut din) = child.into_inner().stdin.take() {
/// din.write_all(b"Woohoo!").expect("failed to write");
/// }
/// ```
pub fn into_inner(self) -> Child {
self.imp.into_inner()
}
/// Forces the child process group to exit. If the group has already exited, an [`InvalidInput`]
/// error is returned.
///
/// This is equivalent to sending a SIGKILL on Unix platforms.
///
/// See [the stdlib documentation](Child::kill) for more.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use std::process::Command;
/// use command_group::CommandGroup;
///
/// let mut command = Command::new("yes");
/// if let Ok(mut child) = command.group_spawn() {
/// child.kill().expect("command wasn't running");
/// } else {
/// println!("yes command didn't start");
/// }
/// ```
///
/// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
pub fn kill(&mut self) -> Result<()> {
self.imp.kill()
}
/// Returns the OS-assigned process group identifier.
///
/// See [the stdlib documentation](Child::id) for more.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use std::process::Command;
/// use command_group::CommandGroup;
///
/// let mut command = Command::new("ls");
/// if let Ok(child) = command.group_spawn() {
/// println!("Child group's ID is {}", child.id());
/// } else {
/// println!("ls command didn't start");
/// }
/// ```
pub fn id(&self) -> u32 {
self.imp.id()
}
/// Waits for the child group to exit completely, returning the status that
/// the process leader exited with.
///
/// See [the stdlib documentation](Child::wait) for more.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use std::process::Command;
/// use command_group::CommandGroup;
///
/// let mut command = Command::new("ls");
/// if let Ok(mut child) = command.group_spawn() {
/// child.wait().expect("command wasn't running");
/// println!("Child has finished its execution!");
/// } else {
/// println!("ls command didn't start");
/// }
/// ```
pub fn wait(&mut self) -> Result<ExitStatus> {
if let Some(es) = self.exitstatus {
return Ok(es);
}
drop(self.imp.take_stdin());
let status = self.imp.wait()?;
self.exitstatus = Some(status);
Ok(status)
}
/// Attempts to collect the exit status of the child if it has already
/// exited.
///
/// See [the stdlib documentation](Child::try_wait) for more.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use std::process::Command;
/// use command_group::CommandGroup;
///
/// let mut child = Command::new("ls").group_spawn().unwrap();
///
/// match child.try_wait() {
/// Ok(Some(status)) => println!("exited with: {}", status),
/// Ok(None) => {
/// println!("status not ready yet, let's really wait");
/// let res = child.wait();
/// println!("result: {:?}", res);
/// }
/// Err(e) => println!("error attempting to wait: {}", e),
/// }
/// ```
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
if self.exitstatus.is_some() {
return Ok(self.exitstatus);
}
match self.imp.try_wait()? {
Some(es) => {
self.exitstatus = Some(es);
Ok(Some(es))
}
None => Ok(None),
}
}
/// Simultaneously waits for the child to exit and collect all remaining
/// output on the stdout/stderr handles, returning an `Output`
/// instance.
///
/// See [the stdlib documentation](Child::wait_with_output) for more.
///
/// # Bugs
///
/// On Windows, STDOUT is read before STDERR if both are piped, which may block. This is mostly
/// because reading two outputs at the same time in synchronous code is horrendous. If you want
/// this, please contribute a better version. Alternatively, prefer using the async API.
///
/// # Examples
///
/// Basic usage:
///
/// ```should_panic
/// use std::process::{Command, Stdio};
/// use command_group::CommandGroup;
///
/// let child = Command::new("/bin/cat")
/// .arg("file.txt")
/// .stdout(Stdio::piped())
/// .group_spawn()
/// .expect("failed to execute child");
///
/// let output = child
/// .wait_with_output()
/// .expect("failed to wait on child");
///
/// assert!(output.status.success());
/// ```
pub fn wait_with_output(mut self) -> Result<Output> {
drop(self.imp.take_stdin());
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
match (self.imp.take_stdout(), self.imp.take_stderr()) {
(None, None) => {}
(Some(mut out), None) => {
out.read_to_end(&mut stdout)?;
}
(None, Some(mut err)) => {
err.read_to_end(&mut stderr)?;
}
(Some(out), Some(err)) => {
let res = ChildImp::read_both(out, &mut stdout, err, &mut stderr);
res.unwrap();
}
}
let status = self.imp.wait()?;
Ok(Output {
status,
stdout,
stderr,
})
}
}
#[cfg(unix)]
impl UnixChildExt for GroupChild {
fn signal(&mut self, sig: Signal) -> Result<()> {
self.imp.signal_imp(sig)
}
}