#[cfg(any(target_os = "macos", target_os = "freebsd"))]
pub fn setup_parent_death_signal(parent_pid: i32, signal: i32) -> std::io::Result<()> {
use std::thread;
if unsafe { libc::kill(parent_pid, 0) } != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ESRCH) {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"parent process not found",
));
}
}
thread::Builder::new()
.name("parent-death-watcher".into())
.spawn(move || {
watch_parent_kqueue(parent_pid, signal);
})?;
Ok(())
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
fn watch_parent_kqueue(parent_pid: i32, signal: i32) {
unsafe {
let kq = libc::kqueue();
if kq < 0 {
return;
}
let mut event: libc::kevent = std::mem::zeroed();
event.ident = parent_pid as libc::uintptr_t;
event.filter = libc::EVFILT_PROC;
event.flags = libc::EV_ADD | libc::EV_ONESHOT;
event.fflags = libc::NOTE_EXIT;
event.data = 0;
event.udata = std::ptr::null_mut();
let result = libc::kevent(kq, &event, 1, std::ptr::null_mut(), 0, std::ptr::null());
if result < 0 {
libc::close(kq);
return;
}
let mut triggered: libc::kevent = std::mem::zeroed();
let n = libc::kevent(kq, std::ptr::null(), 0, &mut triggered, 1, std::ptr::null());
libc::close(kq);
if n > 0 && (triggered.fflags & libc::NOTE_EXIT) != 0 {
libc::kill(libc::getpid(), signal);
}
}
}
#[cfg(target_os = "linux")]
pub fn setup_parent_death_signal(_parent_pid: i32, signal: i32) -> std::io::Result<()> {
unsafe {
if libc::prctl(libc::PR_SET_PDEATHSIG, signal) != 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
pub fn setup_parent_death_signal(_parent_pid: i32, _signal: i32) -> std::io::Result<()> {
Ok(())
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
pub unsafe fn setup_parent_death_signal_preexec(
grandparent_pid: i32,
target_pgid: i32,
signal: i32,
) {
unsafe {
let pid = libc::fork();
if pid < 0 {
return;
}
if pid > 0 {
return;
}
watcher_main(grandparent_pid, target_pgid, signal);
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
unsafe fn watcher_main(grandparent_pid: i32, target_pgid: i32, signal: i32) -> ! {
unsafe {
let _ = libc::setsid();
for fd in 0..1024 {
let _ = libc::close(fd);
}
let kq = libc::kqueue();
if kq < 0 {
libc::_exit(1);
}
let mut event: libc::kevent = std::mem::zeroed();
event.ident = grandparent_pid as libc::uintptr_t;
event.filter = libc::EVFILT_PROC;
event.flags = libc::EV_ADD | libc::EV_ONESHOT;
event.fflags = libc::NOTE_EXIT;
event.data = 0;
event.udata = std::ptr::null_mut();
if libc::kevent(kq, &event, 1, std::ptr::null_mut(), 0, std::ptr::null()) < 0 {
libc::close(kq);
libc::_exit(1);
}
if libc::kill(grandparent_pid, 0) != 0 {
libc::killpg(target_pgid, signal);
libc::close(kq);
libc::_exit(0);
}
let mut triggered: libc::kevent = std::mem::zeroed();
let n = libc::kevent(kq, std::ptr::null(), 0, &mut triggered, 1, std::ptr::null());
libc::close(kq);
if n > 0 && (triggered.fflags & libc::NOTE_EXIT) != 0 {
libc::killpg(target_pgid, signal);
}
libc::_exit(0);
}
}
#[cfg(target_os = "linux")]
pub unsafe fn setup_parent_death_signal_preexec(
_grandparent_pid: i32,
_target_pgid: i32,
signal: i32,
) {
unsafe {
let _ = libc::prctl(libc::PR_SET_PDEATHSIG, signal);
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
pub unsafe fn setup_parent_death_signal_preexec(
_grandparent_pid: i32,
_target_pgid: i32,
_signal: i32,
) {
}
#[cfg(all(test, any(target_os = "macos", target_os = "freebsd")))]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_setup_with_nonexistent_pid() {
let result = setup_parent_death_signal(999_999_999, libc::SIGTERM);
assert!(result.is_err());
}
#[test]
fn test_setup_with_valid_parent() {
let pid = std::process::id() as i32;
let result = setup_parent_death_signal(pid, libc::SIGTERM);
assert!(result.is_ok());
std::thread::sleep(Duration::from_millis(10));
}
#[test]
fn test_setup_with_own_parent() {
let ppid = unsafe { libc::getppid() };
let result = setup_parent_death_signal(ppid, libc::SIGTERM);
assert!(result.is_ok());
std::thread::sleep(Duration::from_millis(10));
}
}