| //! Kernel event notification mechanism |
| //! |
| //! # See Also |
| //! [kqueue(2)](https://www.freebsd.org/cgi/man.cgi?query=kqueue) |
| |
| use crate::{Errno, Result}; |
| #[cfg(not(target_os = "netbsd"))] |
| use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; |
| #[cfg(target_os = "netbsd")] |
| use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t}; |
| use std::convert::TryInto; |
| use std::mem; |
| use std::os::fd::{AsFd, BorrowedFd}; |
| use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; |
| use std::ptr; |
| |
| /// A kernel event queue. Used to notify a process of various asynchronous |
| /// events. |
| #[repr(C)] |
| #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| pub struct KEvent { |
| kevent: libc::kevent, |
| } |
| |
| /// A kernel event queue. |
| /// |
| /// Used by the kernel to notify the process of various types of asynchronous |
| /// events. |
| #[repr(transparent)] |
| #[derive(Debug)] |
| pub struct Kqueue(OwnedFd); |
| |
| impl AsFd for Kqueue { |
| fn as_fd(&self) -> BorrowedFd<'_> { |
| self.0.as_fd() |
| } |
| } |
| |
| impl From<Kqueue> for OwnedFd { |
| fn from(value: Kqueue) -> Self { |
| value.0 |
| } |
| } |
| |
| impl Kqueue { |
| /// Create a new kernel event queue. |
| pub fn new() -> Result<Self> { |
| let res = unsafe { libc::kqueue() }; |
| |
| Errno::result(res).map(|fd| unsafe { Self(OwnedFd::from_raw_fd(fd)) }) |
| } |
| |
| /// Register new events with the kqueue, and return any pending events to |
| /// the user. |
| /// |
| /// This method will block until either the timeout expires, or a registered |
| /// event triggers a notification. |
| /// |
| /// # Arguments |
| /// - `changelist` - Any new kevents to register for notifications. |
| /// - `eventlist` - Storage space for the kernel to return notifications. |
| /// - `timeout` - An optional timeout. |
| /// |
| /// # Returns |
| /// Returns the number of events placed in the `eventlist`. If an error |
| /// occurs while processing an element of the `changelist` and there is |
| /// enough room in the `eventlist`, then the event will be placed in the |
| /// `eventlist` with `EV_ERROR` set in `flags` and the system error in |
| /// `data`. |
| pub fn kevent( |
| &self, |
| changelist: &[KEvent], |
| eventlist: &mut [KEvent], |
| timeout_opt: Option<timespec>, |
| ) -> Result<usize> { |
| let res = unsafe { |
| libc::kevent( |
| self.0.as_raw_fd(), |
| changelist.as_ptr().cast(), |
| changelist.len() as type_of_nchanges, |
| eventlist.as_mut_ptr().cast(), |
| eventlist.len() as type_of_nchanges, |
| if let Some(ref timeout) = timeout_opt { |
| timeout as *const timespec |
| } else { |
| ptr::null() |
| }, |
| ) |
| }; |
| Errno::result(res).map(|r| r as usize) |
| } |
| } |
| |
| #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))] |
| type type_of_udata = *mut libc::c_void; |
| #[cfg(target_os = "netbsd")] |
| type type_of_udata = intptr_t; |
| |
| #[cfg(target_os = "netbsd")] |
| type type_of_event_filter = u32; |
| #[cfg(not(target_os = "netbsd"))] |
| type type_of_event_filter = i16; |
| libc_enum! { |
| #[cfg_attr(target_os = "netbsd", repr(u32))] |
| #[cfg_attr(not(target_os = "netbsd"), repr(i16))] |
| #[non_exhaustive] |
| /// Kqueue filter types. These are all the different types of event that a |
| /// kqueue can notify for. |
| pub enum EventFilter { |
| /// Notifies on the completion of a POSIX AIO operation. |
| EVFILT_AIO, |
| #[cfg(target_os = "freebsd")] |
| /// Returns whenever there is no remaining data in the write buffer |
| EVFILT_EMPTY, |
| #[cfg(target_os = "dragonfly")] |
| /// Takes a descriptor as the identifier, and returns whenever one of |
| /// the specified exceptional conditions has occurred on the descriptor. |
| EVFILT_EXCEPT, |
| #[cfg(any(freebsdlike, apple_targets))] |
| /// Establishes a file system monitor. |
| EVFILT_FS, |
| #[cfg(target_os = "freebsd")] |
| /// Notify for completion of a list of POSIX AIO operations. |
| /// # See Also |
| /// [lio_listio(2)](https://www.freebsd.org/cgi/man.cgi?query=lio_listio) |
| EVFILT_LIO, |
| #[cfg(apple_targets)] |
| /// Mach portsets |
| EVFILT_MACHPORT, |
| /// Notifies when a process performs one or more of the requested |
| /// events. |
| EVFILT_PROC, |
| /// Returns events associated with the process referenced by a given |
| /// process descriptor, created by `pdfork()`. The events to monitor are: |
| /// |
| /// - NOTE_EXIT: the process has exited. The exit status will be stored in data. |
| #[cfg(target_os = "freebsd")] |
| EVFILT_PROCDESC, |
| /// Takes a file descriptor as the identifier, and notifies whenever |
| /// there is data available to read. |
| EVFILT_READ, |
| #[cfg(target_os = "freebsd")] |
| #[doc(hidden)] |
| #[deprecated(since = "0.27.0", note = "Never fully implemented by the OS")] |
| EVFILT_SENDFILE, |
| /// Takes a signal number to monitor as the identifier and notifies when |
| /// the given signal is delivered to the process. |
| EVFILT_SIGNAL, |
| /// Establishes a timer and notifies when the timer expires. |
| EVFILT_TIMER, |
| #[cfg(any(freebsdlike, apple_targets))] |
| /// Notifies only when explicitly requested by the user. |
| EVFILT_USER, |
| #[cfg(apple_targets)] |
| /// Virtual memory events |
| EVFILT_VM, |
| /// Notifies when a requested event happens on a specified file. |
| EVFILT_VNODE, |
| /// Takes a file descriptor as the identifier, and notifies whenever |
| /// it is possible to write to the file without blocking. |
| EVFILT_WRITE, |
| } |
| impl TryFrom<type_of_event_filter> |
| } |
| |
| #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))] |
| #[doc(hidden)] |
| pub type type_of_event_flag = u16; |
| #[cfg(target_os = "netbsd")] |
| #[doc(hidden)] |
| pub type type_of_event_flag = u32; |
| libc_bitflags! { |
| /// Event flags. See the man page for details. |
| // There's no useful documentation we can write for the individual flags |
| // that wouldn't simply be repeating the man page. |
| pub struct EventFlag: type_of_event_flag { |
| #[allow(missing_docs)] |
| EV_ADD; |
| #[allow(missing_docs)] |
| EV_CLEAR; |
| #[allow(missing_docs)] |
| EV_DELETE; |
| #[allow(missing_docs)] |
| EV_DISABLE; |
| #[cfg(bsd)] |
| #[allow(missing_docs)] |
| EV_DISPATCH; |
| #[cfg(target_os = "freebsd")] |
| #[allow(missing_docs)] |
| EV_DROP; |
| #[allow(missing_docs)] |
| EV_ENABLE; |
| #[allow(missing_docs)] |
| EV_EOF; |
| #[allow(missing_docs)] |
| EV_ERROR; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| EV_FLAG0; |
| #[allow(missing_docs)] |
| EV_FLAG1; |
| #[cfg(target_os = "dragonfly")] |
| #[allow(missing_docs)] |
| EV_NODATA; |
| #[allow(missing_docs)] |
| EV_ONESHOT; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| EV_OOBAND; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| EV_POLL; |
| #[cfg(bsd)] |
| #[allow(missing_docs)] |
| EV_RECEIPT; |
| } |
| } |
| |
| libc_bitflags!( |
| /// Filter-specific flags. See the man page for details. |
| // There's no useful documentation we can write for the individual flags |
| // that wouldn't simply be repeating the man page. |
| #[allow(missing_docs)] |
| pub struct FilterFlag: u32 { |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_ABSOLUTE; |
| #[allow(missing_docs)] |
| NOTE_ATTRIB; |
| #[allow(missing_docs)] |
| NOTE_CHILD; |
| #[allow(missing_docs)] |
| NOTE_DELETE; |
| #[cfg(target_os = "openbsd")] |
| #[allow(missing_docs)] |
| NOTE_EOF; |
| #[allow(missing_docs)] |
| NOTE_EXEC; |
| #[allow(missing_docs)] |
| NOTE_EXIT; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_EXITSTATUS; |
| #[allow(missing_docs)] |
| NOTE_EXTEND; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFAND; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFCOPY; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFCTRLMASK; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFLAGSMASK; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFNOP; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_FFOR; |
| #[allow(missing_docs)] |
| NOTE_FORK; |
| #[allow(missing_docs)] |
| NOTE_LINK; |
| #[allow(missing_docs)] |
| NOTE_LOWAT; |
| #[cfg(target_os = "freebsd")] |
| #[allow(missing_docs)] |
| NOTE_MSECONDS; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_NONE; |
| #[cfg(any( |
| apple_targets, |
| target_os = "freebsd"))] |
| #[allow(missing_docs)] |
| NOTE_NSECONDS; |
| #[cfg(target_os = "dragonfly")] |
| #[allow(missing_docs)] |
| NOTE_OOB; |
| #[allow(missing_docs)] |
| NOTE_PCTRLMASK; |
| #[allow(missing_docs)] |
| NOTE_PDATAMASK; |
| #[allow(missing_docs)] |
| NOTE_RENAME; |
| #[allow(missing_docs)] |
| NOTE_REVOKE; |
| #[cfg(any( |
| apple_targets, |
| target_os = "freebsd"))] |
| #[allow(missing_docs)] |
| NOTE_SECONDS; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_SIGNAL; |
| #[allow(missing_docs)] |
| NOTE_TRACK; |
| #[allow(missing_docs)] |
| NOTE_TRACKERR; |
| #[cfg(any(apple_targets, freebsdlike))] |
| #[allow(missing_docs)] |
| NOTE_TRIGGER; |
| #[cfg(target_os = "openbsd")] |
| #[allow(missing_docs)] |
| NOTE_TRUNCATE; |
| #[cfg(any( |
| apple_targets, |
| target_os = "freebsd"))] |
| #[allow(missing_docs)] |
| NOTE_USECONDS; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_VM_ERROR; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_VM_PRESSURE; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_VM_PRESSURE_SUDDEN_TERMINATE; |
| #[cfg(apple_targets)] |
| #[allow(missing_docs)] |
| NOTE_VM_PRESSURE_TERMINATE; |
| #[allow(missing_docs)] |
| NOTE_WRITE; |
| } |
| ); |
| |
| #[allow(missing_docs)] |
| #[deprecated(since = "0.27.0", note = "Use KEvent::new instead")] |
| pub fn kqueue() -> Result<Kqueue> { |
| Kqueue::new() |
| } |
| |
| // KEvent can't derive Send because on some operating systems, udata is defined |
| // as a void*. However, KEvent's public API always treats udata as an intptr_t, |
| // which is safe to Send. |
| unsafe impl Send for KEvent {} |
| |
| impl KEvent { |
| #[allow(clippy::needless_update)] // Not needless on all platforms. |
| /// Construct a new `KEvent` suitable for submission to the kernel via the |
| /// `changelist` argument of [`Kqueue::kevent`]. |
| pub fn new( |
| ident: uintptr_t, |
| filter: EventFilter, |
| flags: EventFlag, |
| fflags: FilterFlag, |
| data: intptr_t, |
| udata: intptr_t, |
| ) -> KEvent { |
| KEvent { |
| kevent: libc::kevent { |
| ident, |
| filter: filter as type_of_event_filter, |
| flags: flags.bits(), |
| fflags: fflags.bits(), |
| // data can be either i64 or intptr_t, depending on platform |
| data: data as _, |
| udata: udata as type_of_udata, |
| ..unsafe { mem::zeroed() } |
| }, |
| } |
| } |
| |
| /// Value used to identify this event. The exact interpretation is |
| /// determined by the attached filter, but often is a raw file descriptor. |
| pub fn ident(&self) -> uintptr_t { |
| self.kevent.ident |
| } |
| |
| /// Identifies the kernel filter used to process this event. |
| /// |
| /// Will only return an error if the kernel reports an event via a filter |
| /// that is unknown to Nix. |
| pub fn filter(&self) -> Result<EventFilter> { |
| self.kevent.filter.try_into() |
| } |
| |
| /// Flags control what the kernel will do when this event is added with |
| /// [`Kqueue::kevent`]. |
| pub fn flags(&self) -> EventFlag { |
| EventFlag::from_bits(self.kevent.flags).unwrap() |
| } |
| |
| /// Filter-specific flags. |
| pub fn fflags(&self) -> FilterFlag { |
| FilterFlag::from_bits(self.kevent.fflags).unwrap() |
| } |
| |
| /// Filter-specific data value. |
| pub fn data(&self) -> intptr_t { |
| self.kevent.data as intptr_t |
| } |
| |
| /// Opaque user-defined value passed through the kernel unchanged. |
| pub fn udata(&self) -> intptr_t { |
| self.kevent.udata as intptr_t |
| } |
| } |
| |
| #[allow(missing_docs)] |
| #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] |
| pub fn kevent( |
| kq: &Kqueue, |
| changelist: &[KEvent], |
| eventlist: &mut [KEvent], |
| timeout_ms: usize, |
| ) -> Result<usize> { |
| // Convert ms to timespec |
| let timeout = timespec { |
| tv_sec: (timeout_ms / 1000) as time_t, |
| tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long, |
| }; |
| |
| kq.kevent(changelist, eventlist, Some(timeout)) |
| } |
| |
| #[cfg(any(apple_targets, freebsdlike, target_os = "openbsd"))] |
| type type_of_nchanges = c_int; |
| #[cfg(target_os = "netbsd")] |
| type type_of_nchanges = size_t; |
| |
| #[allow(missing_docs)] |
| #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] |
| pub fn kevent_ts( |
| kq: &Kqueue, |
| changelist: &[KEvent], |
| eventlist: &mut [KEvent], |
| timeout_opt: Option<timespec>, |
| ) -> Result<usize> { |
| kq.kevent(changelist, eventlist, timeout_opt) |
| } |
| |
| /// Modify an existing [`KEvent`]. |
| // Probably should deprecate. Would anybody ever use it over `KEvent::new`? |
| #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] |
| #[inline] |
| pub fn ev_set( |
| ev: &mut KEvent, |
| ident: usize, |
| filter: EventFilter, |
| flags: EventFlag, |
| fflags: FilterFlag, |
| udata: intptr_t, |
| ) { |
| ev.kevent.ident = ident as uintptr_t; |
| ev.kevent.filter = filter as type_of_event_filter; |
| ev.kevent.flags = flags.bits(); |
| ev.kevent.fflags = fflags.bits(); |
| ev.kevent.data = 0; |
| ev.kevent.udata = udata as type_of_udata; |
| } |