| //! An API for interfacing with `kqueue`. |
| |
| use crate::fd::{AsFd, AsRawFd, OwnedFd, RawFd}; |
| use crate::{backend, io}; |
| |
| use backend::c::{self, kevent as kevent_t, uintptr_t}; |
| use backend::io::syscalls; |
| |
| #[cfg(any(apple, freebsdlike))] |
| use backend::c::intptr_t; |
| |
| use alloc::vec::Vec; |
| use core::ptr::slice_from_raw_parts_mut; |
| use core::time::Duration; |
| |
| /// A `kqueue` event. |
| #[repr(transparent)] |
| #[derive(Copy, Clone)] |
| pub struct Event { |
| // The layout varies between BSDs and macOS. |
| inner: kevent_t, |
| } |
| |
| impl Event { |
| /// Create a new `Event`. |
| #[allow(clippy::needless_update)] |
| pub fn new(filter: EventFilter, flags: EventFlags, udata: isize) -> Event { |
| let (ident, filter, fflags) = match filter { |
| EventFilter::Read(fd) => (fd.as_raw_fd() as uintptr_t, c::EVFILT_READ, 0), |
| EventFilter::Write(fd) => (fd.as_raw_fd() as _, c::EVFILT_WRITE, 0), |
| #[cfg(target_os = "freebsd")] |
| EventFilter::Empty(fd) => (fd.as_raw_fd() as _, c::EVFILT_EMPTY, 0), |
| EventFilter::Vnode { vnode, flags } => { |
| (vnode.as_raw_fd() as _, c::EVFILT_VNODE, flags.bits()) |
| } |
| #[cfg(feature = "process")] |
| EventFilter::Proc { pid, flags } => ( |
| crate::process::Pid::as_raw(Some(pid)) as _, |
| c::EVFILT_PROC, |
| flags.bits(), |
| ), |
| #[cfg(feature = "process")] |
| EventFilter::Signal { signal, times: _ } => (signal as _, c::EVFILT_SIGNAL, 0), |
| EventFilter::Timer(timer) => { |
| #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))] |
| let (data, fflags) = match timer { |
| Some(timer) => { |
| if timer.subsec_millis() == 0 { |
| (timer.as_secs() as _, c::NOTE_SECONDS) |
| } else if timer.subsec_nanos() == 0 { |
| (timer.as_micros() as _, c::NOTE_USECONDS) |
| } else { |
| (timer.as_nanos() as _, c::NOTE_NSECONDS) |
| } |
| } |
| None => (uintptr_t::MAX, c::NOTE_SECONDS), |
| }; |
| #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] |
| let (data, fflags) = match timer { |
| Some(timer) => (timer.as_millis() as _, 0), |
| None => (uintptr_t::MAX, 0), |
| }; |
| |
| (data, c::EVFILT_TIMER, fflags) |
| } |
| #[cfg(any(apple, freebsdlike))] |
| EventFilter::User { |
| ident, |
| flags, |
| user_flags, |
| } => (ident as _, c::EVFILT_USER, flags.bits() | user_flags.0), |
| EventFilter::Unknown => panic!("unknown filter"), |
| }; |
| |
| Event { |
| inner: kevent_t { |
| ident, |
| filter: filter as _, |
| flags: flags.bits() as _, |
| fflags, |
| data: 0, |
| udata: { |
| // On netbsd, udata is an isize and not a pointer. |
| // TODO: Strict provenance, prevent int-to-ptr cast. |
| udata as _ |
| }, |
| ..unsafe { core::mem::zeroed() } |
| }, |
| } |
| } |
| |
| /// Get the event flags for this event. |
| pub fn flags(&self) -> EventFlags { |
| EventFlags::from_bits_truncate(self.inner.flags as _) |
| } |
| |
| /// Get the user data for this event. |
| pub fn udata(&self) -> isize { |
| // On netbsd, udata is an isize and not a pointer. |
| // TODO: Strict provenance, prevent ptr-to-int cast. |
| |
| self.inner.udata as _ |
| } |
| |
| /// Get the filter of this event. |
| pub fn filter(&self) -> EventFilter { |
| match self.inner.filter as _ { |
| c::EVFILT_READ => EventFilter::Read(self.inner.ident as _), |
| c::EVFILT_WRITE => EventFilter::Write(self.inner.ident as _), |
| #[cfg(target_os = "freebsd")] |
| c::EVFILT_EMPTY => EventFilter::Empty(self.inner.ident as _), |
| c::EVFILT_VNODE => EventFilter::Vnode { |
| vnode: self.inner.ident as _, |
| flags: VnodeEvents::from_bits_truncate(self.inner.fflags), |
| }, |
| #[cfg(feature = "process")] |
| c::EVFILT_PROC => EventFilter::Proc { |
| pid: unsafe { crate::process::Pid::from_raw(self.inner.ident as _) }.unwrap(), |
| flags: ProcessEvents::from_bits_truncate(self.inner.fflags), |
| }, |
| #[cfg(feature = "process")] |
| c::EVFILT_SIGNAL => EventFilter::Signal { |
| signal: crate::process::Signal::from_raw(self.inner.ident as _).unwrap(), |
| times: self.inner.data as _, |
| }, |
| c::EVFILT_TIMER => EventFilter::Timer({ |
| let (data, fflags) = (self.inner.data, self.inner.fflags); |
| #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))] |
| match fflags as _ { |
| c::NOTE_SECONDS => Some(Duration::from_secs(data as _)), |
| c::NOTE_USECONDS => Some(Duration::from_micros(data as _)), |
| c::NOTE_NSECONDS => Some(Duration::from_nanos(data as _)), |
| _ => { |
| // Unknown timer flags. |
| None |
| } |
| } |
| #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] |
| Some(Duration::from_millis(data as _)) |
| }), |
| #[cfg(any(apple, freebsdlike))] |
| c::EVFILT_USER => EventFilter::User { |
| ident: self.inner.ident as _, |
| flags: UserFlags::from_bits_truncate(self.inner.fflags), |
| user_flags: UserDefinedFlags(self.inner.fflags & EVFILT_USER_FLAGS), |
| }, |
| _ => EventFilter::Unknown, |
| } |
| } |
| } |
| |
| /// Bottom 24 bits of a u32. |
| #[cfg(any(apple, freebsdlike))] |
| const EVFILT_USER_FLAGS: u32 = 0x00ff_ffff; |
| |
| /// The possible filters for a `kqueue`. |
| #[repr(i16)] |
| #[non_exhaustive] |
| pub enum EventFilter { |
| /// A read filter. |
| Read(RawFd), |
| |
| /// A write filter. |
| Write(RawFd), |
| |
| /// An empty filter. |
| #[cfg(target_os = "freebsd")] |
| Empty(RawFd), |
| |
| /// A VNode filter. |
| Vnode { |
| /// The file descriptor we looked for events in. |
| vnode: RawFd, |
| |
| /// The flags for this event. |
| flags: VnodeEvents, |
| }, |
| |
| /// A process filter. |
| #[cfg(feature = "process")] |
| Proc { |
| /// The process ID we waited on. |
| pid: crate::process::Pid, |
| |
| /// The flags for this event. |
| flags: ProcessEvents, |
| }, |
| |
| /// A signal filter. |
| #[cfg(feature = "process")] |
| Signal { |
| /// The signal number we waited on. |
| signal: crate::process::Signal, |
| |
| /// The number of times the signal has been |
| /// received since the last call to kevent. |
| times: usize, |
| }, |
| |
| /// A timer filter. |
| Timer(Option<Duration>), |
| |
| /// A user filter. |
| #[cfg(any(apple, freebsdlike))] |
| User { |
| /// The identifier for this event. |
| ident: intptr_t, |
| |
| /// The flags for this event. |
| flags: UserFlags, |
| |
| /// The user-defined flags for this event. |
| user_flags: UserDefinedFlags, |
| }, |
| |
| /// This filter is unknown. |
| /// |
| /// # Panics |
| /// |
| /// Passing this into `Event::new()` will result in a panic. |
| Unknown, |
| } |
| |
| bitflags::bitflags! { |
| /// The flags for a `kqueue` event. |
| pub struct EventFlags: u16 { |
| /// Add the event to the `kqueue`. |
| const ADD = c::EV_ADD as _; |
| |
| /// Enable the event. |
| const ENABLE = c::EV_ENABLE as _; |
| |
| /// Disable the event. |
| const DISABLE = c::EV_DISABLE as _; |
| |
| /// Delete the event from the `kqueue`. |
| const DELETE = c::EV_DELETE as _; |
| |
| /// TODO |
| const RECEIPT = c::EV_RECEIPT as _; |
| |
| /// Clear the event after it is triggered. |
| const ONESHOT = c::EV_ONESHOT as _; |
| |
| /// TODO |
| const CLEAR = c::EV_CLEAR as _; |
| |
| /// TODO |
| const EOF = c::EV_EOF as _; |
| |
| /// TODO |
| const ERROR = c::EV_ERROR as _; |
| } |
| } |
| |
| bitflags::bitflags! { |
| /// The flags for a virtual node event. |
| pub struct VnodeEvents: u32 { |
| /// The file was deleted. |
| const DELETE = c::NOTE_DELETE; |
| |
| /// The file was written to. |
| const WRITE = c::NOTE_WRITE; |
| |
| /// The file was extended. |
| const EXTEND = c::NOTE_EXTEND; |
| |
| /// The file had its attributes changed. |
| const ATTRIBUTES = c::NOTE_ATTRIB; |
| |
| /// The file was renamed. |
| const RENAME = c::NOTE_RENAME; |
| |
| /// Access to the file was revoked. |
| const REVOKE = c::NOTE_REVOKE; |
| |
| /// The link count of the file has changed. |
| const LINK = c::NOTE_LINK; |
| } |
| } |
| |
| #[cfg(feature = "process")] |
| bitflags::bitflags! { |
| /// The flags for a process event. |
| pub struct ProcessEvents: u32 { |
| /// The process exited. |
| const EXIT = c::NOTE_EXIT; |
| |
| /// The process forked itself. |
| const FORK = c::NOTE_FORK; |
| |
| /// The process executed a new process. |
| const EXEC = c::NOTE_EXEC; |
| |
| /// Follow the process through fork() calls (write only). |
| const TRACK = c::NOTE_TRACK; |
| |
| /// An error has occurred with following the process. |
| const TRACKERR = c::NOTE_TRACKERR; |
| } |
| } |
| |
| #[cfg(any(apple, freebsdlike))] |
| bitflags::bitflags! { |
| /// The flags for a user event. |
| pub struct UserFlags: u32 { |
| /// Ignore the user input flags. |
| const NOINPUT = c::NOTE_FFNOP; |
| |
| /// Bitwise AND fflags. |
| const AND = c::NOTE_FFAND; |
| |
| /// Bitwise OR fflags. |
| const OR = c::NOTE_FFOR; |
| |
| /// Copy fflags. |
| const COPY = c::NOTE_FFCOPY; |
| |
| /// Control mask for operations. |
| const CTRLMASK = c::NOTE_FFCTRLMASK; |
| |
| /// User defined flags for masks. |
| const UDFMASK = c::NOTE_FFLAGSMASK; |
| |
| /// Trigger the event. |
| const TRIGGER = c::NOTE_TRIGGER; |
| } |
| } |
| |
| /// User-defined flags. |
| /// |
| /// Only the lower 24 bits are used in this struct. |
| #[repr(transparent)] |
| #[cfg(any(apple, freebsdlike))] |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub struct UserDefinedFlags(u32); |
| |
| #[cfg(any(apple, freebsdlike))] |
| impl UserDefinedFlags { |
| /// Create a new `UserDefinedFlags` from a `u32`. |
| pub fn new(flags: u32) -> Self { |
| Self(flags & EVFILT_USER_FLAGS) |
| } |
| |
| /// Get the underlying `u32`. |
| pub fn get(self) -> u32 { |
| self.0 |
| } |
| } |
| |
| /// `kqueue()`—Create a new `kqueue` file descriptor. |
| /// |
| /// # References |
| /// - [Apple] |
| /// - [FreeBSD] |
| /// - [OpenBSD] |
| /// - [NetBSD] |
| /// - [DragonflyBSD] |
| /// |
| /// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html |
| /// [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 |
| /// [OpenBSD]: https://man.openbsd.org/kqueue.2 |
| /// [NetBSD]: https://man.netbsd.org/kqueue.2 |
| /// [DragonflyBSD]: https://www.dragonflybsd.org/cgi/web-man/?command=kqueue |
| pub fn kqueue() -> io::Result<OwnedFd> { |
| syscalls::kqueue() |
| } |
| |
| /// `kevent(kqueue, changelist, eventlist, timeout)`—Wait for events on a |
| /// `kqueue`. |
| /// |
| /// Note: in order to receive events, make sure to allocate capacity in the |
| /// eventlist! Otherwise, the function will return immediately. |
| /// |
| /// # Safety |
| /// |
| /// The file descriptors referred to by the `Event` structs must be valid for |
| /// the lifetime of the `kqueue` file descriptor. |
| /// |
| /// # References |
| /// - [Apple] |
| /// - [FreeBSD] |
| /// - [OpenBSD] |
| /// - [NetBSD] |
| /// - [DragonflyBSD] |
| /// |
| /// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kevent.2.html |
| /// [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?query=kevent&sektion=2 |
| /// [OpenBSD]: https://man.openbsd.org/kevent.2 |
| /// [NetBSD]: https://man.netbsd.org/kevent.2 |
| /// [DragonflyBSD]: https://www.dragonflybsd.org/cgi/web-man/?command=kevent |
| pub unsafe fn kevent( |
| kqueue: impl AsFd, |
| changelist: &[Event], |
| eventlist: &mut Vec<Event>, |
| timeout: Option<Duration>, |
| ) -> io::Result<usize> { |
| let timeout = timeout.map(|timeout| crate::backend::c::timespec { |
| tv_sec: timeout.as_secs() as _, |
| tv_nsec: timeout.subsec_nanos() as _, |
| }); |
| |
| // Populate the event list with events. |
| eventlist.set_len(0); |
| let out_slice = |
| slice_from_raw_parts_mut(eventlist.as_mut_ptr() as *mut _, eventlist.capacity()); |
| let res = syscalls::kevent( |
| kqueue.as_fd(), |
| changelist, |
| &mut *out_slice, |
| timeout.as_ref(), |
| ) |
| .map(|res| res as _); |
| |
| // Update the event list. |
| if let Ok(len) = res { |
| eventlist.set_len(len); |
| } |
| |
| res |
| } |