blob: 807b2e48d81df2c3bc86071e0004e06df2c5a4de [file] [log] [blame]
//-
// Copyright 2018 Jason Lingle
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::fmt;
use std::io;
use std::process::{Child, Output};
#[cfg(feature = "timeout")]
use std::time::Duration;
#[cfg(feature = "timeout")]
use wait_timeout::{self, ChildExt};
/// Wraps `std::process::ExitStatus` and (if enabled)
/// `wait_timeout::ExitStatus` to give a uniform interface to both.
///
/// Method documentation is copied from the [Rust std
/// docs](https://doc.rust-lang.org/stable/std/process/struct.ExitStatus.html)
/// and the [`wait_timeout`
/// docs](https://docs.rs/wait-timeout/0.1.5/wait_timeout/struct.ExitStatus.html)
#[derive(Clone, Copy)]
pub struct ExitStatusWrapper(ExitStatusEnum);
#[derive(Debug, Clone, Copy)]
enum ExitStatusEnum {
Std(::std::process::ExitStatus),
#[cfg(feature = "timeout")]
Wt(wait_timeout::ExitStatus),
}
impl ExitStatusWrapper {
fn std(es: ::std::process::ExitStatus) -> Self {
ExitStatusWrapper(ExitStatusEnum::Std(es))
}
#[cfg(feature = "timeout")]
fn wt(es: wait_timeout::ExitStatus) -> Self {
ExitStatusWrapper(ExitStatusEnum::Wt(es))
}
/// Was termination successful? Signal termination is not considered a
/// success, and success is defined as a zero exit status.
pub fn success(&self) -> bool {
match self.0 {
ExitStatusEnum::Std(es) => es.success(),
#[cfg(feature = "timeout")]
ExitStatusEnum::Wt(es) => es.success(),
}
}
/// Returns the exit code of the process, if any.
///
/// On Unix, this will return `None` if the process was terminated by a
/// signal; `std::os::unix` provides an extension trait for extracting the
/// signal and other details from the `ExitStatus`.
pub fn code(&self) -> Option<i32> {
match self.0 {
ExitStatusEnum::Std(es) => es.code(),
#[cfg(feature = "timeout")]
ExitStatusEnum::Wt(es) => es.code(),
}
}
/// Returns the Unix signal which terminated this process.
///
/// Note that on Windows this will always return None and on Unix this will
/// return None if the process successfully exited otherwise.
///
/// For simplicity and to match `wait_timeout`, this method is always
/// present even on systems that do not support it.
#[cfg(not(target_os = "windows"))]
pub fn unix_signal(&self) -> Option<i32> {
use std::os::unix::process::ExitStatusExt;
match self.0 {
ExitStatusEnum::Std(es) => es.signal(),
#[cfg(feature = "timeout")]
ExitStatusEnum::Wt(es) => es.unix_signal(),
}
}
/// Returns the Unix signal which terminated this process.
///
/// Note that on Windows this will always return None and on Unix this will
/// return None if the process successfully exited otherwise.
///
/// For simplicity and to match `wait_timeout`, this method is always
/// present even on systems that do not support it.
#[cfg(target_os = "windows")]
pub fn unix_signal(&self) -> Option<i32> {
None
}
}
impl fmt::Debug for ExitStatusWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ExitStatusEnum::Std(ref es) => fmt::Debug::fmt(es, f),
#[cfg(feature = "timeout")]
ExitStatusEnum::Wt(ref es) => fmt::Debug::fmt(es, f),
}
}
}
impl fmt::Display for ExitStatusWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ExitStatusEnum::Std(ref es) => fmt::Display::fmt(es, f),
#[cfg(feature = "timeout")]
ExitStatusEnum::Wt(ref es) => fmt::Display::fmt(es, f),
}
}
}
/// Wraps a `std::process::Child` to coordinate state between `std` and
/// `wait_timeout`.
///
/// This is necessary because the completion of a call to
/// `wait_timeout::ChildExt::wait_timeout` leaves the `Child` in an
/// inconsistent state, as it does not know the child has exited, and on Unix
/// may end up referencing another process.
///
/// Documentation for this struct's methods is largely copied from the [Rust
/// std docs](https://doc.rust-lang.org/stable/std/process/struct.Child.html).
#[derive(Debug)]
pub struct ChildWrapper {
child: Child,
exit_status: Option<ExitStatusWrapper>,
}
impl ChildWrapper {
pub(crate) fn new(child: Child) -> Self {
ChildWrapper { child, exit_status: None }
}
/// Return a reference to the inner `std::process::Child`.
///
/// Use care on the returned object, as it does not necessarily reference
/// the correct process unless you know the child process has not exited
/// and no wait calls have succeeded.
pub fn inner(&self) -> &Child {
&self.child
}
/// Return a mutable reference to the inner `std::process::Child`.
///
/// Use care on the returned object, as it does not necessarily reference
/// the correct process unless you know the child process has not exited
/// and no wait calls have succeeded.
pub fn inner_mut(&mut self) -> &mut Child {
&mut self.child
}
/// Forces the child to exit. This is equivalent to sending a SIGKILL on
/// unix platforms.
///
/// If the process has already been reaped by this handle, returns a
/// `NotFound` error.
pub fn kill(&mut self) -> io::Result<()> {
if self.exit_status.is_none() {
self.child.kill()
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "Process already reaped"))
}
}
/// Returns the OS-assigned processor identifier associated with this child.
///
/// This succeeds even if the child has already been reaped. In this case,
/// the process id may reference no process at all or even an unrelated
/// process.
pub fn id(&self) -> u32 {
self.child.id()
}
/// Waits for the child to exit completely, returning the status that it
/// exited with. This function will continue to have the same return value
/// after it has been called at least once.
///
/// The stdin handle to the child process, if any, will be closed before
/// waiting. This helps avoid deadlock: it ensures that the child does not
/// block waiting for input from the parent, while the parent waits for the
/// child to exit.
///
/// If the child process has already been reaped, returns its exit status
/// without blocking.
pub fn wait(&mut self) -> io::Result<ExitStatusWrapper> {
if let Some(status) = self.exit_status {
Ok(status)
} else {
let status = ExitStatusWrapper::std(self.child.wait()?);
self.exit_status = Some(status);
Ok(status)
}
}
/// Attempts to collect the exit status of the child if it has already exited.
///
/// This function will not block the calling thread and will only
/// advisorily check to see if the child process has exited or not. If the
/// child has exited then on Unix the process id is reaped. This function
/// is guaranteed to repeatedly return a successful exit status so long as
/// the child has already exited.
///
/// If the child has exited, then `Ok(Some(status))` is returned. If the
/// exit status is not available at this time then `Ok(None)` is returned.
/// If an error occurs, then that error is returned.
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatusWrapper>> {
if let Some(status) = self.exit_status {
Ok(Some(status))
} else {
let status = self.child.try_wait()?.map(ExitStatusWrapper::std);
self.exit_status = status;
Ok(status)
}
}
/// Simultaneously waits for the child to exit and collect all remaining
/// output on the stdout/stderr handles, returning an `Output` instance.
///
/// The stdin handle to the child process, if any, will be closed before
/// waiting. This helps avoid deadlock: it ensures that the child does not
/// block waiting for input from the parent, while the parent waits for the
/// child to exit.
///
/// By default, stdin, stdout and stderr are inherited from the parent. (In
/// the context of `rusty_fork`, they are by default redirected to a file.)
/// In order to capture the output into this `Result<Output>` it is
/// necessary to create new pipes between parent and child. Use
/// `stdout(Stdio::piped())` or `stderr(Stdio::piped())`, respectively.
///
/// If the process has already been reaped, returns a `NotFound` error.
pub fn wait_with_output(self) -> io::Result<Output> {
if self.exit_status.is_some() {
return Err(io::Error::new(
io::ErrorKind::NotFound, "Process already reaped"));
}
self.child.wait_with_output()
}
/// Wait for the child to exit, but only up to the given maximum duration.
///
/// If the process has already been reaped, returns its exit status
/// immediately. Otherwise, if the process terminates within the duration,
/// returns `Ok(Sone(..))`, or `Ok(None)` otherwise.
///
/// This is only present if the "timeout" feature is enabled.
#[cfg(feature = "timeout")]
pub fn wait_timeout(&mut self, dur: Duration)
-> io::Result<Option<ExitStatusWrapper>> {
if let Some(status) = self.exit_status {
Ok(Some(status))
} else {
let status = self.child.wait_timeout(dur)?.map(ExitStatusWrapper::wt);
self.exit_status = status;
Ok(status)
}
}
}