1 //! An API for interfacing with `kqueue`.
2 
3 use crate::fd::{AsFd, OwnedFd, RawFd};
4 use crate::pid::Pid;
5 use crate::signal::Signal;
6 use crate::{backend, io};
7 
8 use backend::c::{self, intptr_t, kevent as kevent_t, uintptr_t};
9 use backend::event::syscalls;
10 
11 use alloc::vec::Vec;
12 use core::mem::zeroed;
13 use core::ptr::slice_from_raw_parts_mut;
14 use core::time::Duration;
15 
16 /// A `kqueue` event for use with [`kevent`].
17 #[repr(transparent)]
18 #[derive(Copy, Clone)]
19 pub struct Event {
20     // The layout varies between BSDs and macOS.
21     inner: kevent_t,
22 }
23 
24 impl Event {
25     /// Create a new `Event`.
26     #[allow(clippy::needless_update)]
new(filter: EventFilter, flags: EventFlags, udata: isize) -> Event27     pub fn new(filter: EventFilter, flags: EventFlags, udata: isize) -> Event {
28         let (ident, data, filter, fflags) = match filter {
29             EventFilter::Read(fd) => (fd as uintptr_t, 0, c::EVFILT_READ, 0),
30             EventFilter::Write(fd) => (fd as _, 0, c::EVFILT_WRITE, 0),
31             #[cfg(target_os = "freebsd")]
32             EventFilter::Empty(fd) => (fd as _, 0, c::EVFILT_EMPTY, 0),
33             EventFilter::Vnode { vnode, flags } => (vnode as _, 0, c::EVFILT_VNODE, flags.bits()),
34             EventFilter::Proc { pid, flags } => {
35                 (Pid::as_raw(Some(pid)) as _, 0, c::EVFILT_PROC, flags.bits())
36             }
37             EventFilter::Signal { signal, times: _ } => (signal as _, 0, c::EVFILT_SIGNAL, 0),
38             EventFilter::Timer { ident, timer } => {
39                 #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))]
40                 let (data, fflags) = match timer {
41                     Some(timer) => {
42                         if timer.subsec_millis() == 0 {
43                             (timer.as_secs() as _, c::NOTE_SECONDS)
44                         } else if timer.subsec_nanos() == 0 {
45                             (timer.as_micros() as _, c::NOTE_USECONDS)
46                         } else {
47                             (timer.as_nanos() as _, c::NOTE_NSECONDS)
48                         }
49                     }
50                     None => (intptr_t::MAX, c::NOTE_SECONDS),
51                 };
52                 #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
53                 let (data, fflags) = match timer {
54                     Some(timer) => (timer.as_millis() as _, 0),
55                     None => (intptr_t::MAX, 0),
56                 };
57 
58                 (ident as _, data, c::EVFILT_TIMER, fflags)
59             }
60             #[cfg(any(apple, freebsdlike))]
61             EventFilter::User {
62                 ident,
63                 flags,
64                 user_flags,
65             } => (ident as _, 0, c::EVFILT_USER, flags.bits() | user_flags.0),
66             EventFilter::Unknown => panic!("unknown filter"),
67         };
68 
69         Event {
70             inner: kevent_t {
71                 ident,
72                 filter: filter as _,
73                 flags: flags.bits() as _,
74                 fflags,
75                 data: {
76                     // On OpenBSD, data is an `i64` and not an `isize`.
77                     data as _
78                 },
79                 udata: {
80                     // On NetBSD, udata is an `isize` and not a pointer.
81                     // TODO: Strict provenance, prevent int-to-ptr cast.
82                     udata as _
83                 },
84                 ..unsafe { zeroed() }
85             },
86         }
87     }
88 
89     /// Get the event flags for this event.
flags(&self) -> EventFlags90     pub fn flags(&self) -> EventFlags {
91         EventFlags::from_bits_retain(self.inner.flags as _)
92     }
93 
94     /// Get the user data for this event.
udata(&self) -> isize95     pub fn udata(&self) -> isize {
96         // On NetBSD, udata is an isize and not a pointer.
97         // TODO: Strict provenance, prevent ptr-to-int cast.
98 
99         self.inner.udata as _
100     }
101 
102     /// Get the raw data for this event.
data(&self) -> i64103     pub fn data(&self) -> i64 {
104         // On some BSDs, data is an `isize` and not an `i64`.
105         self.inner.data as _
106     }
107 
108     /// Get the filter of this event.
filter(&self) -> EventFilter109     pub fn filter(&self) -> EventFilter {
110         match self.inner.filter as _ {
111             c::EVFILT_READ => EventFilter::Read(self.inner.ident as _),
112             c::EVFILT_WRITE => EventFilter::Write(self.inner.ident as _),
113             #[cfg(target_os = "freebsd")]
114             c::EVFILT_EMPTY => EventFilter::Empty(self.inner.ident as _),
115             c::EVFILT_VNODE => EventFilter::Vnode {
116                 vnode: self.inner.ident as _,
117                 flags: VnodeEvents::from_bits_retain(self.inner.fflags),
118             },
119             c::EVFILT_PROC => EventFilter::Proc {
120                 pid: Pid::from_raw(self.inner.ident as _).unwrap(),
121                 flags: ProcessEvents::from_bits_retain(self.inner.fflags),
122             },
123             c::EVFILT_SIGNAL => EventFilter::Signal {
124                 signal: Signal::from_raw(self.inner.ident as _).unwrap(),
125                 times: self.inner.data as _,
126             },
127             c::EVFILT_TIMER => EventFilter::Timer {
128                 ident: self.inner.ident as _,
129                 timer: {
130                     let (data, fflags) = (self.inner.data, self.inner.fflags);
131                     #[cfg(not(any(apple, target_os = "freebsd", target_os = "netbsd")))]
132                     let _ = fflags;
133                     #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))]
134                     match fflags as _ {
135                         c::NOTE_SECONDS => Some(Duration::from_secs(data as _)),
136                         c::NOTE_USECONDS => Some(Duration::from_micros(data as _)),
137                         c::NOTE_NSECONDS => Some(Duration::from_nanos(data as _)),
138                         _ => {
139                             // Unknown timer flags.
140                             None
141                         }
142                     }
143                     #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
144                     Some(Duration::from_millis(data as _))
145                 },
146             },
147             #[cfg(any(apple, freebsdlike))]
148             c::EVFILT_USER => EventFilter::User {
149                 ident: self.inner.ident as _,
150                 flags: UserFlags::from_bits_retain(self.inner.fflags),
151                 user_flags: UserDefinedFlags(self.inner.fflags & EVFILT_USER_FLAGS),
152             },
153             _ => EventFilter::Unknown,
154         }
155     }
156 }
157 
158 /// Bottom 24 bits of a u32.
159 #[cfg(any(apple, freebsdlike))]
160 const EVFILT_USER_FLAGS: u32 = 0x00ff_ffff;
161 
162 /// The possible filters for a `kqueue`.
163 #[repr(i16)]
164 #[non_exhaustive]
165 pub enum EventFilter {
166     /// A read filter.
167     Read(RawFd),
168 
169     /// A write filter.
170     Write(RawFd),
171 
172     /// An empty filter.
173     #[cfg(target_os = "freebsd")]
174     Empty(RawFd),
175 
176     /// A VNode filter.
177     Vnode {
178         /// The file descriptor we looked for events in.
179         vnode: RawFd,
180 
181         /// The flags for this event.
182         flags: VnodeEvents,
183     },
184 
185     /// A process filter.
186     Proc {
187         /// The process ID we waited on.
188         pid: Pid,
189 
190         /// The flags for this event.
191         flags: ProcessEvents,
192     },
193 
194     /// A signal filter.
195     Signal {
196         /// The signal number we waited on.
197         signal: Signal,
198 
199         /// The number of times the signal has been
200         /// received since the last call to kevent.
201         times: usize,
202     },
203 
204     /// A timer filter.
205     Timer {
206         /// The identifier for this event.
207         ident: intptr_t,
208 
209         /// The duration for this event.
210         timer: Option<Duration>,
211     },
212 
213     /// A user filter.
214     #[cfg(any(apple, freebsdlike))]
215     User {
216         /// The identifier for this event.
217         ident: intptr_t,
218 
219         /// The flags for this event.
220         flags: UserFlags,
221 
222         /// The user-defined flags for this event.
223         user_flags: UserDefinedFlags,
224     },
225 
226     /// This filter is unknown.
227     ///
228     /// # Panics
229     ///
230     /// Passing this into `Event::new()` will result in a panic.
231     Unknown,
232 }
233 
234 bitflags::bitflags! {
235     /// The flags for a `kqueue` event specifying actions to perform.
236     #[repr(transparent)]
237     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
238     pub struct EventFlags: u16 {
239         /// Add the event to the `kqueue`.
240         const ADD = c::EV_ADD as _;
241 
242         /// Enable the event.
243         const ENABLE = c::EV_ENABLE as _;
244 
245         /// Disable the event.
246         const DISABLE = c::EV_DISABLE as _;
247 
248         /// Delete the event from the `kqueue`.
249         const DELETE = c::EV_DELETE as _;
250 
251         /// TODO
252         const RECEIPT = c::EV_RECEIPT as _;
253 
254         /// Clear the event after it is triggered.
255         const ONESHOT = c::EV_ONESHOT as _;
256 
257         /// TODO
258         const CLEAR = c::EV_CLEAR as _;
259 
260         /// TODO
261         const EOF = c::EV_EOF as _;
262 
263         /// TODO
264         const ERROR = c::EV_ERROR as _;
265 
266         /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
267         const _ = !0;
268     }
269 }
270 
271 bitflags::bitflags! {
272     /// The flags for a virtual node event.
273     #[repr(transparent)]
274     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
275     pub struct VnodeEvents: u32 {
276         /// The file was deleted.
277         const DELETE = c::NOTE_DELETE;
278 
279         /// The file was written to.
280         const WRITE = c::NOTE_WRITE;
281 
282         /// The file was extended.
283         const EXTEND = c::NOTE_EXTEND;
284 
285         /// The file had its attributes changed.
286         const ATTRIBUTES = c::NOTE_ATTRIB;
287 
288         /// The file was renamed.
289         const RENAME = c::NOTE_RENAME;
290 
291         /// Access to the file was revoked.
292         const REVOKE = c::NOTE_REVOKE;
293 
294         /// The link count of the file has changed.
295         const LINK = c::NOTE_LINK;
296 
297         /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
298         const _ = !0;
299     }
300 }
301 
302 bitflags::bitflags! {
303     /// The flags for a process event.
304     #[repr(transparent)]
305     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
306     pub struct ProcessEvents: u32 {
307         /// The process exited.
308         const EXIT = c::NOTE_EXIT;
309 
310         /// The process forked itself.
311         const FORK = c::NOTE_FORK;
312 
313         /// The process executed a new process.
314         const EXEC = c::NOTE_EXEC;
315 
316         /// Follow the process through `fork` calls (write only).
317         const TRACK = c::NOTE_TRACK;
318 
319         /// An error has occurred with following the process.
320         const TRACKERR = c::NOTE_TRACKERR;
321 
322         /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
323         const _ = !0;
324     }
325 }
326 
327 #[cfg(any(apple, freebsdlike))]
328 bitflags::bitflags! {
329     /// The flags for a user event.
330     #[repr(transparent)]
331     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
332     pub struct UserFlags: u32 {
333         /// Ignore the user input flags.
334         const NOINPUT = c::NOTE_FFNOP;
335 
336         /// Bitwise AND `fflags`.
337         const AND = c::NOTE_FFAND;
338 
339         /// Bitwise OR `fflags`.
340         const OR = c::NOTE_FFOR;
341 
342         /// Copy `fflags`.
343         const COPY = c::NOTE_FFCOPY;
344 
345         /// Control mask for operations.
346         const CTRLMASK = c::NOTE_FFCTRLMASK;
347 
348         /// User defined flags for masks.
349         const UDFMASK = c::NOTE_FFLAGSMASK;
350 
351         /// Trigger the event.
352         const TRIGGER = c::NOTE_TRIGGER;
353 
354         /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
355         const _ = !0;
356     }
357 }
358 
359 /// User-defined flags.
360 ///
361 /// Only the lower 24 bits are used in this struct.
362 #[repr(transparent)]
363 #[cfg(any(apple, freebsdlike))]
364 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
365 pub struct UserDefinedFlags(u32);
366 
367 #[cfg(any(apple, freebsdlike))]
368 impl UserDefinedFlags {
369     /// Create a new `UserDefinedFlags` from a `u32`.
new(flags: u32) -> Self370     pub fn new(flags: u32) -> Self {
371         Self(flags & EVFILT_USER_FLAGS)
372     }
373 
374     /// Get the underlying `u32`.
get(self) -> u32375     pub fn get(self) -> u32 {
376         self.0
377     }
378 }
379 
380 /// `kqueue()`—Create a new `kqueue` file descriptor.
381 ///
382 /// # References
383 ///  - [Apple]
384 ///  - [FreeBSD]
385 ///  - [OpenBSD]
386 ///  - [NetBSD]
387 ///  - [DragonFly BSD]
388 ///
389 /// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html
390 /// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
391 /// [OpenBSD]: https://man.openbsd.org/kqueue.2
392 /// [NetBSD]: https://man.netbsd.org/kqueue.2
393 /// [DragonFly BSD]: https://man.dragonflybsd.org/?command=kqueue&section=2
kqueue() -> io::Result<OwnedFd>394 pub fn kqueue() -> io::Result<OwnedFd> {
395     syscalls::kqueue()
396 }
397 
398 /// `kevent(kqueue, changelist, eventlist, timeout)`—Wait for events on a
399 /// `kqueue`.
400 ///
401 /// Note: in order to receive events, make sure to allocate capacity in the
402 /// eventlist! Otherwise, the function will return immediately.
403 ///
404 /// # Safety
405 ///
406 /// The file descriptors referred to by the `Event` structs must be valid for
407 /// the lifetime of the `kqueue` file descriptor.
408 ///
409 /// # References
410 ///  - [Apple]
411 ///  - [FreeBSD]
412 ///  - [OpenBSD]
413 ///  - [NetBSD]
414 ///  - [DragonFly BSD]
415 ///
416 /// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kevent.2.html
417 /// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=kevent&sektion=2
418 /// [OpenBSD]: https://man.openbsd.org/kevent.2
419 /// [NetBSD]: https://man.netbsd.org/kevent.2
420 /// [DragonFly BSD]: https://man.dragonflybsd.org/?command=kevent&section=2
kevent( kqueue: impl AsFd, changelist: &[Event], eventlist: &mut Vec<Event>, timeout: Option<Duration>, ) -> io::Result<usize>421 pub unsafe fn kevent(
422     kqueue: impl AsFd,
423     changelist: &[Event],
424     eventlist: &mut Vec<Event>,
425     timeout: Option<Duration>,
426 ) -> io::Result<usize> {
427     let timeout = timeout.map(|timeout| backend::c::timespec {
428         tv_sec: timeout.as_secs() as _,
429         tv_nsec: timeout.subsec_nanos() as _,
430     });
431 
432     // Populate the event list with events.
433     eventlist.set_len(0);
434     let out_slice = slice_from_raw_parts_mut(eventlist.as_mut_ptr().cast(), eventlist.capacity());
435     let res = syscalls::kevent(
436         kqueue.as_fd(),
437         changelist,
438         &mut *out_slice,
439         timeout.as_ref(),
440     )
441     .map(|res| res as _);
442 
443     // Update the event list.
444     if let Ok(len) = res {
445         eventlist.set_len(len);
446     }
447 
448     res
449 }
450