| //! 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<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() 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(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<type_of_event_filter> | |
| } | |
| #[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(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> { | |
| 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( | |
| 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<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; | |
| } | |
| #[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::<libc::kevent>(), mem::size_of::<KEvent>()); | |
| } | |
| #[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()); | |
| } |