rusty_fork/child_wrapper.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
//-
// 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::ChildExt;
/// Wraps `std::process::ExitStatus`. Historically, this was due to the
/// `wait_timeout` crate having its own `ExitStatus` type.
///
/// 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),
}
impl ExitStatusWrapper {
fn std(es: ::std::process::ExitStatus) -> Self {
ExitStatusWrapper(ExitStatusEnum::Std(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(),
}
}
/// 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(),
}
}
/// 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(),
}
}
/// 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),
}
}
}
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),
}
}
}
/// 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::std);
self.exit_status = status;
Ok(status)
}
}
}