Source code

Revision control

Copy as Markdown

Other Tools

//! Kernel event notification mechanism
//!
//! # See Also
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
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;
}