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}