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