#![allow(bad_style)]
use std::cmp;
use std::collections::HashMap;
use std::io::{self, Read, Write};
use std::mem;
use std::os::unix::net::UnixStream;
use std::os::unix::prelude::*;
use std::process::{Child, ExitStatus};
use std::sync::{Mutex, Once};
use std::time::{Duration, Instant};
use libc::{self, c_int};
static INIT: Once = Once::new();
static mut STATE: *mut State = 0 as *mut _;
struct State {
prev: libc::sigaction,
write: UnixStream,
read: UnixStream,
map: Mutex<StateMap>,
}
type StateMap = HashMap<*mut Child, (UnixStream, Option<ExitStatus>)>;
pub fn wait_timeout(child: &mut Child, dur: Duration) -> io::Result<Option<ExitStatus>> {
INIT.call_once(State::init);
unsafe { (*STATE).wait_timeout(child, dur) }
}
impl State {
#[allow(unused_assignments)]
fn init() {
unsafe {
let (read, write) = UnixStream::pair().unwrap();
read.set_nonblocking(true).unwrap();
write.set_nonblocking(true).unwrap();
let state = Box::new(State {
prev: mem::zeroed(),
write: write,
read: read,
map: Mutex::new(HashMap::new()),
});
let mut new: libc::sigaction = mem::zeroed();
new.sa_sigaction = sigchld_handler as usize;
new.sa_flags = libc::SA_NOCLDSTOP | libc::SA_RESTART | libc::SA_SIGINFO;
STATE = Box::into_raw(state);
assert_eq!(libc::sigaction(libc::SIGCHLD, &new, &mut (*STATE).prev), 0);
}
}
fn wait_timeout(&self, child: &mut Child, dur: Duration) -> io::Result<Option<ExitStatus>> {
let (read, write) = UnixStream::pair()?;
read.set_nonblocking(true)?;
write.set_nonblocking(true)?;
let mut map = self.map.lock().unwrap();
if let Some(status) = child.try_wait()? {
return Ok(Some(status));
}
assert!(map.insert(child, (write, None)).is_none());
drop(map);
struct Remove<'a> {
state: &'a State,
child: &'a mut Child,
}
impl<'a> Drop for Remove<'a> {
fn drop(&mut self) {
let mut map = self.state.map.lock().unwrap();
drop(map.remove(&(self.child as *mut Child)));
}
}
let remove = Remove { state: self, child };
let start = Instant::now();
let mut fds = [
libc::pollfd {
fd: self.read.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
},
libc::pollfd {
fd: read.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
},
];
loop {
let elapsed = start.elapsed();
if elapsed >= dur {
break;
}
let timeout = dur - elapsed;
let timeout = timeout
.as_secs()
.checked_mul(1_000)
.and_then(|amt| amt.checked_add(timeout.subsec_nanos() as u64 / 1_000_000))
.unwrap_or(u64::max_value());
let timeout = cmp::min(<c_int>::max_value() as u64, timeout) as c_int;
let r = unsafe { libc::poll(fds.as_mut_ptr(), 2, timeout) };
let timeout = match r {
0 => true,
n if n > 0 => false,
n => {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
} else {
panic!("error in select = {}: {}", n, err)
}
}
};
let mut map = self.map.lock().unwrap();
if drain(&self.read) {
self.process_sigchlds(&mut map);
}
if drain(&read) || timeout {
break;
}
}
let mut map = self.map.lock().unwrap();
let (_write, ret) = map.remove(&(remove.child as *mut Child)).unwrap();
drop(map);
Ok(ret)
}
fn process_sigchlds(&self, map: &mut StateMap) {
for (&k, &mut (ref write, ref mut status)) in map {
if status.is_some() {
continue;
}
*status = unsafe { (*k).try_wait().unwrap() };
if status.is_some() {
notify(write);
}
}
}
}
fn drain(mut file: &UnixStream) -> bool {
let mut ret = false;
let mut buf = [0u8; 16];
loop {
match file.read(&mut buf) {
Ok(0) => return true, Ok(..) => ret = true, Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
return ret;
} else {
panic!("bad read: {}", e)
}
}
}
}
}
fn notify(mut file: &UnixStream) {
match file.write(&[1]) {
Ok(..) => {}
Err(e) => {
if e.kind() != io::ErrorKind::WouldBlock {
panic!("bad error on write fd: {}", e)
}
}
}
}
#[allow(unused_assignments)]
extern "C" fn sigchld_handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc::c_void) {
type FnSigaction = extern "C" fn(c_int, *mut libc::siginfo_t, *mut libc::c_void);
type FnHandler = extern "C" fn(c_int);
unsafe {
let state = &*STATE;
notify(&state.write);
let fnptr = state.prev.sa_sigaction;
if fnptr == 0 {
return;
}
if state.prev.sa_flags & libc::SA_SIGINFO == 0 {
let action = mem::transmute::<usize, FnHandler>(fnptr);
action(signum)
} else {
let action = mem::transmute::<usize, FnSigaction>(fnptr);
action(signum, info, ptr)
}
}
}