use std::convert::TryInto;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io;
use std::mem::MaybeUninit;
use std::os::raw::c_int;
use std::os::unix::process::ExitStatusExt;
use std::process;
use std::process::Child;
use std::thread;
use std::time::Duration;
use libc::pid_t;
use libc::EINTR;
use libc::ESRCH;
use libc::SIGKILL;
extern "C" {
fn wait_for_process(pid: pid_t, exit_status: *mut ExitStatus) -> c_int;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(super) struct ExitStatus {
value: c_int,
terminated: bool,
}
impl ExitStatus {
pub(super) fn success(self) -> bool {
!self.terminated && self.value == 0
}
fn get_value(self, normal_exit: bool) -> Option<c_int> {
Some(self.value).filter(|_| self.terminated != normal_exit)
}
pub(super) fn code(self) -> Option<c_int> {
self.get_value(true)
}
pub(super) fn signal(self) -> Option<c_int> {
self.get_value(false)
}
}
impl Display for ExitStatus {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
if self.terminated {
write!(formatter, "signal: {}", self.value)
} else {
write!(formatter, "exit code: {}", self.value)
}
}
}
impl From<process::ExitStatus> for ExitStatus {
fn from(value: process::ExitStatus) -> Self {
if let Some(exit_code) = value.code() {
Self {
value: exit_code,
terminated: false,
}
} else if let Some(signal) = value.signal() {
Self {
value: signal,
terminated: true,
}
} else {
unreachable!()
}
}
}
pub(super) fn run_with_timeout<TReturn>(
get_result_fn: impl 'static + FnOnce() -> TReturn + Send,
time_limit: Duration,
) -> io::Result<Option<TReturn>>
where
TReturn: 'static + Send,
{
let (result_sender, result_receiver) = {
#[cfg(feature = "crossbeam-channel")]
{
crossbeam_channel::bounded(0)
}
#[cfg(not(feature = "crossbeam-channel"))]
{
use std::sync::mpsc;
mpsc::channel()
}
};
let _ = thread::Builder::new()
.spawn(move || result_sender.send(get_result_fn()))?;
Ok(result_receiver.recv_timeout(time_limit).ok())
}
#[derive(Debug)]
pub(super) struct Handle(pid_t);
impl Handle {
fn check_syscall(result: c_int) -> io::Result<()> {
if result >= 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub(super) fn new(process: &Child) -> io::Result<Self> {
Ok(Self::inherited(process))
}
pub(super) fn inherited(process: &Child) -> Self {
Self(
process
.id()
.try_into()
.expect("returned process identifier is invalid"),
)
}
pub(super) unsafe fn terminate(&self) -> io::Result<()> {
let result = Self::check_syscall(libc::kill(self.0, SIGKILL));
if let Err(error) = &result {
if error.raw_os_error() == Some(ESRCH) {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"No such process",
));
}
}
result
}
pub(super) fn wait_with_timeout(
&self,
time_limit: Duration,
) -> io::Result<Option<ExitStatus>> {
let process_id = self.0;
run_with_timeout(
move || {
let mut exit_status = MaybeUninit::uninit();
loop {
let result = Self::check_syscall(unsafe {
wait_for_process(process_id, exit_status.as_mut_ptr())
});
match result {
Ok(()) => break,
Err(error) => {
if error.raw_os_error() != Some(EINTR) {
return Err(error);
}
}
}
}
Ok(unsafe { exit_status.assume_init() })
},
time_limit,
)?
.transpose()
}
}