use std::{io, os::fd::RawFd};
pub(crate) struct Inheritance {
allowed: Vec<RawFd>,
upper_bound: RawFd,
}
impl Inheritance {
pub(crate) fn new(mut allowed: Vec<RawFd>) -> io::Result<Self> {
if allowed.iter().any(|fd| *fd < 0) {
return Err(io::Error::from_raw_os_error(libc::EBADF));
}
allowed.sort_unstable();
allowed.dedup();
for &fd in &allowed {
if unsafe { libc::fcntl(fd, libc::F_GETFD) } < 0 {
return Err(io::Error::last_os_error());
}
}
let maximum = unsafe { libc::sysconf(libc::_SC_OPEN_MAX) };
if maximum < 0 {
return Err(io::Error::last_os_error());
}
let highest = ["/proc/self/fd", "/dev/fd"]
.into_iter()
.find_map(|path| {
std::fs::read_dir(path).ok().map(|entries| {
entries
.filter_map(Result::ok)
.filter_map(|entry| entry.file_name().to_str()?.parse::<RawFd>().ok())
.max()
.unwrap_or(2)
})
})
.unwrap_or(2);
let upper_bound = maximum.min(i64::from(i32::MAX) as libc::c_long) as RawFd;
Ok(Self {
allowed,
upper_bound: upper_bound.max(highest.saturating_add(1)),
})
}
pub(crate) fn apply(&self) -> io::Result<()> {
let mut first = 3u32;
for &fd in self.allowed.iter().filter(|fd| **fd >= 3) {
self.exclude(first, fd as u32 - 1)?;
first = fd as u32 + 1;
}
self.exclude(first, u32::MAX)?;
for &fd in self.allowed.iter().filter(|fd| **fd >= 3) {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags < 0 || unsafe { libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) } < 0
{
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn exclude(&self, first: u32, last: u32) -> io::Result<()> {
if first > last {
return Ok(());
}
#[cfg(target_os = "linux")]
{
const CLOSE_RANGE_CLOEXEC: libc::c_uint = 4;
if unsafe { libc::syscall(libc::SYS_close_range, first, last, CLOSE_RANGE_CLOEXEC) }
== 0
{
return Ok(());
}
}
let end = last.min(self.upper_bound.saturating_sub(1) as u32);
for fd in first..=end {
let fd = fd as RawFd;
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags < 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::EBADF) {
return Err(error);
}
} else if flags & libc::FD_CLOEXEC == 0 {
if unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) } < 0 {
return Err(io::Error::last_os_error());
}
unsafe {
libc::close(fd);
}
}
}
Ok(())
}
}
pub(crate) fn raise_nice_to(floor: libc::c_int) -> io::Result<()> {
let current = unsafe { libc::getpriority(libc::PRIO_PROCESS, 0) };
if current >= floor {
return Ok(());
}
if unsafe { libc::setpriority(libc::PRIO_PROCESS, 0, floor) } < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
#[cfg(target_os = "macos")]
pub(crate) fn set_utility_qos() {
unsafe { libc::pthread_set_qos_class_self_np(libc::qos_class_t::QOS_CLASS_UTILITY, 0) };
}
#[cfg(target_os = "macos")]
const WATCHDOG_GRACE_STEPS: u32 = 40; #[cfg(target_os = "macos")]
const WATCHDOG_STEP_NANOS: libc::c_long = 50_000_000;
#[cfg(target_os = "macos")]
const WATCHDOG_ARM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
#[cfg(target_os = "macos")]
pub(crate) fn arm_parent_death_watch(child: u32) -> io::Result<()> {
let supervisor = unsafe { libc::getpid() };
let child = child as libc::pid_t;
let mut ends = [0 as libc::c_int; 2];
if unsafe { libc::pipe(ends.as_mut_ptr()) } < 0 {
return Err(io::Error::last_os_error());
}
let [ack_read, ack_write] = ends;
for fd in ends {
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFD);
if flags >= 0 {
libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC);
}
}
}
let inherited = open_descriptors();
let middle = unsafe { libc::fork() };
if middle < 0 {
let error = io::Error::last_os_error();
unsafe {
libc::close(ack_read);
libc::close(ack_write);
}
return Err(error);
}
if middle == 0 {
let watchdog = unsafe { libc::fork() };
if watchdog == 0 {
run_watchdog(supervisor, child, ack_read, ack_write, &inherited);
}
unsafe { libc::_exit(if watchdog < 0 { 1 } else { 0 }) }
}
unsafe { libc::close(ack_write) };
let mut status: libc::c_int = 0;
let reaped = loop {
let result = unsafe { libc::waitpid(middle, &mut status, 0) };
if result >= 0 {
break true;
}
match io::Error::last_os_error().raw_os_error() {
Some(libc::EINTR) => continue,
_ => break false,
}
};
let middle_ok = !reaped || (libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0);
let armed = middle_ok && read_ack(ack_read);
unsafe { libc::close(ack_read) };
if armed {
Ok(())
} else {
Err(io::Error::other(
"the macOS parent-death watchdog could not be armed",
))
}
}
#[cfg(target_os = "macos")]
fn read_ack(fd: libc::c_int) -> bool {
let deadline = std::time::Instant::now() + WATCHDOG_ARM_TIMEOUT;
loop {
let left = deadline.saturating_duration_since(std::time::Instant::now());
if left.is_zero() {
return false;
}
let mut entry = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let millis = left.as_millis().min(libc::c_int::MAX as u128) as libc::c_int;
let ready = unsafe { libc::poll(&mut entry, 1, millis.max(1)) };
if ready < 0 {
if io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
continue;
}
return false;
}
if ready == 0 {
continue; }
let mut byte = 0u8;
let count = unsafe { libc::read(fd, (&mut byte as *mut u8).cast(), 1) };
return count == 1 && byte == b'A';
}
}
#[cfg(target_os = "macos")]
fn open_descriptors() -> Vec<RawFd> {
let listed: Option<Vec<RawFd>> = std::fs::read_dir("/dev/fd").ok().map(|entries| {
entries
.filter_map(Result::ok)
.filter_map(|entry| entry.file_name().to_str()?.parse::<RawFd>().ok())
.collect()
});
listed.unwrap_or_else(|| {
let limit = unsafe { libc::getdtablesize() }.clamp(3, 65_536);
(3..limit).collect()
})
}
#[cfg(target_os = "macos")]
fn proc_exit_change(pid: libc::pid_t, tag: usize) -> libc::kevent {
libc::kevent {
ident: pid as usize,
filter: libc::EVFILT_PROC,
flags: libc::EV_ADD | libc::EV_RECEIPT,
fflags: libc::NOTE_EXIT,
data: 0,
udata: tag as *mut libc::c_void,
}
}
#[cfg(target_os = "macos")]
fn run_watchdog(
supervisor: libc::pid_t,
child: libc::pid_t,
ack_read: libc::c_int,
ack_write: libc::c_int,
inherited: &[RawFd],
) -> ! {
const PARENT_TAG: usize = 1;
const CHILD_TAG: usize = 2;
unsafe {
let report = |byte: u8| {
libc::write(ack_write, (&byte as *const u8).cast(), 1);
libc::close(ack_write);
};
libc::signal(libc::SIGPIPE, libc::SIG_IGN);
libc::setsid();
libc::close(ack_read);
let null = libc::open(c"/dev/null".as_ptr(), libc::O_RDWR);
if null >= 0 {
for target in 0..3 {
libc::dup2(null, target);
}
if null > 2 {
libc::close(null);
}
}
for &fd in inherited {
if fd > 2 && fd != ack_write {
libc::close(fd);
}
}
let kq = libc::kqueue();
if kq < 0 {
report(b'F');
libc::_exit(1);
}
let changes = [
proc_exit_change(supervisor, PARENT_TAG),
proc_exit_change(child, CHILD_TAG),
];
let mut receipts: [libc::kevent; 2] = std::mem::zeroed();
let no_wait = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let count = libc::kevent(kq, changes.as_ptr(), 2, receipts.as_mut_ptr(), 2, &no_wait);
if count != 2 {
report(b'F');
libc::_exit(1);
}
let (mut parent_error, mut child_error) = (-1, -1);
for receipt in &receipts {
let error = if receipt.flags & libc::EV_ERROR != 0 {
receipt.data as i32
} else {
0
};
match receipt.udata as usize {
PARENT_TAG => parent_error = error,
CHILD_TAG => child_error = error,
_ => {}
}
}
if child_error == libc::ESRCH {
report(b'A');
libc::_exit(0);
}
if child_error != 0 || (parent_error != 0 && parent_error != libc::ESRCH) {
report(b'F');
libc::_exit(1);
}
report(b'A');
let mut parent_dead = parent_error == libc::ESRCH;
let mut out: [libc::kevent; 2] = std::mem::zeroed();
while !parent_dead {
let count = libc::kevent(
kq,
std::ptr::null(),
0,
out.as_mut_ptr(),
2,
std::ptr::null(),
);
if count < 0 {
if *libc::__error() == libc::EINTR {
continue;
}
libc::_exit(1);
}
let events = &out[..count as usize];
if events.iter().any(|e| e.ident == child as usize) {
libc::_exit(0); }
if events.iter().any(|e| e.ident == supervisor as usize) {
parent_dead = true;
}
}
let count = libc::kevent(kq, std::ptr::null(), 0, out.as_mut_ptr(), 2, &no_wait);
if count > 0
&& out[..count as usize]
.iter()
.any(|e| e.ident == child as usize)
{
libc::_exit(0);
}
libc::kill(-child, libc::SIGTERM);
let step = libc::timespec {
tv_sec: 0,
tv_nsec: WATCHDOG_STEP_NANOS,
};
for _ in 0..WATCHDOG_GRACE_STEPS {
let count = libc::kevent(kq, std::ptr::null(), 0, out.as_mut_ptr(), 2, &step);
if count > 0
&& out[..count as usize]
.iter()
.any(|e| e.ident == child as usize)
{
libc::_exit(0);
}
}
libc::kill(-child, libc::SIGKILL);
libc::_exit(0);
}
}
#[cfg(target_os = "linux")]
pub(crate) fn die_with_parent(supervisor: libc::pid_t) -> io::Result<()> {
if unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM as libc::c_ulong) } < 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::getppid() } != supervisor {
return Err(io::Error::from_raw_os_error(libc::ESRCH));
}
Ok(())
}