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)
        }
    }
}