rusty_forkfork/child_wrapper.rs
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
10use std::fmt;
11use std::io;
12use std::process::{Child, Output};
13#[cfg(feature = "timeout")]
14use std::time::Duration;
15
16#[cfg(feature = "timeout")]
17use 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)]
27pub struct ExitStatusWrapper(ExitStatusEnum);
28
29#[derive(Debug, Clone, Copy)]
30enum ExitStatusEnum {
31 Std(::std::process::ExitStatus),
32}
33
34impl ExitStatusWrapper {
35 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.
41 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`.
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"))]
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")]
82 pub fn unix_signal(&self) -> Option<i32> {
83 None
84 }
85}
86
87impl fmt::Debug for ExitStatusWrapper {
88 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
95impl fmt::Display for ExitStatusWrapper {
96 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)]
114pub struct ChildWrapper {
115 child: Child,
116 exit_status: Option<ExitStatusWrapper>,
117}
118
119impl ChildWrapper {
120 pub(crate) fn new(child: Child) -> Self {
121 ChildWrapper {
122 child,
123 exit_status: None,
124 }
125 }
126
127 /// Return a reference to the inner `std::process::Child`.
128 ///
129 /// Use care on the returned object, as it does not necessarily reference
130 /// the correct process unless you know the child process has not exited
131 /// and no wait calls have succeeded.
132 pub fn inner(&self) -> &Child {
133 &self.child
134 }
135
136 /// Return a mutable reference to the inner `std::process::Child`.
137 ///
138 /// Use care on the returned object, as it does not necessarily reference
139 /// the correct process unless you know the child process has not exited
140 /// and no wait calls have succeeded.
141 pub fn inner_mut(&mut self) -> &mut Child {
142 &mut self.child
143 }
144
145 /// Forces the child to exit. This is equivalent to sending a SIGKILL on
146 /// unix platforms.
147 ///
148 /// If the process has already been reaped by this handle, returns a
149 /// `NotFound` error.
150 pub fn kill(&mut self) -> io::Result<()> {
151 if self.exit_status.is_none() {
152 self.child.kill()
153 } else {
154 Err(io::Error::new(
155 io::ErrorKind::NotFound,
156 "Process already reaped",
157 ))
158 }
159 }
160
161 /// Returns the OS-assigned processor identifier associated with this child.
162 ///
163 /// This succeeds even if the child has already been reaped. In this case,
164 /// the process id may reference no process at all or even an unrelated
165 /// process.
166 pub fn id(&self) -> u32 {
167 self.child.id()
168 }
169
170 /// Waits for the child to exit completely, returning the status that it
171 /// exited with. This function will continue to have the same return value
172 /// after it has been called at least once.
173 ///
174 /// The stdin handle to the child process, if any, will be closed before
175 /// waiting. This helps avoid deadlock: it ensures that the child does not
176 /// block waiting for input from the parent, while the parent waits for the
177 /// child to exit.
178 ///
179 /// If the child process has already been reaped, returns its exit status
180 /// without blocking.
181 pub fn wait(&mut self) -> io::Result<ExitStatusWrapper> {
182 if let Some(status) = self.exit_status {
183 Ok(status)
184 } else {
185 let status = ExitStatusWrapper::std(self.child.wait()?);
186 self.exit_status = Some(status);
187 Ok(status)
188 }
189 }
190
191 /// Attempts to collect the exit status of the child if it has already exited.
192 ///
193 /// This function will not block the calling thread and will only
194 /// advisorily check to see if the child process has exited or not. If the
195 /// child has exited then on Unix the process id is reaped. This function
196 /// is guaranteed to repeatedly return a successful exit status so long as
197 /// the child has already exited.
198 ///
199 /// If the child has exited, then `Ok(Some(status))` is returned. If the
200 /// exit status is not available at this time then `Ok(None)` is returned.
201 /// If an error occurs, then that error is returned.
202 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatusWrapper>> {
203 if let Some(status) = self.exit_status {
204 Ok(Some(status))
205 } else {
206 let status = self.child.try_wait()?.map(ExitStatusWrapper::std);
207 self.exit_status = status;
208 Ok(status)
209 }
210 }
211
212 /// Simultaneously waits for the child to exit and collect all remaining
213 /// output on the stdout/stderr handles, returning an `Output` instance.
214 ///
215 /// The stdin handle to the child process, if any, will be closed before
216 /// waiting. This helps avoid deadlock: it ensures that the child does not
217 /// block waiting for input from the parent, while the parent waits for the
218 /// child to exit.
219 ///
220 /// By default, stdin, stdout and stderr are inherited from the parent. (In
221 /// the context of `rusty_fork`, they are by default redirected to a file.)
222 /// In order to capture the output into this `Result<Output>` it is
223 /// necessary to create new pipes between parent and child. Use
224 /// `stdout(Stdio::piped())` or `stderr(Stdio::piped())`, respectively.
225 ///
226 /// If the process has already been reaped, returns a `NotFound` error.
227 pub fn wait_with_output(self) -> io::Result<Output> {
228 if self.exit_status.is_some() {
229 return Err(io::Error::new(
230 io::ErrorKind::NotFound,
231 "Process already reaped",
232 ));
233 }
234
235 self.child.wait_with_output()
236 }
237
238 /// Wait for the child to exit, but only up to the given maximum duration.
239 ///
240 /// If the process has already been reaped, returns its exit status
241 /// immediately. Otherwise, if the process terminates within the duration,
242 /// returns `Ok(Sone(..))`, or `Ok(None)` otherwise.
243 ///
244 /// This is only present if the "timeout" feature is enabled.
245 #[cfg(feature = "timeout")]
246 pub fn wait_timeout(&mut self, dur: Duration) -> io::Result<Option<ExitStatusWrapper>> {
247 if let Some(status) = self.exit_status {
248 Ok(Some(status))
249 } else {
250 let status = self.child.wait_timeout(dur)?.map(ExitStatusWrapper::std);
251 self.exit_status = status;
252 Ok(status)
253 }
254 }
255}