use std::os::fd::{AsFd, FromRawFd, OwnedFd};
use std::time::{Duration, Instant};
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
#[derive(Debug)]
pub(crate) enum KernfsWaitOutcome<T> {
Done(T),
Timeout,
NoEventedSource,
}
pub(crate) fn kernfs_evented_wait<T, P, A>(
parent_dir: P,
event_mask: AddWatchFlags,
attr_path: Option<A>,
cadence: Duration,
deadline: Instant,
mut check_done: impl FnMut() -> Option<T>,
) -> KernfsWaitOutcome<T>
where
P: AsRef<std::path::Path>,
A: AsRef<std::path::Path>,
{
if let Some(v) = check_done() {
return KernfsWaitOutcome::Done(v);
}
let inotify = Inotify::init(InitFlags::IN_NONBLOCK | InitFlags::IN_CLOEXEC)
.ok()
.and_then(|inot| {
let parent = parent_dir.as_ref();
match inot.add_watch(parent, event_mask) {
Ok(_wd) => Some(inot),
Err(_) => None,
}
});
let attr_fd: Option<OwnedFd> = attr_path.and_then(|path| {
let c_path = match std::ffi::CString::new(path.as_ref().as_os_str().as_encoded_bytes()) {
Ok(c) => c,
Err(_) => return None,
};
let raw = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if raw < 0 {
None
} else {
Some(unsafe { OwnedFd::from_raw_fd(raw) })
}
});
if inotify.is_none() && attr_fd.is_none() {
return KernfsWaitOutcome::NoEventedSource;
}
loop {
let now = Instant::now();
if now >= deadline {
return KernfsWaitOutcome::Timeout;
}
let remaining = deadline - now;
let wait_ms = remaining.min(cadence).as_millis().min(i32::MAX as u128) as i32;
let timeout = PollTimeout::try_from(wait_ms).unwrap_or(PollTimeout::ZERO);
let mut pfds: Vec<PollFd<'_>> = Vec::with_capacity(2);
if let Some(ref fd) = attr_fd {
pfds.push(PollFd::new(fd.as_fd(), PollFlags::POLLPRI));
}
if let Some(ref inot) = inotify {
pfds.push(PollFd::new(inot.as_fd(), PollFlags::POLLIN));
}
let _ = poll(&mut pfds, timeout);
if let Some(ref inot) = inotify {
let _ = inot.read_events();
}
if let Some(v) = check_done() {
return KernfsWaitOutcome::Done(v);
}
}
}
pub(crate) fn pidfd_wait_exit(
pid: u32,
deadline: Instant,
mut still_alive: impl FnMut() -> bool,
) -> bool {
if !still_alive() {
return true;
}
let raw =
unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::c_int, 0u32) as libc::c_int };
if raw < 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ESRCH) {
return true;
}
if !still_alive() {
return true;
}
panic!(
"evented_wait::pidfd_wait_exit: pidfd_open(pid={pid}) returned {err} \
(errno {:?}); pidfd_open is unconditional from Linux 5.3 so a \
non-ESRCH failure indicates env breakage — check ulimit -n / \
memory pressure / cgroup pids.max",
err.raw_os_error()
);
}
let pidfd = unsafe { OwnedFd::from_raw_fd(raw) };
loop {
if !still_alive() {
return true;
}
let now = Instant::now();
if now >= deadline {
return !still_alive();
}
let remaining = deadline - now;
let wait_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
let timeout = PollTimeout::try_from(wait_ms).unwrap_or(PollTimeout::ZERO);
let mut pfds = [PollFd::new(pidfd.as_fd(), PollFlags::POLLIN)];
let _ = poll(&mut pfds, timeout);
}
}