/* * This file is part of the uutils coreutils package. * * (c) Maciej Dziardziel * * For the full copyright and license information, please view the LICENSE file * that was distributed with this source code. */ extern crate libc; extern crate time; use libc::{c_int, pid_t}; use std::fmt; use std::io; use std::process::Child; use std::sync::{Arc, Condvar, Mutex}; use std::thread; use time::{Duration, get_time}; // This is basically sys::unix::process::ExitStatus #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum ExitStatus { Code(i32), Signal(i32), } impl ExitStatus { fn from_status(status: c_int) -> ExitStatus { if status & 0x7F != 0 { // WIFSIGNALED(status) ExitStatus::Signal(status & 0x7F) } else { ExitStatus::Code(status & 0xFF00 >> 8) } } pub fn success(&self) -> bool { match *self { ExitStatus::Code(code) => code == 0, _ => false, } } pub fn code(&self) -> Option { match *self { ExitStatus::Code(code) => Some(code), _ => None, } } pub fn signal(&self) -> Option { match *self { ExitStatus::Signal(code) => Some(code), _ => None, } } } impl fmt::Display for ExitStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ExitStatus::Code(code) => write!(f, "exit code: {}", code), ExitStatus::Signal(code) => write!(f, "exit code: {}", code), } } } /// Missing methods for Child objects pub trait ChildExt { /// Send a signal to a Child process. fn send_signal(&mut self, signal: usize) -> io::Result<()>; /// Wait for a process to finish or return after the specified duration. fn wait_or_timeout(&mut self, timeout: f64) -> io::Result>; } impl ChildExt for Child { fn send_signal(&mut self, signal: usize) -> io::Result<()> { if unsafe { libc::funcs::posix88::signal::kill(self.id() as pid_t, signal as i32) } != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } fn wait_or_timeout(&mut self, timeout: f64) -> io::Result> { // The result will be written to that Option, protected by a Mutex // Then the Condvar will be signaled let state = Arc::new(( Mutex::new(Option::None::>), Condvar::new(), )); // Start the waiting thread let state_th = state.clone(); let pid_th = self.id(); thread::spawn(move || { let &(ref lock_th, ref cvar_th) = &*state_th; // Child::wait() would need a &mut to self, can't use that... // use waitpid() directly, with our own ExitStatus let mut status: c_int = 0; let r = unsafe { libc::waitpid(pid_th as i32, &mut status, 0) }; // Fill the Option and notify on the Condvar let mut exitstatus_th = lock_th.lock().unwrap(); if r != pid_th as c_int { *exitstatus_th = Some(Err(io::Error::last_os_error())); } else { let s = ExitStatus::from_status(status); *exitstatus_th = Some(Ok(s)); } cvar_th.notify_one(); }); // Main thread waits let &(ref lock, ref cvar) = &*state; let mut exitstatus = lock.lock().unwrap(); // Condvar::wait_timeout_ms() can wake too soon, in this case wait again let target = get_time() + Duration::seconds(timeout as i64) + Duration::nanoseconds((timeout * 1.0e-6) as i64); while exitstatus.is_none() { let now = get_time(); if now >= target { return Ok(None) } let ms = (target - get_time()).num_milliseconds() as u32; exitstatus = cvar.wait_timeout_ms(exitstatus, ms).unwrap().0; } // Turn Option> into Result> match exitstatus.take() { Some(r) => match r { Ok(s) => Ok(Some(s)), Err(e) => Err(e), }, None => panic!(), } } }