#![doc(html_root_url = "https://docs.rs/process_control/*")]
#![warn(unused_results)]
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io::Read;
use std::io::Result as IoResult;
use std::os::raw::c_uint;
use std::process::Child;
use std::process::ExitStatus as ProcessExitStatus;
use std::time::Duration;
#[cfg(unix)]
#[path = "unix.rs"]
mod imp;
#[cfg(windows)]
#[path = "windows.rs"]
mod imp;
#[derive(Debug)]
pub struct Terminator(imp::Handle);
impl Terminator {
#[inline]
pub unsafe fn terminate(&self) -> IoResult<()> {
self.0.terminate()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ExitStatus(imp::ExitStatus);
impl ExitStatus {
#[inline]
#[must_use]
pub fn success(self) -> bool {
self.0.success()
}
#[inline]
#[must_use]
pub fn code(self) -> Option<c_uint> {
self.0.code()
}
#[cfg(any(unix, doc))]
#[inline]
#[must_use]
pub fn signal(self) -> Option<c_uint> {
self.0.signal()
}
}
impl Display for ExitStatus {
#[inline]
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(formatter)
}
}
impl From<ProcessExitStatus> for ExitStatus {
#[inline]
fn from(status: ProcessExitStatus) -> Self {
Self(status.into())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Output {
pub status: ExitStatus,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
macro_rules! r#impl {
(
$struct:ident $(< $lifetime:lifetime >)? ,
$process_type:ty ,
$return_type:ty ,
$create_result_fn:expr $(,)?
) => {
#[derive(Debug)]
pub struct $struct$(<$lifetime>)? {
process: $process_type,
handle: imp::Handle,
time_limit: Duration,
strict_errors: bool,
terminate: bool,
}
impl$(<$lifetime>)? $struct$(<$lifetime>)? {
fn new(process: $process_type, time_limit: Duration) -> Self {
Self {
handle: imp::Handle::inherited(&process),
process,
time_limit,
strict_errors: false,
terminate: false,
}
}
#[inline]
#[must_use]
pub fn strict_errors(mut self) -> Self {
self.strict_errors = true;
self
}
#[inline]
#[must_use]
pub fn terminating(mut self) -> Self {
self.terminate = true;
self
}
fn run_wait(&mut self) -> IoResult<Option<ExitStatus>> {
let result = self.process.try_wait();
if let Ok(Some(exit_status)) = result {
return Ok(Some(exit_status.into()));
}
let _ = self.process.stdin.take();
let mut result = self
.handle
.wait_with_timeout(self.time_limit)
.map(|x| x.map(ExitStatus));
macro_rules! try_run {
( $result:expr ) => {
let next_result = $result;
if self.strict_errors && result.is_ok() {
if let Err(error) = next_result {
result = Err(error);
}
}
};
}
if self.terminate {
if let Ok(Some(_)) = result {
} else {
try_run!(self.process.kill().and(self.process.wait()));
}
}
try_run!(self.process.try_wait());
result
}
#[inline]
pub fn wait(mut self) -> IoResult<Option<$return_type>> {
self.run_wait()?
.map(|x| $create_result_fn(&mut self, x))
.transpose()
}
}
};
}
r#impl!(
_ExitStatusTimeout<'a>,
&'a mut Child,
ExitStatus,
|_, status| Ok(status),
);
r#impl!(
_OutputTimeout,
Child,
Output,
|timeout: &mut Self, status| {
let mut output = Output {
status,
stdout: Vec::new(),
stderr: Vec::new(),
};
if let Some(stdout) = &mut timeout.process.stdout {
let _ = stdout.read_to_end(&mut output.stdout)?;
}
if let Some(stderr) = &mut timeout.process.stderr {
let _ = stderr.read_to_end(&mut output.stderr)?;
}
Ok(output)
},
);
pub trait ChildExt: private::Sealed {
fn terminator(&self) -> IoResult<Terminator>;
#[must_use]
fn with_timeout(&mut self, time_limit: Duration)
-> _ExitStatusTimeout<'_>;
#[must_use]
fn with_output_timeout(self, time_limit: Duration) -> _OutputTimeout;
}
impl ChildExt for Child {
#[inline]
fn terminator(&self) -> IoResult<Terminator> {
imp::Handle::new(self).map(Terminator)
}
#[inline]
fn with_timeout(
&mut self,
time_limit: Duration,
) -> _ExitStatusTimeout<'_> {
_ExitStatusTimeout::new(self, time_limit)
}
#[inline]
fn with_output_timeout(self, time_limit: Duration) -> _OutputTimeout {
_OutputTimeout::new(self, time_limit)
}
}
mod private {
use std::process::Child;
pub trait Sealed {}
impl Sealed for Child {}
}