use std::process;
use std::ffi::{CString, NulError};
use std::os::unix::io::RawFd;
use nix::unistd::{close, execvp, fork, read, write, ForkResult, Pid};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
use nix::sys::signal::{kill, Signal};
const BAD_EXEC: i32 = 42;
pub struct Exec {
target: CString,
args: Vec<CString>,
}
pub struct Child {
socket: RawFd,
pid: Option<Pid>,
}
impl Exec {
pub fn new(target: &str) -> Result<Self, NulError> {
Ok(Exec {
target: CString::new(target)?,
args: Vec::new(),
})
}
pub fn exec(self) -> Child {
let mut c = Child {
pid: None,
socket: 0,
};
let (parent_sock, child_sock) = socketpair(
AddressFamily::Unix,
SockType::Stream,
None,
SockFlag::empty(),
).unwrap();
let mut buf = [0];
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
c.socket = parent_sock;
c.pid = Some(child);
}
Ok(ForkResult::Child) => {
let _ = close(parent_sock);
let _ = read(child_sock, &mut buf);
let _ = close(child_sock);
let _ = execvp(&self.target, &self.args);
process::exit(BAD_EXEC);
}
Err(_) => panic!("fork failed"),
};
c
}
pub fn args(mut self, args: &[&str]) -> Result<Self, NulError> {
for arg in args.iter() {
self.args.push(CString::new(*arg)?);
}
Ok(self)
}
}
impl Child {
pub fn pid(&self) -> Option<u32> {
self.pid
.map(|pid| format!("{}", pid).parse::<u32>().unwrap())
}
pub fn run(self) -> Option<i32> {
self.pid?;
let _ = write(self.socket, b"!");
match waitpid(self.pid, None) {
Ok(WaitStatus::Exited(_, BAD_EXEC)) => None,
Ok(WaitStatus::Exited(_, val)) => Some(val),
_ => None,
}
}
}
impl Drop for Child {
fn drop(&mut self) {
let _ = close(self.socket);
if let Some(pid) = self.pid {
let _ = kill(pid, Signal::SIGTERM);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success() {
let c = Exec::new("/usr/bin/true")
.unwrap()
.args(&vec!["test"])
.unwrap()
.exec();
assert!(c.pid().is_some());
assert_eq!(c.run(), Some(0));
}
#[test]
fn bad_exit_status() {
let c = Exec::new("/usr/bin/false")
.unwrap()
.args(&vec!["test"])
.unwrap()
.exec();
assert!(c.pid().is_some());
assert_eq!(c.run(), Some(1));
}
#[test]
fn missing_binary() {
let c = Exec::new("not-a-thing")
.unwrap()
.args(&vec!["test"])
.unwrap()
.exec();
assert!(c.pid().is_some());
assert_eq!(c.run(), None);
}
}