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