From 34f0eea7e3e4d6448efe2d9ffbb344f73d2e0fbe Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 11 Dec 2022 09:43:30 -0700 Subject: Rustier kqueue API * Prefer methods instead of functions. * Create a newtype for a kqueue. * Document everything. * Deprecate EVFILT_SENDFILE, because it was never fully implemented upstream. * Add support to the libc_enum! macro to be able to deprecate variants. --- src/macros.rs | 2 + src/sys/event.rs | 213 +++++++++++++++++++++++++++++++++++++++++++++++-------- src/sys/mod.rs | 1 - 3 files changed, 184 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/macros.rs b/src/macros.rs index 07d80f68..5d83a5ac 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -132,6 +132,8 @@ macro_rules! libc_enum { impl ::std::convert::TryFrom<$repr> for $BitFlags { type Error = $crate::Error; #[allow(unused_doc_comments)] + #[allow(deprecated)] + #[allow(unused_attributes)] fn try_from(x: $repr) -> $crate::Result { match x { $($try_froms)* diff --git a/src/sys/event.rs b/src/sys/event.rs index f21ba173..5dcf121a 100644 --- a/src/sys/event.rs +++ b/src/sys/event.rs @@ -1,5 +1,7 @@ -/* TOOD: Implement for other kqueue based systems - */ +//! 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"))] @@ -8,16 +10,74 @@ use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; 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::{AsFd, AsRawFd, FromRawFd, OwnedFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; use std::ptr; -// Redefine kevent in terms of programmer-friendly enums and bitfields. +/// 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", @@ -37,22 +97,34 @@ 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, - /// Returns whenever there is no remaining data in the write buffer #[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: @@ -60,20 +132,31 @@ libc_enum! { /// - 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, - /// Returns whenever an asynchronous `sendfile()` call completes. #[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 @@ -86,131 +169,194 @@ libc_enum! { 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; } ); -pub fn kqueue() -> Result { - let res = unsafe { libc::kqueue() }; - - Errno::result(res).map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) +#[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 @@ -220,6 +366,8 @@ 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, @@ -242,33 +390,46 @@ impl KEvent { } } + /// 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 } } -pub fn kevent( - kq: Fd, +#[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, @@ -279,7 +440,7 @@ pub fn kevent( tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long, }; - kevent_ts(kq, changelist, eventlist, Some(timeout)) + kq.kevent(changelist, eventlist, Some(timeout)) } #[cfg(any( @@ -293,30 +454,20 @@ type type_of_nchanges = c_int; #[cfg(target_os = "netbsd")] type type_of_nchanges = size_t; -pub fn kevent_ts( - kq: Fd, +#[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 { - let res = unsafe { - libc::kevent( - kq.as_fd().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) + 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, diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 2065059d..383f08df 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -25,7 +25,6 @@ feature! { target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] - #[allow(missing_docs)] pub mod event; #[cfg(any(target_os = "android", target_os = "linux"))] -- cgit v1.2.3