diff --git a/src/backend/libc/io/syscalls.rs b/src/backend/libc/io/syscalls.rs index 5075cfcaa..32d01d712 100644 --- a/src/backend/libc/io/syscalls.rs +++ b/src/backend/libc/io/syscalls.rs @@ -27,6 +27,18 @@ use core::ptr; #[cfg(all(feature = "fs", feature = "net"))] use libc_errno::errno; +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" +))] +use crate::io::kqueue::Event; + pub(crate) fn read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result { let nread = unsafe { ret_ssize_t(c::read( @@ -450,6 +462,46 @@ pub(crate) fn ioctl_tiocnxcl(fd: BorrowedFd) -> io::Result<()> { unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCNXCL as _)) } } +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" +))] +pub(crate) fn kqueue() -> io::Result { + unsafe { ret_owned_fd(c::kqueue()) } +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" +))] +pub(crate) unsafe fn kevent( + kq: BorrowedFd<'_>, + changelist: &[Event], + eventlist: &mut [MaybeUninit], + timeout: Option<&c::timespec>, +) -> io::Result { + ret_c_int(c::kevent( + borrowed_fd(kq), + changelist.as_ptr() as *const _, + changelist.len() as _, + eventlist.as_mut_ptr() as *mut _, + eventlist.len() as _, + timeout.map_or(core::ptr::null(), |t| t as *const _), + )) +} + #[cfg(not(target_os = "wasi"))] pub(crate) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> { unsafe { diff --git a/src/io/kqueue.rs b/src/io/kqueue.rs new file mode 100644 index 000000000..776e4089e --- /dev/null +++ b/src/io/kqueue.rs @@ -0,0 +1,448 @@ +//! An API for interfacing with `kqueue`. + +use crate::backend; +use crate::fd::{AsFd, AsRawFd, OwnedFd, RawFd}; +use crate::io; + +use backend::c::{self, kevent as kevent_t, uintptr_t}; +use backend::io::syscalls; + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", +))] +use backend::c::intptr_t; + +use alloc::vec::Vec; +use core::ptr::slice_from_raw_parts_mut; +use core::time::Duration; + +/// A `kqueue` event. +#[repr(transparent)] +#[derive(Copy, Clone)] +pub struct Event { + // The layout varies between BSDs and macOS. + inner: kevent_t, +} + +impl Event { + /// Create a new `Event`. + #[allow(clippy::needless_update)] + pub fn new(filter: EventFilter, flags: EventFlags, udata: isize) -> Event { + let (ident, filter, fflags) = match filter { + EventFilter::Read(fd) => (fd.as_raw_fd() as uintptr_t, c::EVFILT_READ, 0), + EventFilter::Write(fd) => (fd.as_raw_fd() as _, c::EVFILT_WRITE, 0), + EventFilter::Vnode { vnode, flags } => { + (vnode.as_raw_fd() as _, c::EVFILT_VNODE, flags.bits()) + } + #[cfg(feature = "process")] + EventFilter::Proc { pid, flags } => ( + crate::process::Pid::as_raw(Some(pid)) as _, + c::EVFILT_PROC, + flags.bits(), + ), + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + ))] + EventFilter::Timer(timer) => { + let (data, fflags) = match timer { + Some(timer) => { + if timer.subsec_millis() == 0 { + (timer.as_secs() as _, c::NOTE_SECONDS) + } else if timer.subsec_nanos() == 0 { + (timer.as_micros() as _, c::NOTE_USECONDS) + } else { + (timer.as_nanos() as _, c::NOTE_NSECONDS) + } + } + None => (uintptr_t::MAX, c::NOTE_SECONDS), + }; + + (data, c::EVFILT_TIMER, fflags) + } + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", + ))] + EventFilter::User { + ident, + flags, + user_flags, + } => (ident as _, c::EVFILT_USER, flags.bits() | user_flags.0), + EventFilter::Unknown => panic!("unknown filter"), + }; + + Event { + inner: kevent_t { + ident, + filter: filter as _, + flags: flags.bits() as _, + fflags, + data: 0, + udata: { + // On netbsd, udata is an isize and not a pointer. + // TODO: Strict provenance, prevent int-to-ptr cast. + udata as _ + }, + ..unsafe { core::mem::zeroed() } + }, + } + } + + /// Get the event flags for this event. + pub fn flags(&self) -> EventFlags { + EventFlags::from_bits_truncate(self.inner.flags as _) + } + + /// Get the user data for this event. + pub fn udata(&self) -> isize { + // On netbsd, udata is an isize and not a pointer. + // TODO: Strict provenance, prevent ptr-to-int cast. + + self.inner.udata as _ + } + + /// Get the filter of this event. + pub fn filter(&self) -> EventFilter { + match self.inner.filter as _ { + c::EVFILT_READ => EventFilter::Read(self.inner.ident as _), + c::EVFILT_WRITE => EventFilter::Write(self.inner.ident as _), + c::EVFILT_VNODE => EventFilter::Vnode { + vnode: self.inner.ident as _, + flags: VnodeEvents::from_bits_truncate(self.inner.fflags), + }, + #[cfg(feature = "process")] + c::EVFILT_PROC => EventFilter::Proc { + pid: unsafe { crate::process::Pid::from_raw(self.inner.ident as _) }.unwrap(), + flags: ProcessEvents::from_bits_truncate(self.inner.fflags), + }, + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + ))] + c::EVFILT_TIMER => EventFilter::Timer({ + let (data, fflags) = (self.inner.data, self.inner.fflags); + match fflags as _ { + c::NOTE_SECONDS => Some(Duration::from_secs(data as _)), + c::NOTE_USECONDS => Some(Duration::from_micros(data as _)), + c::NOTE_NSECONDS => Some(Duration::from_nanos(data as _)), + _ => { + // Unknown timer flags. + None + } + } + }), + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", + ))] + c::EVFILT_USER => EventFilter::User { + ident: self.inner.ident as _, + flags: UserFlags::from_bits_truncate(self.inner.fflags), + user_flags: UserDefinedFlags(self.inner.fflags & EVFILT_USER_FLAGS), + }, + _ => EventFilter::Unknown, + } + } +} + +/// Bottom 24 bits of a u32. +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", +))] +const EVFILT_USER_FLAGS: u32 = 0x00ff_ffff; + +/// The possible filters for a `kqueue`. +#[repr(i16)] +#[non_exhaustive] +pub enum EventFilter { + /// A read filter. + Read(RawFd), + + /// A write filter. + Write(RawFd), + + /// A VNode filter. + Vnode { + /// The file descriptor we looked for events in. + vnode: RawFd, + + /// The flags for this event. + flags: VnodeEvents, + }, + + /// A process filter. + #[cfg(feature = "process")] + Proc { + /// The process ID we waited on. + pid: crate::process::Pid, + + /// The flags for this event. + flags: ProcessEvents, + }, + + /// A timer filter. + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + ))] + Timer(Option), + + /// A user filter. + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", + ))] + User { + /// The identifier for this event. + ident: intptr_t, + + /// The flags for this event. + flags: UserFlags, + + /// The user-defined flags for this event. + user_flags: UserDefinedFlags, + }, + + /// This filter is unknown. + /// + /// # Panics + /// + /// Passing this into `Event::new()` will result in a panic. + Unknown, +} + +bitflags::bitflags! { + /// The flags for a `kqueue` event. + pub struct EventFlags: u16 { + /// Add the event to the `kqueue`. + const ADD = c::EV_ADD as _; + + /// Enable the event. + const ENABLE = c::EV_ENABLE as _; + + /// Disable the event. + const DISABLE = c::EV_DISABLE as _; + + /// Delete the event from the `kqueue`. + const DELETE = c::EV_DELETE as _; + + /// TODO + const RECEIPT = c::EV_RECEIPT as _; + + /// Clear the event after it is triggered. + const ONESHOT = c::EV_ONESHOT as _; + + /// TODO + const CLEAR = c::EV_CLEAR as _; + + /// TODO + const EOF = c::EV_EOF as _; + + /// TODO + const ERROR = c::EV_ERROR as _; + } +} + +bitflags::bitflags! { + /// The flags for a virtual node event. + pub struct VnodeEvents : u32 { + /// The file was deleted. + const DELETE = c::NOTE_DELETE; + + /// The file was written to. + const WRITE = c::NOTE_WRITE; + + /// The file was extended. + const EXTEND = c::NOTE_EXTEND; + + /// The file had its attributes changed. + const ATTRIBUTES = c::NOTE_ATTRIB; + + /// The file was renamed. + const RENAME = c::NOTE_RENAME; + + /// Access to the file was revoked. + const REVOKE = c::NOTE_REVOKE; + } +} + +#[cfg(feature = "process")] +bitflags::bitflags! { + /// The flags for a process event. + pub struct ProcessEvents : u32 { + /// The process exited. + const EXIT = c::NOTE_EXIT; + + /// The process forked itself. + const FORK = c::NOTE_FORK; + + /// The process executed a new process. + const EXEC = c::NOTE_EXEC; + + /// TODO + const TRACK = c::NOTE_TRACK; + } +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", +))] +bitflags::bitflags! { + /// The flags for a user event. + pub struct UserFlags : u32 { + /// Ignore the user input flags. + const NOINPUT = c::NOTE_FFNOP; + + /// Bitwise AND fflags. + const AND = c::NOTE_FFAND; + + /// Bitwise OR fflags. + const OR = c::NOTE_FFOR; + + /// Copy fflags. + const COPY = c::NOTE_FFCOPY; + + /// Control mask for operations. + const CTRLMASK = c::NOTE_FFCTRLMASK; + + /// User defined flags for masks. + const UDFMASK = c::NOTE_FFLAGSMASK; + + /// Trigger the event. + const TRIGGER = c::NOTE_TRIGGER; + } +} + +/// User-defined flags. +/// +/// Only the lower 24 bits are used in this struct. +#[repr(transparent)] +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", +))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UserDefinedFlags(u32); + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "dragonfly", + target_os = "freebsd", +))] +impl UserDefinedFlags { + /// Create a new `UserDefinedFlags` from a `u32`. + pub fn new(flags: u32) -> Self { + Self(flags & EVFILT_USER_FLAGS) + } + + /// Get the underlying `u32`. + pub fn get(self) -> u32 { + self.0 + } +} + +/// `kqueue()`- Create a new `kqueue` file descriptor. +/// +/// # References +/// +/// - [Apple] +/// - [FreeBSD] +/// - [OpenBSD] +/// - [NetBSD] +/// - [DragonflyBSD] +/// +/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html +/// [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 +/// [OpenBSD]: https://man.openbsd.org/kqueue.2 +/// [NetBSD]: https://man.netbsd.org/kqueue.2 +/// [DragonflyBSD]: https://www.dragonflybsd.org/cgi/web-man/?command=kqueue +pub fn kqueue() -> io::Result { + syscalls::kqueue() +} + +/// `kevent()`- Wait for events on a `kqueue`. +/// +/// # Safety +/// +/// The file descriptors referred to by the `Event` structs must be valid for the lifetime +/// of the `kqueue` file descriptor. +/// +/// # References +/// +/// - [Apple] +/// - [FreeBSD] +/// - [OpenBSD] +/// - [NetBSD] +/// - [DragonflyBSD] +/// +/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kevent.2.html +/// [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?query=kevent&sektion=2 +/// [OpenBSD]: https://man.openbsd.org/kevent.2 +/// [NetBSD]: https://man.netbsd.org/kevent.2 +/// [DragonflyBSD]: https://www.dragonflybsd.org/cgi/web-man/?command=kevent +pub unsafe fn kevent( + kqueue: impl AsFd, + changelist: &[Event], + eventlist: &mut Vec, + timeout: Option, +) -> io::Result { + let timeout = timeout.map(|timeout| crate::backend::c::timespec { + tv_sec: timeout.as_secs() as _, + tv_nsec: timeout.subsec_nanos() as _, + }); + + // Populate the event list with events. + eventlist.set_len(0); + let out_slice = + slice_from_raw_parts_mut(eventlist.as_mut_ptr() as *mut _, eventlist.capacity()); + let res = syscalls::kevent( + kqueue.as_fd(), + changelist, + &mut *out_slice, + timeout.as_ref(), + ) + .map(|res| res as _); + + // Update the event list. + if let Ok(len) = res { + eventlist.set_len(len); + } + + res +} diff --git a/src/io/mod.rs b/src/io/mod.rs index b6b2ff908..6091d0cec 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -13,6 +13,17 @@ pub(crate) mod fd; mod ioctl; #[cfg(not(any(windows, target_os = "redox")))] mod is_read_write; +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" +))] +pub mod kqueue; #[cfg(not(any(windows, target_os = "wasi")))] mod pipe; mod poll;