diff --git a/src/event/kqueue.rs b/src/event/kqueue.rs index 897d93980..a4c0b179f 100644 --- a/src/event/kqueue.rs +++ b/src/event/kqueue.rs @@ -65,6 +65,8 @@ impl Event { flags, user_flags, } => (ident as _, 0, c::EVFILT_USER, flags.bits() | user_flags.0), + #[cfg(apple)] + EventFilter::MachPort { port } => (port as _, 0, c::EVFILT_MACHPORT, 0), EventFilter::Unknown => panic!("unknown filter"), }; @@ -150,6 +152,10 @@ impl Event { flags: UserFlags::from_bits_retain(self.inner.fflags), user_flags: UserDefinedFlags(self.inner.fflags & EVFILT_USER_FLAGS), }, + #[cfg(apple)] + c::EVFILT_MACHPORT => EventFilter::MachPort { + port: self.inner.ident as _, + }, _ => EventFilter::Unknown, } } @@ -159,6 +165,13 @@ impl Event { #[cfg(any(apple, freebsdlike))] const EVFILT_USER_FLAGS: u32 = 0x00ff_ffff; +/// The name of a Mach port, as `EVFILT_MACHPORT` identifies it. +/// +/// This is a `mach_port_name_t` rather than a file descriptor, so it is kept +/// distinct from `RawFd`. +#[cfg(apple)] +pub type RawMachPort = u32; + /// The possible filters for a `kqueue`. #[repr(i16)] #[non_exhaustive] @@ -210,6 +223,17 @@ pub enum EventFilter { timer: Option, }, + /// A Mach port filter. + /// + /// Reports that a message is waiting on a Mach port or port set, without + /// dequeuing it. The identifier is a port name rather than a file + /// descriptor, and the port must hold a receive right. + #[cfg(apple)] + MachPort { + /// The name of the port or port set to watch. + port: RawMachPort, + }, + /// A user filter. #[cfg(any(apple, freebsdlike))] User { diff --git a/tests/event/kqueue_machport.rs b/tests/event/kqueue_machport.rs new file mode 100644 index 000000000..7e8202d3a --- /dev/null +++ b/tests/event/kqueue_machport.rs @@ -0,0 +1,99 @@ +//! Tests for `EventFilter::MachPort`. + +use std::mem::MaybeUninit; +use std::ptr::null_mut; + +use rustix::event::kqueue::{kevent, kqueue, Event, EventFilter, EventFlags}; + +type KernReturn = libc::c_int; + +extern "C" { + static mach_task_self_: u32; + fn mach_port_allocate(task: u32, right: libc::c_uint, name: *mut u32) -> KernReturn; + fn mach_port_insert_right( + task: u32, + name: u32, + poly: u32, + poly_poly: libc::c_uint, + ) -> KernReturn; + fn mach_msg( + msg: *mut MachMsgHeader, + option: libc::c_int, + send_size: u32, + rcv_size: u32, + rcv_name: u32, + timeout: u32, + notify: u32, + ) -> KernReturn; +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct MachMsgHeader { + bits: u32, + size: u32, + remote_port: u32, + local_port: u32, + voucher_port: u32, + id: i32, +} + +/// A port with a message queued is reported ready, and the message is left in +/// place for the caller to receive itself. +#[test] +fn test_kqueue_machport() { + const MACH_PORT_RIGHT_RECEIVE: libc::c_uint = 1; + const MACH_MSG_TYPE_MAKE_SEND: libc::c_uint = 20; + const MACH_MSG_TYPE_COPY_SEND: u32 = 19; + const MACH_SEND_MSG: libc::c_int = 1; + + let mut port = 0u32; + unsafe { + assert_eq!( + mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &mut port), + 0 + ); + assert_eq!( + mach_port_insert_right(mach_task_self_, port, port, MACH_MSG_TYPE_MAKE_SEND), + 0 + ); + } + + let queue = kqueue().unwrap(); + let change = Event::new( + EventFilter::MachPort { port }, + EventFlags::ADD | EventFlags::ENABLE, + null_mut(), + ); + // Registering only: an events buffer with no timeout would block here. + let mut none: [MaybeUninit; 0] = []; + unsafe { kevent(&queue, &[change], &mut none[..], None).unwrap() }; + + // Nothing has been sent, so nothing is ready. + let mut events = [MaybeUninit::::uninit(); 1]; + let (ready, _) = + unsafe { kevent(&queue, &[], &mut events[..], Some(Default::default())).unwrap() }; + assert!(ready.is_empty()); + + let size = core::mem::size_of::() as u32; + let mut message = MachMsgHeader { + bits: MACH_MSG_TYPE_COPY_SEND, + size, + remote_port: port, + id: 1234, + ..Default::default() + }; + assert_eq!( + unsafe { mach_msg(&mut message, MACH_SEND_MSG, size, 0, 0, 0, 0) }, + 0 + ); + + let mut events = [MaybeUninit::::uninit(); 1]; + let (ready, _) = + unsafe { kevent(&queue, &[], &mut events[..], Some(Default::default())).unwrap() }; + assert_eq!(ready.len(), 1); + assert!(matches!( + ready[0].filter(), + EventFilter::MachPort { port: p } if p == port + )); +} diff --git a/tests/event/main.rs b/tests/event/main.rs index ad29a36f3..91108af19 100644 --- a/tests/event/main.rs +++ b/tests/event/main.rs @@ -13,6 +13,8 @@ mod epoll_timeout; #[cfg(not(windows))] #[cfg(not(target_os = "wasi"))] mod eventfd; +#[cfg(apple)] +mod kqueue_machport; mod poll; #[cfg(solarish)] mod port;