use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::ptr;
use std::time::{Duration, Instant};
use super::Opened;
#[derive(Debug)]
pub(super) struct Event {
kqueue: OwnedFd,
}
pub(super) fn open(pid: u32) -> io::Result<Opened> {
let pid =
libc::pid_t::try_from(pid).map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
let kqueue = unsafe { libc::kqueue() };
if kqueue == -1 {
return Err(io::Error::last_os_error());
}
let kqueue = unsafe { OwnedFd::from_raw_fd(kqueue) };
let mut change: libc::kevent = unsafe { std::mem::zeroed() };
change.ident = pid as libc::uintptr_t;
change.filter = libc::EVFILT_PROC;
change.flags = libc::EV_ADD | libc::EV_ONESHOT;
change.fflags = libc::NOTE_EXIT;
let registered = unsafe {
libc::kevent(
kqueue.as_raw_fd(),
&change,
1,
ptr::null_mut(),
0,
ptr::null(),
)
};
if registered == -1 {
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return match error.raw_os_error() {
Some(libc::ESRCH) => Ok(Opened::Gone),
Some(libc::EPERM | libc::EACCES) => Ok(Opened::Unsupported),
_ => Err(error),
};
}
}
Ok(Opened::Watching(Event { kqueue }))
}
impl Event {
pub(super) fn wait(&mut self, deadline: Option<Instant>) -> io::Result<bool> {
next_exit(self.kqueue.as_raw_fd(), deadline)
}
#[cfg(feature = "async")]
pub(super) async fn exited(&mut self) -> io::Result<()> {
use std::os::fd::AsFd;
use tokio::io::{Interest, unix::AsyncFd};
if next_exit(self.kqueue.as_raw_fd(), Some(Instant::now()))? {
return Ok(());
}
let registered = AsyncFd::with_interest(self.kqueue.as_fd(), Interest::READABLE)?;
loop {
let mut ready = registered.readable().await?;
if next_exit(self.kqueue.as_raw_fd(), Some(Instant::now()))? {
return Ok(());
}
ready.clear_ready();
}
}
}
fn next_exit(kqueue: RawFd, deadline: Option<Instant>) -> io::Result<bool> {
loop {
let timeout =
deadline.map(|deadline| timespec(deadline.saturating_duration_since(Instant::now())));
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
let count = unsafe {
libc::kevent(
kqueue,
ptr::null(),
0,
&mut event,
1,
timeout.as_ref().map_or(ptr::null(), ptr::from_ref),
)
};
if count == -1 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(error);
}
if count == 0 {
if deadline.is_none_or(|deadline| Instant::now() >= deadline) {
return Ok(false);
}
continue;
}
if event.flags & libc::EV_ERROR != 0 {
return Err(io::Error::from_raw_os_error(event.data as i32));
}
if event.fflags & libc::NOTE_EXIT != 0 {
return Ok(true);
}
}
}
fn timespec(left: Duration) -> libc::timespec {
libc::timespec {
tv_sec: libc::time_t::try_from(left.as_secs()).unwrap_or(libc::time_t::MAX),
tv_nsec: left.subsec_nanos() as libc::c_long,
}
}