1 //! Kernel event notification mechanism
2 //!
3 //! # See Also
4 //! [kqueue(2)](https://www.freebsd.org/cgi/man.cgi?query=kqueue)
5 
6 use crate::{Errno, Result};
7 #[cfg(not(target_os = "netbsd"))]
8 use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t};
9 #[cfg(target_os = "netbsd")]
10 use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t};
11 use std::convert::TryInto;
12 use std::mem;
13 use std::os::fd::{AsFd, BorrowedFd};
14 use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
15 use std::ptr;
16 
17 /// A kernel event queue.  Used to notify a process of various asynchronous
18 /// events.
19 #[repr(C)]
20 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21 pub struct KEvent {
22     kevent: libc::kevent,
23 }
24 
25 /// A kernel event queue.
26 ///
27 /// Used by the kernel to notify the process of various types of asynchronous
28 /// events.
29 #[repr(transparent)]
30 #[derive(Debug)]
31 pub struct Kqueue(OwnedFd);
32 
33 impl AsFd for Kqueue {
as_fd(&self) -> BorrowedFd<'_>34     fn as_fd(&self) -> BorrowedFd<'_> {
35         self.0.as_fd()
36     }
37 }
38 
39 impl From<Kqueue> for OwnedFd {
from(value: Kqueue) -> Self40     fn from(value: Kqueue) -> Self {
41         value.0
42     }
43 }
44 
45 impl Kqueue {
46     /// Create a new kernel event queue.
new() -> Result<Self>47     pub fn new() -> Result<Self> {
48         let res = unsafe { libc::kqueue() };
49 
50         Errno::result(res).map(|fd| unsafe { Self(OwnedFd::from_raw_fd(fd)) })
51     }
52 
53     /// Register new events with the kqueue, and return any pending events to
54     /// the user.
55     ///
56     /// This method will block until either the timeout expires, or a registered
57     /// event triggers a notification.
58     ///
59     /// # Arguments
60     /// - `changelist` - Any new kevents to register for notifications.
61     /// - `eventlist` - Storage space for the kernel to return notifications.
62     /// - `timeout` - An optional timeout.
63     ///
64     /// # Returns
65     /// Returns the number of events placed in the `eventlist`.  If an error
66     /// occurs while processing an element of the `changelist` and there is
67     /// enough room in the `eventlist`, then the event will be placed in the
68     /// `eventlist` with `EV_ERROR` set in `flags` and the system error in
69     /// `data`.
kevent( &self, changelist: &[KEvent], eventlist: &mut [KEvent], timeout_opt: Option<timespec>, ) -> Result<usize>70     pub fn kevent(
71         &self,
72         changelist: &[KEvent],
73         eventlist: &mut [KEvent],
74         timeout_opt: Option<timespec>,
75     ) -> Result<usize> {
76         let res = unsafe {
77             libc::kevent(
78                 self.0.as_raw_fd(),
79                 changelist.as_ptr().cast(),
80                 changelist.len() as type_of_nchanges,
81                 eventlist.as_mut_ptr().cast(),
82                 eventlist.len() as type_of_nchanges,
83                 if let Some(ref timeout) = timeout_opt {
84                     timeout as *const timespec
85                 } else {
86                     ptr::null()
87                 },
88             )
89         };
90         Errno::result(res).map(|r| r as usize)
91     }
92 }
93 
94 #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))]
95 type type_of_udata = *mut libc::c_void;
96 #[cfg(target_os = "netbsd")]
97 type type_of_udata = intptr_t;
98 
99 #[cfg(target_os = "netbsd")]
100 type type_of_event_filter = u32;
101 #[cfg(not(target_os = "netbsd"))]
102 type type_of_event_filter = i16;
103 libc_enum! {
104     #[cfg_attr(target_os = "netbsd", repr(u32))]
105     #[cfg_attr(not(target_os = "netbsd"), repr(i16))]
106     #[non_exhaustive]
107     /// Kqueue filter types.  These are all the different types of event that a
108     /// kqueue can notify for.
109     pub enum EventFilter {
110         /// Notifies on the completion of a POSIX AIO operation.
111         EVFILT_AIO,
112         #[cfg(target_os = "freebsd")]
113         /// Returns whenever there is no remaining data in the write buffer
114         EVFILT_EMPTY,
115         #[cfg(target_os = "dragonfly")]
116         /// Takes a descriptor as the identifier, and returns whenever one of
117         /// the specified exceptional conditions has occurred on the descriptor.
118         EVFILT_EXCEPT,
119         #[cfg(any(freebsdlike, apple_targets))]
120         /// Establishes a file system monitor.
121         EVFILT_FS,
122         #[cfg(target_os = "freebsd")]
123         /// Notify for completion of a list of POSIX AIO operations.
124         /// # See Also
125         /// [lio_listio(2)](https://www.freebsd.org/cgi/man.cgi?query=lio_listio)
126         EVFILT_LIO,
127         #[cfg(apple_targets)]
128         /// Mach portsets
129         EVFILT_MACHPORT,
130         /// Notifies when a process performs one or more of the requested
131         /// events.
132         EVFILT_PROC,
133         /// Returns events associated with the process referenced by a given
134         /// process descriptor, created by `pdfork()`. The events to monitor are:
135         ///
136         /// - NOTE_EXIT: the process has exited. The exit status will be stored in data.
137         #[cfg(target_os = "freebsd")]
138         EVFILT_PROCDESC,
139         /// Takes a file descriptor as the identifier, and notifies whenever
140         /// there is data available to read.
141         EVFILT_READ,
142         #[cfg(target_os = "freebsd")]
143         #[doc(hidden)]
144         #[deprecated(since = "0.27.0", note = "Never fully implemented by the OS")]
145         EVFILT_SENDFILE,
146         /// Takes a signal number to monitor as the identifier and notifies when
147         /// the given signal is delivered to the process.
148         EVFILT_SIGNAL,
149         /// Establishes a timer and notifies when the timer expires.
150         EVFILT_TIMER,
151         #[cfg(any(freebsdlike, apple_targets))]
152         /// Notifies only when explicitly requested by the user.
153         EVFILT_USER,
154         #[cfg(apple_targets)]
155         /// Virtual memory events
156         EVFILT_VM,
157         /// Notifies when a requested event happens on a specified file.
158         EVFILT_VNODE,
159         /// Takes a file descriptor as the identifier, and notifies whenever
160         /// it is possible to write to the file without blocking.
161         EVFILT_WRITE,
162     }
163     impl TryFrom<type_of_event_filter>
164 }
165 
166 #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))]
167 #[doc(hidden)]
168 pub type type_of_event_flag = u16;
169 #[cfg(target_os = "netbsd")]
170 #[doc(hidden)]
171 pub type type_of_event_flag = u32;
172 libc_bitflags! {
173     /// Event flags.  See the man page for details.
174     // There's no useful documentation we can write for the individual flags
175     // that wouldn't simply be repeating the man page.
176     pub struct EventFlag: type_of_event_flag {
177         #[allow(missing_docs)]
178         EV_ADD;
179         #[allow(missing_docs)]
180         EV_CLEAR;
181         #[allow(missing_docs)]
182         EV_DELETE;
183         #[allow(missing_docs)]
184         EV_DISABLE;
185         #[cfg(bsd)]
186         #[allow(missing_docs)]
187         EV_DISPATCH;
188         #[cfg(target_os = "freebsd")]
189         #[allow(missing_docs)]
190         EV_DROP;
191         #[allow(missing_docs)]
192         EV_ENABLE;
193         #[allow(missing_docs)]
194         EV_EOF;
195         #[allow(missing_docs)]
196         EV_ERROR;
197         #[cfg(apple_targets)]
198         #[allow(missing_docs)]
199         EV_FLAG0;
200         #[allow(missing_docs)]
201         EV_FLAG1;
202         #[cfg(target_os = "dragonfly")]
203         #[allow(missing_docs)]
204         EV_NODATA;
205         #[allow(missing_docs)]
206         EV_ONESHOT;
207         #[cfg(apple_targets)]
208         #[allow(missing_docs)]
209         EV_OOBAND;
210         #[cfg(apple_targets)]
211         #[allow(missing_docs)]
212         EV_POLL;
213         #[cfg(bsd)]
214         #[allow(missing_docs)]
215         EV_RECEIPT;
216     }
217 }
218 
219 libc_bitflags!(
220     /// Filter-specific flags.  See the man page for details.
221     // There's no useful documentation we can write for the individual flags
222     // that wouldn't simply be repeating the man page.
223     #[allow(missing_docs)]
224     pub struct FilterFlag: u32 {
225         #[cfg(apple_targets)]
226         #[allow(missing_docs)]
227         NOTE_ABSOLUTE;
228         #[allow(missing_docs)]
229         NOTE_ATTRIB;
230         #[allow(missing_docs)]
231         NOTE_CHILD;
232         #[allow(missing_docs)]
233         NOTE_DELETE;
234         #[cfg(target_os = "openbsd")]
235         #[allow(missing_docs)]
236         NOTE_EOF;
237         #[allow(missing_docs)]
238         NOTE_EXEC;
239         #[allow(missing_docs)]
240         NOTE_EXIT;
241         #[cfg(apple_targets)]
242         #[allow(missing_docs)]
243         NOTE_EXITSTATUS;
244         #[allow(missing_docs)]
245         NOTE_EXTEND;
246         #[cfg(any(apple_targets, freebsdlike))]
247         #[allow(missing_docs)]
248         NOTE_FFAND;
249         #[cfg(any(apple_targets, freebsdlike))]
250         #[allow(missing_docs)]
251         NOTE_FFCOPY;
252         #[cfg(any(apple_targets, freebsdlike))]
253         #[allow(missing_docs)]
254         NOTE_FFCTRLMASK;
255         #[cfg(any(apple_targets, freebsdlike))]
256         #[allow(missing_docs)]
257         NOTE_FFLAGSMASK;
258         #[cfg(any(apple_targets, freebsdlike))]
259         #[allow(missing_docs)]
260         NOTE_FFNOP;
261         #[cfg(any(apple_targets, freebsdlike))]
262         #[allow(missing_docs)]
263         NOTE_FFOR;
264         #[allow(missing_docs)]
265         NOTE_FORK;
266         #[allow(missing_docs)]
267         NOTE_LINK;
268         #[allow(missing_docs)]
269         NOTE_LOWAT;
270         #[cfg(target_os = "freebsd")]
271         #[allow(missing_docs)]
272         NOTE_MSECONDS;
273         #[cfg(apple_targets)]
274         #[allow(missing_docs)]
275         NOTE_NONE;
276         #[cfg(any(
277             apple_targets,
278             target_os = "freebsd"))]
279         #[allow(missing_docs)]
280         NOTE_NSECONDS;
281         #[cfg(target_os = "dragonfly")]
282         #[allow(missing_docs)]
283         NOTE_OOB;
284         #[allow(missing_docs)]
285         NOTE_PCTRLMASK;
286         #[allow(missing_docs)]
287         NOTE_PDATAMASK;
288         #[allow(missing_docs)]
289         NOTE_RENAME;
290         #[allow(missing_docs)]
291         NOTE_REVOKE;
292         #[cfg(any(
293             apple_targets,
294             target_os = "freebsd"))]
295         #[allow(missing_docs)]
296         NOTE_SECONDS;
297         #[cfg(apple_targets)]
298         #[allow(missing_docs)]
299         NOTE_SIGNAL;
300         #[allow(missing_docs)]
301         NOTE_TRACK;
302         #[allow(missing_docs)]
303         NOTE_TRACKERR;
304         #[cfg(any(apple_targets, freebsdlike))]
305         #[allow(missing_docs)]
306         NOTE_TRIGGER;
307         #[cfg(target_os = "openbsd")]
308         #[allow(missing_docs)]
309         NOTE_TRUNCATE;
310         #[cfg(any(
311             apple_targets,
312             target_os = "freebsd"))]
313         #[allow(missing_docs)]
314         NOTE_USECONDS;
315         #[cfg(apple_targets)]
316         #[allow(missing_docs)]
317         NOTE_VM_ERROR;
318         #[cfg(apple_targets)]
319         #[allow(missing_docs)]
320         NOTE_VM_PRESSURE;
321         #[cfg(apple_targets)]
322         #[allow(missing_docs)]
323         NOTE_VM_PRESSURE_SUDDEN_TERMINATE;
324         #[cfg(apple_targets)]
325         #[allow(missing_docs)]
326         NOTE_VM_PRESSURE_TERMINATE;
327         #[allow(missing_docs)]
328         NOTE_WRITE;
329     }
330 );
331 
332 #[allow(missing_docs)]
333 #[deprecated(since = "0.27.0", note = "Use KEvent::new instead")]
kqueue() -> Result<Kqueue>334 pub fn kqueue() -> Result<Kqueue> {
335     Kqueue::new()
336 }
337 
338 // KEvent can't derive Send because on some operating systems, udata is defined
339 // as a void*.  However, KEvent's public API always treats udata as an intptr_t,
340 // which is safe to Send.
341 unsafe impl Send for KEvent {}
342 
343 impl KEvent {
344     #[allow(clippy::needless_update)] // Not needless on all platforms.
345     /// Construct a new `KEvent` suitable for submission to the kernel via the
346     /// `changelist` argument of [`Kqueue::kevent`].
new( ident: uintptr_t, filter: EventFilter, flags: EventFlag, fflags: FilterFlag, data: intptr_t, udata: intptr_t, ) -> KEvent347     pub fn new(
348         ident: uintptr_t,
349         filter: EventFilter,
350         flags: EventFlag,
351         fflags: FilterFlag,
352         data: intptr_t,
353         udata: intptr_t,
354     ) -> KEvent {
355         KEvent {
356             kevent: libc::kevent {
357                 ident,
358                 filter: filter as type_of_event_filter,
359                 flags: flags.bits(),
360                 fflags: fflags.bits(),
361                 // data can be either i64 or intptr_t, depending on platform
362                 data: data as _,
363                 udata: udata as type_of_udata,
364                 ..unsafe { mem::zeroed() }
365             },
366         }
367     }
368 
369     /// Value used to identify this event.  The exact interpretation is
370     /// determined by the attached filter, but often is a raw file descriptor.
ident(&self) -> uintptr_t371     pub fn ident(&self) -> uintptr_t {
372         self.kevent.ident
373     }
374 
375     /// Identifies the kernel filter used to process this event.
376     ///
377     /// Will only return an error if the kernel reports an event via a filter
378     /// that is unknown to Nix.
filter(&self) -> Result<EventFilter>379     pub fn filter(&self) -> Result<EventFilter> {
380         self.kevent.filter.try_into()
381     }
382 
383     /// Flags control what the kernel will do when this event is added with
384     /// [`Kqueue::kevent`].
flags(&self) -> EventFlag385     pub fn flags(&self) -> EventFlag {
386         EventFlag::from_bits(self.kevent.flags).unwrap()
387     }
388 
389     /// Filter-specific flags.
fflags(&self) -> FilterFlag390     pub fn fflags(&self) -> FilterFlag {
391         FilterFlag::from_bits(self.kevent.fflags).unwrap()
392     }
393 
394     /// Filter-specific data value.
data(&self) -> intptr_t395     pub fn data(&self) -> intptr_t {
396         self.kevent.data as intptr_t
397     }
398 
399     /// Opaque user-defined value passed through the kernel unchanged.
udata(&self) -> intptr_t400     pub fn udata(&self) -> intptr_t {
401         self.kevent.udata as intptr_t
402     }
403 }
404 
405 #[allow(missing_docs)]
406 #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")]
kevent( kq: &Kqueue, changelist: &[KEvent], eventlist: &mut [KEvent], timeout_ms: usize, ) -> Result<usize>407 pub fn kevent(
408     kq: &Kqueue,
409     changelist: &[KEvent],
410     eventlist: &mut [KEvent],
411     timeout_ms: usize,
412 ) -> Result<usize> {
413     // Convert ms to timespec
414     let timeout = timespec {
415         tv_sec: (timeout_ms / 1000) as time_t,
416         tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long,
417     };
418 
419     kq.kevent(changelist, eventlist, Some(timeout))
420 }
421 
422 #[cfg(any(apple_targets, freebsdlike, target_os = "openbsd"))]
423 type type_of_nchanges = c_int;
424 #[cfg(target_os = "netbsd")]
425 type type_of_nchanges = size_t;
426 
427 #[allow(missing_docs)]
428 #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")]
kevent_ts( kq: &Kqueue, changelist: &[KEvent], eventlist: &mut [KEvent], timeout_opt: Option<timespec>, ) -> Result<usize>429 pub fn kevent_ts(
430     kq: &Kqueue,
431     changelist: &[KEvent],
432     eventlist: &mut [KEvent],
433     timeout_opt: Option<timespec>,
434 ) -> Result<usize> {
435     kq.kevent(changelist, eventlist, timeout_opt)
436 }
437 
438 /// Modify an existing [`KEvent`].
439 // Probably should deprecate.  Would anybody ever use it over `KEvent::new`?
440 #[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")]
441 #[inline]
ev_set( ev: &mut KEvent, ident: usize, filter: EventFilter, flags: EventFlag, fflags: FilterFlag, udata: intptr_t, )442 pub fn ev_set(
443     ev: &mut KEvent,
444     ident: usize,
445     filter: EventFilter,
446     flags: EventFlag,
447     fflags: FilterFlag,
448     udata: intptr_t,
449 ) {
450     ev.kevent.ident = ident as uintptr_t;
451     ev.kevent.filter = filter as type_of_event_filter;
452     ev.kevent.flags = flags.bits();
453     ev.kevent.fflags = fflags.bits();
454     ev.kevent.data = 0;
455     ev.kevent.udata = udata as type_of_udata;
456 }
457