#[cfg(unix)]
pub fn detach_session(cmd: &mut tokio::process::Command) {
unsafe {
use std::os::unix::process::CommandExt;
cmd.as_std_mut().pre_exec(|| {
if libc::setsid() == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
}
#[cfg(not(unix))]
pub fn detach_session(_cmd: &mut tokio::process::Command) {}
#[cfg_attr(not(unix), allow(dead_code))]
pub struct ProcessGroupGuard {
#[cfg(unix)]
pgid: u32,
}
impl ProcessGroupGuard {
pub fn from_pid(pid: Option<u32>) -> Option<Self> {
#[cfg(unix)]
{
pid.filter(|&p| p > 1).map(|pgid| Self { pgid })
}
#[cfg(not(unix))]
{
let _ = pid;
None
}
}
}
#[cfg(unix)]
impl Drop for ProcessGroupGuard {
fn drop(&mut self) {
if self.pgid <= 1 {
return;
}
unsafe {
let _ = libc::kill(-(self.pgid as libc::pid_t), libc::SIGKILL);
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn from_pid_rejects_reserved_and_missing_groups() {
assert!(ProcessGroupGuard::from_pid(None).is_none());
assert!(
ProcessGroupGuard::from_pid(Some(0)).is_none(),
"group 0 is our own"
);
assert!(
ProcessGroupGuard::from_pid(Some(1)).is_none(),
"group 1 is everything"
);
assert!(ProcessGroupGuard::from_pid(Some(4242)).is_some());
}
#[tokio::test]
async fn guard_group_kill_reaps_the_child_on_drop() {
let mut cmd = tokio::process::Command::new("sleep");
cmd.arg("30").kill_on_drop(false);
detach_session(&mut cmd);
let mut child = cmd.spawn().expect("spawn sleep");
let pid = child.id().expect("child pid");
assert_eq!(
unsafe { libc::kill(-(pid as libc::pid_t), 0) },
0,
"process group should be alive before the guard drops"
);
let guard = ProcessGroupGuard::from_pid(Some(pid)).expect("guard");
drop(guard);
use std::os::unix::process::ExitStatusExt;
let status = child.wait().await.expect("wait");
assert_eq!(
status.signal(),
Some(libc::SIGKILL),
"child must be terminated by the group SIGKILL"
);
}
}