//! 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::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 Kqueue { /// Create a new kernel event queue. pub fn new() -> Result { 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, ) -> Result { let res = unsafe { libc::kevent( self.0.as_raw_fd(), changelist.as_ptr() as *const libc::kevent, changelist.len() as type_of_nchanges, eventlist.as_mut_ptr() as *mut libc::kevent, 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( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "openbsd" ))] type type_of_udata = *mut libc::c_void; #[cfg(any(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(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] /// 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(any(target_os = "ios", target_os = "macos"))] /// 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(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] /// Notifies only when explicitly requested by the user. EVFILT_USER, #[cfg(any(target_os = "ios", target_os = "macos"))] /// 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 } #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "openbsd" ))] #[doc(hidden)] pub type type_of_event_flag = u16; #[cfg(any(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(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] #[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(any(target_os = "macos", target_os = "ios"))] #[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(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] EV_OOBAND; #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] EV_POLL; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] #[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(any(target_os = "macos", target_os = "ios"))] #[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(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_EXITSTATUS; #[allow(missing_docs)] NOTE_EXTEND; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_FFAND; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_FFCOPY; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_FFCTRLMASK; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_FFLAGSMASK; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_FFNOP; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[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(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_NONE; #[cfg(any(target_os = "macos", target_os = "ios", 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(target_os = "macos", target_os = "ios", target_os = "freebsd"))] #[allow(missing_docs)] NOTE_SECONDS; #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_SIGNAL; #[allow(missing_docs)] NOTE_TRACK; #[allow(missing_docs)] NOTE_TRACKERR; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] #[allow(missing_docs)] NOTE_TRIGGER; #[cfg(target_os = "openbsd")] #[allow(missing_docs)] NOTE_TRUNCATE; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] #[allow(missing_docs)] NOTE_USECONDS; #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_VM_ERROR; #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_VM_PRESSURE; #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(missing_docs)] NOTE_VM_PRESSURE_SUDDEN_TERMINATE; #[cfg(any(target_os = "macos", target_os = "ios"))] #[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::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 { 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 { // 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( target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly", 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, ) -> Result { 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; } #[test] fn test_struct_kevent() { use std::mem; let udata: intptr_t = 12345; let actual = KEvent::new( 0xdead_beef, EventFilter::EVFILT_READ, EventFlag::EV_ONESHOT | EventFlag::EV_ADD, FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, 0x1337, udata, ); assert_eq!(0xdead_beef, actual.ident()); let filter = actual.kevent.filter; assert_eq!(libc::EVFILT_READ, filter); assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); assert_eq!(0x1337, actual.data()); assert_eq!(udata as type_of_udata, actual.udata() as type_of_udata); assert_eq!(mem::size_of::(), mem::size_of::()); } #[test] fn test_kevent_filter() { let udata: intptr_t = 12345; let actual = KEvent::new( 0xdead_beef, EventFilter::EVFILT_READ, EventFlag::EV_ONESHOT | EventFlag::EV_ADD, FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, 0x1337, udata, ); assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); }