diff --git a/src/common/process.rs b/src/common/process.rs new file mode 100644 index 000000000..5895904b3 --- /dev/null +++ b/src/common/process.rs @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#![feature(process_id)] + +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_ms: usize) -> 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_ms: usize) -> 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::milliseconds(timeout_ms 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!(), + } + } +}