inotify/events.rs
1use std::{
2 convert::{TryFrom, TryInto},
3 error::Error,
4 ffi::{OsStr, OsString},
5 fmt::Display,
6 mem,
7 os::unix::ffi::OsStrExt,
8 sync::Weak,
9};
10
11use inotify_sys as ffi;
12
13use crate::fd_guard::FdGuard;
14use crate::watches::WatchDescriptor;
15
16/// Iterator over inotify events
17///
18/// Allows for iteration over the events returned by
19/// [`Inotify::read_events_blocking`] or [`Inotify::read_events`].
20///
21/// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking
22/// [`Inotify::read_events`]: crate::Inotify::read_events
23#[derive(Debug)]
24pub struct Events<'a> {
25 fd: Weak<FdGuard>,
26 buffer: &'a [u8],
27 num_bytes: usize,
28 pos: usize,
29}
30
31impl<'a> Events<'a> {
32 pub(crate) fn new(fd: Weak<FdGuard>, buffer: &'a [u8], num_bytes: usize) -> Self {
33 Events {
34 fd,
35 buffer,
36 num_bytes,
37 pos: 0,
38 }
39 }
40}
41
42impl<'a> Iterator for Events<'a> {
43 type Item = Event<&'a OsStr>;
44
45 fn next(&mut self) -> Option<Self::Item> {
46 if self.pos < self.num_bytes {
47 let (step, event) = Event::from_buffer(self.fd.clone(), &self.buffer[self.pos..]);
48 self.pos += step;
49
50 Some(event)
51 } else {
52 None
53 }
54 }
55}
56
57/// An inotify event
58///
59/// A file system event that describes a change that the user previously
60/// registered interest in. To watch for events, call [`Watches::add`]. To
61/// retrieve events, call [`Inotify::read_events_blocking`] or
62/// [`Inotify::read_events`].
63///
64/// [`Watches::add`]: crate::Watches::add
65/// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking
66/// [`Inotify::read_events`]: crate::Inotify::read_events
67#[derive(Clone, Debug)]
68pub struct Event<S> {
69 /// Identifies the watch this event originates from
70 ///
71 /// This [`WatchDescriptor`] is equal to the one that [`Watches::add`]
72 /// returned when interest for this event was registered. The
73 /// [`WatchDescriptor`] can be used to remove the watch using
74 /// [`Watches::remove`], thereby preventing future events of this type
75 /// from being created.
76 ///
77 /// [`Watches::add`]: crate::Watches::add
78 /// [`Watches::remove`]: crate::Watches::remove
79 pub wd: WatchDescriptor,
80
81 /// Indicates what kind of event this is
82 pub mask: EventMask,
83
84 /// Connects related events to each other
85 ///
86 /// When a file is renamed, this results two events: [`MOVED_FROM`] and
87 /// [`MOVED_TO`]. The `cookie` field will be the same for both of them,
88 /// thereby making is possible to connect the event pair.
89 ///
90 /// [`MOVED_FROM`]: EventMask::MOVED_FROM
91 /// [`MOVED_TO`]: EventMask::MOVED_TO
92 pub cookie: u32,
93
94 /// The name of the file the event originates from
95 ///
96 /// This field is set only if the subject of the event is a file or directory in a
97 /// watched directory. If the event concerns a file or directory that is
98 /// watched directly, `name` will be `None`.
99 pub name: Option<S>,
100}
101
102impl<'a> Event<&'a OsStr> {
103 fn new(fd: Weak<FdGuard>, event: &ffi::inotify_event, name: &'a OsStr) -> Self {
104 let mask = EventMask::from_bits(event.mask)
105 .expect("Failed to convert event mask. This indicates a bug.");
106
107 let wd = crate::WatchDescriptor { id: event.wd, fd };
108
109 let name = if name.is_empty() { None } else { Some(name) };
110
111 Event {
112 wd,
113 mask,
114 cookie: event.cookie,
115 name,
116 }
117 }
118
119 /// Create an `Event` from a buffer
120 ///
121 /// Assumes that a full `inotify_event` plus its name is located at the
122 /// beginning of `buffer`.
123 ///
124 /// Returns the number of bytes used from the buffer, and the event.
125 ///
126 /// # Panics
127 ///
128 /// Panics if the buffer does not contain a full event, including its name.
129 pub(crate) fn from_buffer(fd: Weak<FdGuard>, buffer: &'a [u8]) -> (usize, Self) {
130 let event_size = mem::size_of::<ffi::inotify_event>();
131
132 // Make sure that the buffer is big enough to contain an event, without
133 // the name. Otherwise we can't safely convert it to an `inotify_event`.
134 assert!(buffer.len() >= event_size);
135
136 let ffi_event_ptr = buffer.as_ptr() as *const ffi::inotify_event;
137
138 // We have a pointer to an `inotify_event`, pointing to the beginning of
139 // `buffer`. Since we know, as per the assertion above, that there are
140 // enough bytes in the buffer for at least one event, we can safely
141 // read that `inotify_event`.
142 // We call `read_unaligned()` since the byte buffer has alignment 1
143 // and `inotify_event` has a higher alignment, so `*` cannot be used to dereference
144 // the unaligned pointer (undefined behavior).
145 let ffi_event = unsafe { ffi_event_ptr.read_unaligned() };
146
147 // The name's length is given by `event.len`. There should always be
148 // enough bytes left in the buffer to fit the name. Let's make sure that
149 // is the case.
150 let bytes_left_in_buffer = buffer.len() - event_size;
151 assert!(bytes_left_in_buffer >= ffi_event.len as usize);
152
153 // Directly after the event struct should be a name, if there's one
154 // associated with the event. Let's make a new slice that starts with
155 // that name. If there's no name, this slice might have a length of `0`.
156 let bytes_consumed = event_size + ffi_event.len as usize;
157 let name = &buffer[event_size..bytes_consumed];
158
159 // Remove trailing '\0' bytes
160 //
161 // The events in the buffer are aligned, and `name` is filled up
162 // with '\0' up to the alignment boundary. Here we remove those
163 // additional bytes.
164 //
165 // The `unwrap` here is safe, because `splitn` always returns at
166 // least one result, even if the original slice contains no '\0'.
167 let name = name.splitn(2, |b| b == &0u8).next().unwrap();
168
169 let event = Event::new(fd, &ffi_event, OsStr::from_bytes(name));
170
171 (bytes_consumed, event)
172 }
173
174 /// Returns an owned copy of the event.
175 #[deprecated = "use `to_owned()` instead; methods named `into_owned()` usually take self by value"]
176 #[allow(clippy::wrong_self_convention)]
177 pub fn into_owned(&self) -> EventOwned {
178 self.to_owned()
179 }
180
181 /// Returns an owned copy of the event.
182 #[must_use = "cloning is often expensive and is not expected to have side effects"]
183 pub fn to_owned(&self) -> EventOwned {
184 Event {
185 wd: self.wd.clone(),
186 mask: self.mask,
187 cookie: self.cookie,
188 name: self.name.map(OsStr::to_os_string),
189 }
190 }
191}
192
193/// An owned version of `Event`
194pub type EventOwned = Event<OsString>;
195
196bitflags! {
197 /// Indicates the type of an event
198 ///
199 /// This struct can be retrieved from an [`Event`] via its `mask` field.
200 /// You can determine the [`Event`]'s type by comparing the `EventMask` to
201 /// its associated constants.
202 ///
203 /// Please refer to the documentation of [`Event`] for a usage example.
204 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
205 pub struct EventMask: u32 {
206 /// File was accessed
207 ///
208 /// When watching a directory, this event is only triggered for objects
209 /// inside the directory, not the directory itself.
210 ///
211 /// See [`inotify_sys::IN_ACCESS`].
212 const ACCESS = ffi::IN_ACCESS;
213
214 /// Metadata (permissions, timestamps, ...) changed
215 ///
216 /// When watching a directory, this event can be triggered for the
217 /// directory itself, as well as objects inside the directory.
218 ///
219 /// See [`inotify_sys::IN_ATTRIB`].
220 const ATTRIB = ffi::IN_ATTRIB;
221
222 /// File opened for writing was closed
223 ///
224 /// When watching a directory, this event is only triggered for objects
225 /// inside the directory, not the directory itself.
226 ///
227 /// See [`inotify_sys::IN_CLOSE_WRITE`].
228 const CLOSE_WRITE = ffi::IN_CLOSE_WRITE;
229
230 /// File or directory not opened for writing was closed
231 ///
232 /// When watching a directory, this event can be triggered for the
233 /// directory itself, as well as objects inside the directory.
234 ///
235 /// See [`inotify_sys::IN_CLOSE_NOWRITE`].
236 const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE;
237
238 /// File/directory created in watched directory
239 ///
240 /// When watching a directory, this event is only triggered for objects
241 /// inside the directory, not the directory itself.
242 ///
243 /// See [`inotify_sys::IN_CREATE`].
244 const CREATE = ffi::IN_CREATE;
245
246 /// File/directory deleted from watched directory
247 ///
248 /// When watching a directory, this event is only triggered for objects
249 /// inside the directory, not the directory itself.
250 const DELETE = ffi::IN_DELETE;
251
252 /// Watched file/directory was deleted
253 ///
254 /// See [`inotify_sys::IN_DELETE_SELF`].
255 const DELETE_SELF = ffi::IN_DELETE_SELF;
256
257 /// File was modified
258 ///
259 /// When watching a directory, this event is only triggered for objects
260 /// inside the directory, not the directory itself.
261 ///
262 /// See [`inotify_sys::IN_MODIFY`].
263 const MODIFY = ffi::IN_MODIFY;
264
265 /// Watched file/directory was moved
266 ///
267 /// See [`inotify_sys::IN_MOVE_SELF`].
268 const MOVE_SELF = ffi::IN_MOVE_SELF;
269
270 /// File was renamed/moved; watched directory contained old name
271 ///
272 /// When watching a directory, this event is only triggered for objects
273 /// inside the directory, not the directory itself.
274 ///
275 /// See [`inotify_sys::IN_MOVED_FROM`].
276 const MOVED_FROM = ffi::IN_MOVED_FROM;
277
278 /// File was renamed/moved; watched directory contains new name
279 ///
280 /// When watching a directory, this event is only triggered for objects
281 /// inside the directory, not the directory itself.
282 ///
283 /// See [`inotify_sys::IN_MOVED_TO`].
284 const MOVED_TO = ffi::IN_MOVED_TO;
285
286 /// File or directory was opened
287 ///
288 /// When watching a directory, this event can be triggered for the
289 /// directory itself, as well as objects inside the directory.
290 ///
291 /// See [`inotify_sys::IN_OPEN`].
292 const OPEN = ffi::IN_OPEN;
293
294 /// Watch was removed
295 ///
296 /// This event will be generated, if the watch was removed explicitly
297 /// (via [`Watches::remove`]), or automatically (because the file was
298 /// deleted or the file system was unmounted).
299 ///
300 /// See [`inotify_sys::IN_IGNORED`].
301 ///
302 /// [`Watches::remove`]: crate::Watches::remove
303 const IGNORED = ffi::IN_IGNORED;
304
305 /// Event related to a directory
306 ///
307 /// The subject of the event is a directory.
308 ///
309 /// See [`inotify_sys::IN_ISDIR`].
310 const ISDIR = ffi::IN_ISDIR;
311
312 /// Event queue overflowed
313 ///
314 /// The event queue has overflowed and events have presumably been lost.
315 ///
316 /// See [`inotify_sys::IN_Q_OVERFLOW`].
317 const Q_OVERFLOW = ffi::IN_Q_OVERFLOW;
318
319 /// File system containing watched object was unmounted.
320 /// File system was unmounted
321 ///
322 /// The file system that contained the watched object has been
323 /// unmounted. An event with [`EventMask::IGNORED`] will subsequently be
324 /// generated for the same watch descriptor.
325 ///
326 /// See [`inotify_sys::IN_UNMOUNT`].
327 const UNMOUNT = ffi::IN_UNMOUNT;
328 }
329}
330
331impl EventMask {
332 /// Parse this event mask into a ParsedEventMask
333 pub fn parse(self) -> Result<ParsedEventMask, EventMaskParseError> {
334 self.try_into()
335 }
336
337 /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility
338 ///
339 /// # Safety
340 ///
341 /// This function is not actually unsafe. It is just a wrapper around the
342 /// safe [`Self::from_bits_retain`].
343 #[deprecated = "Use the safe `from_bits_retain` method instead"]
344 pub unsafe fn from_bits_unchecked(bits: u32) -> Self {
345 Self::from_bits_retain(bits)
346 }
347}
348
349/// A struct that provides structured access to event masks
350/// returned from reading an event from an inotify fd
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
352pub struct ParsedEventMask {
353 /// The kind of event that occurred
354 ///
355 /// Inotify events that come from the kernel have
356 /// exactly 0 or 1 of the flags associated with the
357 /// event type set.
358 pub kind: Option<EventKind>,
359 /// The auxiliary flags about the event
360 pub auxiliary_flags: EventAuxiliaryFlags,
361}
362
363impl ParsedEventMask {
364 /// Construct a `ParsedEventMask` from its component parts
365 pub fn from_parts(kind: Option<EventKind>, auxiliary_flags: EventAuxiliaryFlags) -> Self {
366 ParsedEventMask {
367 kind,
368 auxiliary_flags,
369 }
370 }
371
372 /// Parse a raw event mask
373 pub fn from_raw_event_mask(mask: EventMask) -> Result<Self, EventMaskParseError> {
374 if mask.contains(EventMask::Q_OVERFLOW) {
375 return Err(EventMaskParseError::QueueOverflow);
376 }
377
378 let kind = Option::<EventKind>::try_from(mask)?;
379 let auxiliary_flags = EventAuxiliaryFlags::from(mask);
380
381 Ok(ParsedEventMask::from_parts(kind, auxiliary_flags))
382 }
383}
384
385impl TryFrom<EventMask> for ParsedEventMask {
386 type Error = EventMaskParseError;
387
388 fn try_from(value: EventMask) -> Result<Self, Self::Error> {
389 Self::from_raw_event_mask(value)
390 }
391}
392
393/// Represents the type of inotify event
394#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
395pub enum EventKind {
396 /// File was accessed (e.g., [`read(2)`], [`execve(2)`])
397 ///
398 /// [`read(2)`]: https://man7.org/linux/man-pages/man2/read.2.html
399 /// [`execve(2)`]: https://man7.org/linux/man-pages/man2/execve.2.html
400 Access,
401
402 /// Metadata changed—for example, permissions (e.g.,
403 /// [`chmod(2)`]), timestamps (e.g., [`utimensat(2)`]), extended
404 /// attributes ([`setxattr(2)`]), link count (since Linux
405 /// 2.6.25; e.g., for the target of [`link(2)`] and for
406 /// [`unlink(2)`]), and user/group ID (e.g., [`chown(2)`])
407 ///
408 /// [`chmod(2)`]: https://man7.org/linux/man-pages/man2/chmod.2.html
409 /// [`utimensat(2)`]: https://man7.org/linux/man-pages/man2/utimensat.2.html
410 /// [`setxattr(2)`]: https://man7.org/linux/man-pages/man2/setxattr.2.html
411 /// [`link(2)`]: https://man7.org/linux/man-pages/man2/link.2.html
412 /// [`unlink(2)`]: https://man7.org/linux/man-pages/man2/unlink.2.html
413 /// [`chown(2)`]: https://man7.org/linux/man-pages/man2/chown.2.html
414 Attrib,
415
416 /// File opened for writing was closed
417 CloseWrite,
418
419 /// File or directory not opened for writing was closed
420 CloseNowrite,
421
422 /// File/directory created in watched directory (e.g.,
423 /// [`open(2)`] **O_CREAT**, [`mkdir(2)`], [`link(2)`], [`symlink(2)`], [`bind(2)`]
424 /// on a UNIX domain socket)
425 ///
426 /// [`open(2)`]: https://man7.org/linux/man-pages/man2/open.2.html
427 /// [`mkdir(2)`]: https://man7.org/linux/man-pages/man2/mkdir.2.html
428 /// [`link(2)`]: https://man7.org/linux/man-pages/man2/link.2.html
429 /// [`symlink(2)`]: https://man7.org/linux/man-pages/man2/symlink.2.html
430 /// [`bind(2)`]: https://man7.org/linux/man-pages/man2/bind.2.html
431 Create,
432
433 /// File/directory deleted from watched directory
434 Delete,
435
436 /// Watched file/directory was itself deleted. (This event
437 /// also occurs if an object is moved to another
438 /// filesystem, since [`mv(1)`] in effect copies the file to
439 /// the other filesystem and then deletes it from the
440 /// original filesystem.)
441 ///
442 /// [`mv(1)`]: https://man7.org/linux/man-pages/man1/mv.1.html
443 DeleteSelf,
444
445 /// File was modified (e.g., [`write(2)`], [`truncate(2)`])
446 ///
447 /// [`write(2)`]: https://man7.org/linux/man-pages/man2/write.2.html
448 /// [`truncate(2)`]: https://man7.org/linux/man-pages/man2/truncate.2.html
449 Modify,
450
451 /// Watched file/directory was itself moved
452 MoveSelf,
453
454 /// Generated for the directory containing the old filename when a file is renamed
455 MovedFrom,
456
457 /// Generated for the directory containing the new filename when a file is renamed
458 MovedTo,
459
460 /// File or directory was opened
461 Open,
462}
463
464impl EventKind {
465 const BITFLAG_ENUM_MAP: &[(EventMask, EventKind)] = &[
466 (EventMask::ACCESS, EventKind::Access),
467 (EventMask::ATTRIB, EventKind::Attrib),
468 (EventMask::CLOSE_WRITE, EventKind::CloseWrite),
469 (EventMask::CLOSE_NOWRITE, EventKind::CloseNowrite),
470 (EventMask::CREATE, EventKind::Create),
471 (EventMask::DELETE, EventKind::Delete),
472 (EventMask::DELETE_SELF, EventKind::DeleteSelf),
473 (EventMask::MODIFY, EventKind::Modify),
474 (EventMask::MOVE_SELF, EventKind::MoveSelf),
475 (EventMask::MOVED_FROM, EventKind::MovedFrom),
476 (EventMask::MOVED_TO, EventKind::MovedTo),
477 (EventMask::OPEN, EventKind::Open),
478 ];
479
480 /// Parse the auxiliary flags from a raw event mask
481 pub fn from_raw_event_mask(mask: EventMask) -> Result<Option<Self>, EventMaskParseError> {
482 let mut kinds = Self::BITFLAG_ENUM_MAP.iter().filter_map(|bf_map| {
483 if mask.contains(bf_map.0) {
484 Some(bf_map.1)
485 } else {
486 None
487 }
488 });
489
490 // Optionally take the first matching bitflag
491 let kind = kinds.next();
492
493 if kinds.next().is_some() {
494 // The mask is invalid.
495 //
496 // More than one of the bitflags are set
497 return Err(EventMaskParseError::TooManyBitsSet(mask));
498 }
499
500 Ok(kind)
501 }
502}
503
504impl TryFrom<EventMask> for Option<EventKind> {
505 type Error = EventMaskParseError;
506
507 fn try_from(value: EventMask) -> Result<Self, Self::Error> {
508 EventKind::from_raw_event_mask(value)
509 }
510}
511
512/// Auxiliary flags for inotify events
513///
514/// The non-mutually-exclusive bitflags that may be set
515/// in an event read from an inotify fd. 0 or more of these
516/// bitflags may be set.
517#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
518pub struct EventAuxiliaryFlags {
519 /// Watch was removed when explicitly removed via [`inotify_rm_watch(2)`]
520 /// or automatically (because the file was deleted or the filesystem was unmounted)
521 ///
522 /// [`inotify_rm_watch(2)`]: https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html
523 pub ignored: bool,
524
525 /// Event subject is a directory rather than a regular file
526 pub isdir: bool,
527
528 /// File system containing watched object was unmounted
529 ///
530 /// An event with **IN_IGNORED** will subsequently be generated for the same watch descriptor.
531 pub unmount: bool,
532}
533
534impl EventAuxiliaryFlags {
535 /// Parse the auxiliary flags from a raw event mask
536 pub fn from_raw_event_mask(mask: EventMask) -> Self {
537 EventAuxiliaryFlags {
538 ignored: mask.contains(EventMask::IGNORED),
539 isdir: mask.contains(EventMask::ISDIR),
540 unmount: mask.contains(EventMask::UNMOUNT),
541 }
542 }
543}
544
545impl From<EventMask> for EventAuxiliaryFlags {
546 fn from(value: EventMask) -> Self {
547 Self::from_raw_event_mask(value)
548 }
549}
550
551/// An error that occured from parsing an raw event mask
552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
553pub enum EventMaskParseError {
554 /// More than one bit repesenting the event type was set
555 TooManyBitsSet(EventMask),
556 /// The event is a signal that the kernels event queue overflowed
557 QueueOverflow,
558}
559
560impl Display for EventMaskParseError {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 match self {
563 Self::TooManyBitsSet(mask) => {
564 writeln!(
565 f,
566 "Error parsing event mask: too many event type bits set | {mask:?}"
567 )
568 }
569 Self::QueueOverflow => writeln!(f, "Error: the kernel's event queue overflowed"),
570 }
571 }
572}
573
574impl Error for EventMaskParseError {}
575
576#[cfg(test)]
577mod tests {
578 use std::{io::prelude::*, mem, slice, sync};
579
580 use inotify_sys as ffi;
581
582 use crate::{EventMask, EventMaskParseError};
583
584 use super::{Event, EventAuxiliaryFlags, EventKind, ParsedEventMask};
585
586 #[test]
587 fn from_buffer_should_not_mistake_next_event_for_name_of_previous_event() {
588 let mut buffer = [0u8; 1024];
589
590 // First, put a normal event into the buffer
591 let event = ffi::inotify_event {
592 wd: 0,
593 mask: 0,
594 cookie: 0,
595 len: 0, // no name following after event
596 };
597 let event = unsafe {
598 slice::from_raw_parts(&event as *const _ as *const u8, mem::size_of_val(&event))
599 };
600 (&mut buffer[..])
601 .write_all(event)
602 .expect("Failed to write into buffer");
603
604 // After that event, simulate an event that starts with a non-zero byte.
605 buffer[mem::size_of_val(event)] = 1;
606
607 // Now create the event and verify that the name is actually `None`, as
608 // dictated by the value `len` above.
609 let (_, event) = Event::from_buffer(sync::Weak::new(), &buffer);
610 assert_eq!(event.name, None);
611 }
612
613 #[test]
614 fn parse_event_kinds() {
615 // Parse each event kind
616 for bf_map in EventKind::BITFLAG_ENUM_MAP {
617 assert_eq!(
618 Ok(ParsedEventMask {
619 kind: Some(bf_map.1),
620 auxiliary_flags: Default::default()
621 }),
622 bf_map.0.parse()
623 );
624 }
625
626 // Parse an event with no event kind
627 assert_eq!(
628 Ok(ParsedEventMask {
629 kind: None,
630 auxiliary_flags: Default::default()
631 }),
632 EventMask::from_bits_retain(0).parse()
633 )
634 }
635
636 #[test]
637 fn parse_event_auxiliary_flags() {
638 assert_eq!(
639 Ok(ParsedEventMask {
640 kind: None,
641 auxiliary_flags: EventAuxiliaryFlags {
642 ignored: true,
643 isdir: false,
644 unmount: false
645 }
646 }),
647 EventMask::IGNORED.parse()
648 );
649
650 assert_eq!(
651 Ok(ParsedEventMask {
652 kind: None,
653 auxiliary_flags: EventAuxiliaryFlags {
654 ignored: false,
655 isdir: true,
656 unmount: false
657 }
658 }),
659 EventMask::ISDIR.parse()
660 );
661
662 assert_eq!(
663 Ok(ParsedEventMask {
664 kind: None,
665 auxiliary_flags: EventAuxiliaryFlags {
666 ignored: false,
667 isdir: false,
668 unmount: true
669 }
670 }),
671 EventMask::UNMOUNT.parse()
672 );
673 }
674
675 #[test]
676 fn parse_event_errors() {
677 assert_eq!(
678 Err(EventMaskParseError::QueueOverflow),
679 EventMask::Q_OVERFLOW.parse()
680 );
681
682 let mask = EventMask::ATTRIB | EventMask::ACCESS;
683 assert_eq!(Err(EventMaskParseError::TooManyBitsSet(mask)), mask.parse());
684 }
685}