use nix::{
sys::{ptrace, wait::WaitStatus},
unistd::{execve, fork, ForkResult, Pid},
};
use std::ffi::CString;
use crate::diag::Result;
pub trait Tracer {
fn trace(&self, pid: Pid) -> Result<i32>;
}
fn tracee(args: &[CString], env: &[CString]) -> Result<i32> {
ptrace::traceme()?;
execve(&args[0], args, env)?;
Ok(0)
}
pub fn run<T: Tracer>(tracer: &T, args: &[CString], env: &[CString]) -> Result<i32> {
match unsafe { fork() }? {
ForkResult::Parent { child: pid, .. } => tracer.trace(pid),
ForkResult::Child => tracee(args, env),
}
}
#[must_use]
pub fn terminated(status: WaitStatus) -> Option<i32> {
match status {
WaitStatus::Signaled(_, signal, coredump) => {
let coredump_str = if coredump { " (core dumped)" } else { "" };
eprintln!("+++ killed by {signal:?}{coredump_str} +++");
Some(signal as i32)
}
WaitStatus::Exited(_, code) => {
eprintln!("+++ exited with {code} +++");
Some(code)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_terminated_signaled() {
let status = WaitStatus::Signaled(
Pid::from_raw(1),
nix::sys::signal::Signal::SIGKILL,
false,
);
assert!(terminated(status).is_some());
}
#[test]
fn test_terminated_other() {
let status =
WaitStatus::Stopped(Pid::from_raw(1), nix::sys::signal::Signal::SIGSTOP);
assert!(terminated(status).is_none());
}
}