1 //-
2 // Copyright 2018 Jason Lingle
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9 
10 use std::fmt;
11 use std::io;
12 use std::process::{Child, Output};
13 #[cfg(feature = "timeout")]
14 use std::time::Duration;
15 
16 #[cfg(feature = "timeout")]
17 use wait_timeout::ChildExt;
18 
19 /// Wraps `std::process::ExitStatus`. Historically, this was due to the
20 /// `wait_timeout` crate having its own `ExitStatus` type.
21 ///
22 /// Method documentation is copied from the [Rust std
23 /// docs](https://doc.rust-lang.org/stable/std/process/struct.ExitStatus.html)
24 /// and the [`wait_timeout`
25 /// docs](https://docs.rs/wait-timeout/0.1.5/wait_timeout/struct.ExitStatus.html).
26 #[derive(Clone, Copy)]
27 pub struct ExitStatusWrapper(ExitStatusEnum);
28 
29 #[derive(Debug, Clone, Copy)]
30 enum ExitStatusEnum {
31     Std(::std::process::ExitStatus),
32 }
33 
34 impl ExitStatusWrapper {
std(es: ::std::process::ExitStatus) -> Self35     fn std(es: ::std::process::ExitStatus) -> Self {
36         ExitStatusWrapper(ExitStatusEnum::Std(es))
37     }
38 
39     /// Was termination successful? Signal termination is not considered a
40     /// success, and success is defined as a zero exit status.
success(&self) -> bool41     pub fn success(&self) -> bool {
42         match self.0 {
43             ExitStatusEnum::Std(es) => es.success(),
44         }
45     }
46 
47     /// Returns the exit code of the process, if any.
48     ///
49     /// On Unix, this will return `None` if the process was terminated by a
50     /// signal; `std::os::unix` provides an extension trait for extracting the
51     /// signal and other details from the `ExitStatus`.
code(&self) -> Option<i32>52     pub fn code(&self) -> Option<i32> {
53         match self.0 {
54             ExitStatusEnum::Std(es) => es.code(),
55         }
56     }
57 
58     /// Returns the Unix signal which terminated this process.
59     ///
60     /// Note that on Windows this will always return None and on Unix this will
61     /// return None if the process successfully exited otherwise.
62     ///
63     /// For simplicity and to match `wait_timeout`, this method is always
64     /// present even on systems that do not support it.
65     #[cfg(not(target_os = "windows"))]
unix_signal(&self) -> Option<i32>66     pub fn unix_signal(&self) -> Option<i32> {
67         use std::os::unix::process::ExitStatusExt;
68 
69         match self.0 {
70             ExitStatusEnum::Std(es) => es.signal(),
71         }
72     }
73 
74     /// Returns the Unix signal which terminated this process.
75     ///
76     /// Note that on Windows this will always return None and on Unix this will
77     /// return None if the process successfully exited otherwise.
78     ///
79     /// For simplicity and to match `wait_timeout`, this method is always
80     /// present even on systems that do not support it.
81     #[cfg(target_os = "windows")]
unix_signal(&self) -> Option<i32>82     pub fn unix_signal(&self) -> Option<i32> {
83         None
84     }
85 }
86 
87 impl fmt::Debug for ExitStatusWrapper {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result88     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89         match self.0 {
90             ExitStatusEnum::Std(ref es) => fmt::Debug::fmt(es, f),
91         }
92     }
93 }
94 
95 impl fmt::Display for ExitStatusWrapper {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result96     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97         match self.0 {
98             ExitStatusEnum::Std(ref es) => fmt::Display::fmt(es, f),
99         }
100     }
101 }
102 
103 /// Wraps a `std::process::Child` to coordinate state between `std` and
104 /// `wait_timeout`.
105 ///
106 /// This is necessary because the completion of a call to
107 /// `wait_timeout::ChildExt::wait_timeout` leaves the `Child` in an
108 /// inconsistent state, as it does not know the child has exited, and on Unix
109 /// may end up referencing another process.
110 ///
111 /// Documentation for this struct's methods is largely copied from the [Rust
112 /// std docs](https://doc.rust-lang.org/stable/std/process/struct.Child.html).
113 #[derive(Debug)]
114 pub struct ChildWrapper {
115     child: Child,
116     exit_status: Option<ExitStatusWrapper>,
117 }
118 
119 impl ChildWrapper {
new(child: Child) -> Self120     pub(crate) fn new(child: Child) -> Self {
121         ChildWrapper { child, exit_status: None }
122     }
123 
124     /// Return a reference to the inner `std::process::Child`.
125     ///
126     /// Use care on the returned object, as it does not necessarily reference
127     /// the correct process unless you know the child process has not exited
128     /// and no wait calls have succeeded.
inner(&self) -> &Child129     pub fn inner(&self) -> &Child {
130         &self.child
131     }
132 
133     /// Return a mutable reference to the inner `std::process::Child`.
134     ///
135     /// Use care on the returned object, as it does not necessarily reference
136     /// the correct process unless you know the child process has not exited
137     /// and no wait calls have succeeded.
inner_mut(&mut self) -> &mut Child138     pub fn inner_mut(&mut self) -> &mut Child {
139         &mut self.child
140     }
141 
142     /// Forces the child to exit. This is equivalent to sending a SIGKILL on
143     /// unix platforms.
144     ///
145     /// If the process has already been reaped by this handle, returns a
146     /// `NotFound` error.
kill(&mut self) -> io::Result<()>147     pub fn kill(&mut self) -> io::Result<()> {
148         if self.exit_status.is_none() {
149             self.child.kill()
150         } else {
151             Err(io::Error::new(io::ErrorKind::NotFound, "Process already reaped"))
152         }
153     }
154 
155     /// Returns the OS-assigned processor identifier associated with this child.
156     ///
157     /// This succeeds even if the child has already been reaped. In this case,
158     /// the process id may reference no process at all or even an unrelated
159     /// process.
id(&self) -> u32160     pub fn id(&self) -> u32 {
161         self.child.id()
162     }
163 
164     /// Waits for the child to exit completely, returning the status that it
165     /// exited with. This function will continue to have the same return value
166     /// after it has been called at least once.
167     ///
168     /// The stdin handle to the child process, if any, will be closed before
169     /// waiting. This helps avoid deadlock: it ensures that the child does not
170     /// block waiting for input from the parent, while the parent waits for the
171     /// child to exit.
172     ///
173     /// If the child process has already been reaped, returns its exit status
174     /// without blocking.
wait(&mut self) -> io::Result<ExitStatusWrapper>175     pub fn wait(&mut self) -> io::Result<ExitStatusWrapper> {
176         if let Some(status) = self.exit_status {
177             Ok(status)
178         } else {
179             let status = ExitStatusWrapper::std(self.child.wait()?);
180             self.exit_status = Some(status);
181             Ok(status)
182         }
183     }
184 
185     /// Attempts to collect the exit status of the child if it has already exited.
186     ///
187     /// This function will not block the calling thread and will only
188     /// advisorily check to see if the child process has exited or not. If the
189     /// child has exited then on Unix the process id is reaped. This function
190     /// is guaranteed to repeatedly return a successful exit status so long as
191     /// the child has already exited.
192     ///
193     /// If the child has exited, then `Ok(Some(status))` is returned. If the
194     /// exit status is not available at this time then `Ok(None)` is returned.
195     /// If an error occurs, then that error is returned.
try_wait(&mut self) -> io::Result<Option<ExitStatusWrapper>>196     pub fn try_wait(&mut self) -> io::Result<Option<ExitStatusWrapper>> {
197         if let Some(status) = self.exit_status {
198             Ok(Some(status))
199         } else {
200             let status = self.child.try_wait()?.map(ExitStatusWrapper::std);
201             self.exit_status = status;
202             Ok(status)
203         }
204     }
205 
206     /// Simultaneously waits for the child to exit and collect all remaining
207     /// output on the stdout/stderr handles, returning an `Output` instance.
208     ///
209     /// The stdin handle to the child process, if any, will be closed before
210     /// waiting. This helps avoid deadlock: it ensures that the child does not
211     /// block waiting for input from the parent, while the parent waits for the
212     /// child to exit.
213     ///
214     /// By default, stdin, stdout and stderr are inherited from the parent. (In
215     /// the context of `rusty_fork`, they are by default redirected to a file.)
216     /// In order to capture the output into this `Result<Output>` it is
217     /// necessary to create new pipes between parent and child. Use
218     /// `stdout(Stdio::piped())` or `stderr(Stdio::piped())`, respectively.
219     ///
220     /// If the process has already been reaped, returns a `NotFound` error.
wait_with_output(self) -> io::Result<Output>221     pub fn wait_with_output(self) -> io::Result<Output> {
222         if self.exit_status.is_some() {
223             return Err(io::Error::new(
224                 io::ErrorKind::NotFound, "Process already reaped"));
225         }
226 
227         self.child.wait_with_output()
228     }
229 
230     /// Wait for the child to exit, but only up to the given maximum duration.
231     ///
232     /// If the process has already been reaped, returns its exit status
233     /// immediately. Otherwise, if the process terminates within the duration,
234     /// returns `Ok(Sone(..))`, or `Ok(None)` otherwise.
235     ///
236     /// This is only present if the "timeout" feature is enabled.
237     #[cfg(feature = "timeout")]
wait_timeout(&mut self, dur: Duration) -> io::Result<Option<ExitStatusWrapper>>238     pub fn wait_timeout(&mut self, dur: Duration)
239                         -> io::Result<Option<ExitStatusWrapper>> {
240         if let Some(status) = self.exit_status {
241             Ok(Some(status))
242         } else {
243             let status = self.child.wait_timeout(dur)?.map(ExitStatusWrapper::std);
244             self.exit_status = status;
245             Ok(status)
246         }
247     }
248 }
249