use crate::{Result, Signal};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WaitStatus {
Exited(libc::pid_t, libc::c_int),
Signaled(libc::pid_t, Signal, bool),
Stopped(libc::pid_t, Signal),
Continued(libc::pid_t),
}
impl WaitStatus {
pub fn from_raw(pid: libc::pid_t, status: libc::c_int) -> Result<WaitStatus> {
Ok(if libc::WIFEXITED(status) {
WaitStatus::Exited(pid, libc::WEXITSTATUS(status))
} else if libc::WIFSIGNALED(status) {
WaitStatus::Signaled(
pid,
libc::WTERMSIG(status).try_into()?,
libc::WCOREDUMP(status),
)
} else if libc::WIFSTOPPED(status) {
WaitStatus::Stopped(pid, libc::WSTOPSIG(status).try_into()?)
} else {
assert!(libc::WIFCONTINUED(status));
WaitStatus::Continued(pid)
})
}
}
pub fn wait<P>(pid: P) -> Result<WaitStatus>
where
P: Into<Option<libc::pid_t>>,
{
let mut status: i32 = 0;
let res = syscall!(waitpid(
pid.into().unwrap_or(-1_i32),
&mut status as &mut libc::c_int,
libc::WUNTRACED
))?;
WaitStatus::from_raw(res, status)
}
#[cfg(test)]
mod tests {
use std::process::exit;
use crate::{wait, Result, Signal, WaitStatus};
#[test]
#[ignore = "must run in isolation"]
fn wait_any() -> Result<()> {
let child = match syscall!(fork())? {
pid if pid != 0 => pid,
_ => {
exit(42);
}
};
if let WaitStatus::Exited(pid, status) = wait(None)? {
assert_eq!(pid, child);
assert_eq!(42, status);
} else {
assert!(false);
}
Ok(())
}
#[test]
fn wait_exit() -> Result<()> {
let child = match syscall!(fork())? {
pid if pid != 0 => pid,
_ => {
exit(42);
}
};
if let WaitStatus::Exited(pid, status) = wait(child)? {
assert_eq!(pid, child);
assert_eq!(42, status);
} else {
assert!(false);
}
Ok(())
}
#[test]
fn wait_stop() -> Result<()> {
let child = match syscall!(fork())? {
pid if pid != 0 => pid,
_ => loop {
std::thread::sleep(std::time::Duration::from_millis(5));
},
};
syscall!(kill(child, Signal::SIGSTOP as libc::c_int))?;
if let WaitStatus::Stopped(pid, signal) = wait(child)? {
assert_eq!(pid, child);
assert_eq!(signal, Signal::SIGSTOP);
} else {
assert!(false);
}
Ok(())
}
#[test]
fn wait_kill() -> Result<()> {
let child = match syscall!(fork())? {
pid if pid != 0 => pid,
_ => loop {
std::thread::sleep(std::time::Duration::from_millis(5));
},
};
syscall!(kill(child, Signal::SIGKILL as libc::c_int))?;
if let WaitStatus::Signaled(pid, signal, core) = wait(child)? {
assert_eq!(pid, child);
assert_eq!(signal, Signal::SIGKILL);
assert_eq!(core, false);
} else {
assert!(false);
}
Ok(())
}
#[test]
fn wait_stop_kill() -> Result<()> {
let child = match syscall!(fork())? {
pid if pid != 0 => pid,
_ => loop {
std::thread::sleep(std::time::Duration::from_millis(5));
},
};
syscall!(kill(child, Signal::SIGSTOP as libc::c_int))?;
if let WaitStatus::Stopped(pid, signal) = wait(child)? {
assert_eq!(pid, child);
assert_eq!(signal, Signal::SIGSTOP);
} else {
assert!(false);
}
syscall!(kill(child, Signal::SIGKILL as libc::c_int))?;
if let WaitStatus::Signaled(pid, signal, core) = wait(child)? {
assert_eq!(pid, child);
assert_eq!(signal, Signal::SIGKILL);
assert_eq!(core, false);
} else {
assert!(false);
}
Ok(())
}
#[test]
fn wait_unknown() {
let res = wait(42);
assert_eq!(
format!("{}", res.err().unwrap()),
"System call error: No child processes (os error 10)"
);
}
}