From aa235f4ecdd72f8510e45bb0264010dfbafaa982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Thu, 16 Nov 2023 15:02:20 +0100 Subject: [PATCH 01/11] Add fanotify API wrappers --- Cargo.toml | 1 + changelog/2194.added.md | 1 + src/lib.rs | 2 + src/sys/fanotify.rs | 365 ++++++++++++++++++++++++++++++++++++++ src/sys/mod.rs | 6 + test/sys/mod.rs | 2 + test/sys/test_fanotify.rs | 150 ++++++++++++++++ 7 files changed, 527 insertions(+) create mode 100644 changelog/2194.added.md create mode 100644 src/sys/fanotify.rs create mode 100644 test/sys/test_fanotify.rs diff --git a/Cargo.toml b/Cargo.toml index dbcb99e277..aa365449e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ aio = ["pin-utils"] dir = ["fs"] env = [] event = [] +fanotify = [] feature = [] fs = [] hostname = [] diff --git a/changelog/2194.added.md b/changelog/2194.added.md new file mode 100644 index 0000000000..18e39b1ccd --- /dev/null +++ b/changelog/2194.added.md @@ -0,0 +1 @@ +Added new fanotify API: wrappers for `fanotify_init` and `fanotify_mark` diff --git a/src/lib.rs b/src/lib.rs index 467b839d56..62220a822f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ //! * `dir` - Stuff relating to directory iteration //! * `env` - Manipulate environment variables //! * `event` - Event-driven APIs, like `kqueue` and `epoll` +//! * `fanotify` - Linux's `fanotify` filesystem events monitoring API //! * `feature` - Query characteristics of the OS at runtime //! * `fs` - File system functionality //! * `hostname` - Get and set the system's hostname @@ -53,6 +54,7 @@ feature = "dir", feature = "env", feature = "event", + feature = "fanotify", feature = "feature", feature = "fs", feature = "hostname", diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs new file mode 100644 index 0000000000..6ec03f0c2d --- /dev/null +++ b/src/sys/fanotify.rs @@ -0,0 +1,365 @@ +//! Monitoring API for filesystem events. +//! +//! Fanotify is a Linux-only API to monitor filesystems events. +//! +//! Additional capabilities compared to the `inotify` API include the ability to +//! monitor all of the objects in a mounted filesystem, the ability to make +//! access permission decisions, and the possibility to read or modify files +//! before access by other applications. +//! +//! For more documentation, please read +//! [fanotify(7)](https://man7.org/linux/man-pages/man7/fanotify.7.html). + +use crate::{NixPath, Result}; +use crate::errno::Errno; +use crate::unistd::{read, write}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; +use std::mem::{MaybeUninit, size_of}; +use std::ptr; + +libc_bitflags! { + /// Mask for defining which events shall be listened with + /// [`fanotify_mark`](fn.fanotify_mark.html) and for querying notifications. + pub struct MaskFlags: u64 { + /// File was accessed. + FAN_ACCESS; + /// File was modified. + FAN_MODIFY; + /// Metadata changed. Since Linux 5.1. + FAN_ATTRIB; + /// Writtable file closed. + FAN_CLOSE_WRITE; + /// Unwrittable file closed. + FAN_CLOSE_NOWRITE; + /// File was opened. + FAN_OPEN; + /// File was moved from X. Since Linux 5.1. + FAN_MOVED_FROM; + /// File was moved to Y. Since Linux 5.1. + FAN_MOVED_TO; + /// Subfile was created. Since Linux 5.1. + FAN_CREATE; + /// Subfile was deleted. Since Linux 5.1. + FAN_DELETE; + /// Self was deleted. Since Linux 5.1. + FAN_DELETE_SELF; + /// Self was moved. Since Linux 5.1. + FAN_MOVE_SELF; + /// File was opened for exec. Since Linux 5.0. + FAN_OPEN_EXEC; + + /// Event queued overflowed. + FAN_Q_OVERFLOW; + /// Filesystem error. Since Linux 5.16. + FAN_FS_ERROR; + + /// File open in perm check. + FAN_OPEN_PERM; + /// File accessed in perm check. + FAN_ACCESS_PERM; + /// File open/exec in perm check. Since Linux 5.0. + FAN_OPEN_EXEC_PERM; + + /// Interested in child events. + FAN_EVENT_ON_CHILD; + + /// File was renamed. Since Linux 5.17. + FAN_RENAME; + + /// Event occured against dir. + FAN_ONDIR; + + /// Combination of `FAN_CLOSE_WRITE` and `FAN_CLOSE_NOWRITE`. + FAN_CLOSE; + /// Combination of `FAN_MOVED_FROM` and `FAN_MOVED_TO`. + FAN_MOVE; + } +} + +libc_bitflags! { + /// Configuration options for [`fanotify_init`](fn.fanotify_init.html). + pub struct InitFlags: libc::c_uint { + /// Close-on-exec flag set on the file descriptor. + FAN_CLOEXEC; + /// Nonblocking flag set on the file descriptor. + FAN_NONBLOCK; + + /// Receipt of events notifications. + FAN_CLASS_NOTIF; + /// Receipt of events for permission decisions, after they contain final + /// data. + FAN_CLASS_CONTENT; + /// Receipt of events for permission decisions, before they contain + /// final data. + FAN_CLASS_PRE_CONTENT; + + /// Remove the limit of 16384 events for the event queue. + FAN_UNLIMITED_QUEUE; + /// Remove the limit of 8192 marks. + FAN_UNLIMITED_MARKS; + } +} + +libc_bitflags! { + /// File status flags for fanotify events file descriptors. + pub struct OFlags: libc::c_uint { + /// Read only access. + O_RDONLY as libc::c_uint; + /// Write only access. + O_WRONLY as libc::c_uint; + /// Read and write access. + O_RDWR as libc::c_uint; + /// Support for files exceeded 2 GB. + O_LARGEFILE as libc::c_uint; + /// Close-on-exec flag for the file descriptor. Since Linux 3.18. + O_CLOEXEC as libc::c_uint; + /// Append mode for the file descriptor. + O_APPEND as libc::c_uint; + /// Synchronized I/O data integrity completion. + O_DSYNC as libc::c_uint; + /// No file last access time update. + O_NOATIME as libc::c_uint; + /// Nonblocking mode for the file descriptor. + O_NONBLOCK as libc::c_uint; + /// Synchronized I/O file integrity completion. + O_SYNC as libc::c_uint; + } +} + +libc_bitflags! { + /// Configuration options for [`fanotify_mark`](fn.fanotify_mark.html). + pub struct MarkFlags: libc::c_uint { + /// Add the events to the marks. + FAN_MARK_ADD; + /// Remove the events to the marks. + FAN_MARK_REMOVE; + /// Don't follow symlinks, mark them. + FAN_MARK_DONT_FOLLOW; + /// Raise an error if filesystem to be marked is not a directory. + FAN_MARK_ONLYDIR; + /// Events added to or removed from the marks. + FAN_MARK_IGNORED_MASK; + /// Ignore mask shall survive modify events. + FAN_MARK_IGNORED_SURV_MODIFY; + /// Remove all marks. + FAN_MARK_FLUSH; + /// Do not pin inode object in the inode cache. Since Linux 5.19. + FAN_MARK_EVICTABLE; + /// Events added to or removed from the marks. Since Linux 6.0. + FAN_MARK_IGNORE; + + /// Default flag. + FAN_MARK_INODE; + /// Mark the mount specified by pathname. + FAN_MARK_MOUNT; + /// Mark the filesystem specified by pathname. Since Linux 4.20. + FAN_MARK_FILESYSTEM; + + /// Combination of `FAN_MARK_IGNORE` and `FAN_MARK_IGNORED_SURV_MODIFY`. + FAN_MARK_IGNORE_SURV; + } +} + +/// Compile version number of fanotify API. +pub const FANOTIFY_METADATA_VERSION: u8 = libc::FANOTIFY_METADATA_VERSION; + +#[derive(Debug)] +/// Abstract over `libc::fanotify_event_metadata`, which represents an event +/// received via `Fanotify::read_events`. +pub struct FanotifyEvent { + /// Version number for the structure. It must be compared to + /// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime + /// version does match. It can be done with the + /// `FanotifyEvent::has_compile_version` method. + pub version: u8, + /// Mask flags of the events. + pub mask: MaskFlags, + /// The file descriptor of the event. If the value is `None` when reading + /// from the fanotify group, this event is to notify that a group queue + /// overflow occured. + pub fd: Option, + /// PID of the process that caused the event. TID in case flag + /// `FAN_REPORT_TID` was set at group initialization. + pub pid: i32, +} + +impl FanotifyEvent { + /// Checks that compile fanotify API version is equal to the version of the + /// event. + pub fn has_compile_version(&self) -> bool { + self.version == FANOTIFY_METADATA_VERSION + } +} + +#[derive(Debug)] +/// Abstraction over the structure to be sent to allow or deny a given event. +pub struct FanotifyResponse<'e> { + /// A borrow of the file descriptor from the structure `FanotifyEvent`. + pub fd: BorrowedFd<'e>, + /// Indication whether or not the permission is to be granted. + pub response: Response, +} + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +/// Response to be wrapped in `FanotifyResponse` and sent to the `Fanotify` +/// group to allow or deny an event. +pub enum Response { + /// Allow the event. + Allow, + /// Deny the event. + Deny, +} + +/// A fanotify group. This is also a file descriptor that can feed to other +/// interfaces consuming file descriptors. +#[derive(Debug)] +pub struct Fanotify { + fd: OwnedFd, +} + +impl Fanotify { + /// Initialize a new fanotify group. + /// + /// Returns a Result containing a Fanotify instance. + /// + /// For more information, see [fanotify_init(2)](https://man7.org/linux/man-pages/man7/fanotify_init.2.html). + pub fn init(flags: InitFlags, event_f_flags: OFlags) -> Result { + let res = Errno::result(unsafe { + libc::fanotify_init(flags.bits(), event_f_flags.bits()) + }); + res.map(|fd| Fanotify { fd: unsafe { OwnedFd::from_raw_fd(fd) }}) + } + + /// Add, remove, or modify an fanotify mark on a filesystem object. + /// If `dirfd` is `None`, `AT_FDCWD` is used. + /// + /// Returns a Result containing either `()` on success or errno otherwise. + /// + /// For more information, see [fanotify_mark(2)](https://man7.org/linux/man-pages/man7/fanotify_mark.2.html). + pub fn mark( + &self, + flags: MarkFlags, + mask: MaskFlags, + dirfd: Option, + path: Option<&P>, + ) -> Result<()> { + fn with_opt_nix_path(p: Option<&P>, f: F) -> Result + where + P: ?Sized + NixPath, + F: FnOnce(*const libc::c_char) -> T, + { + match p { + Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), + None => Ok(f(std::ptr::null())), + } + } + + let res = with_opt_nix_path(path, |p| unsafe { + libc::fanotify_mark( + self.fd.as_raw_fd(), + flags.bits(), + mask.bits(), + dirfd.unwrap_or(libc::AT_FDCWD), + p, + ) + })?; + + Errno::result(res).map(|_| ()) + } + + /// Read incoming events from the fanotify group. + /// + /// Returns a Result containing either a `Vec` of events on success or errno + /// otherwise. + /// + /// # Errors + /// + /// Possible errors can be those that are explicitly listed in + /// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in + /// addition to the possible errors caused by `read` call. + /// In particular, `EAGAIN` is returned when no event is available on a + /// group that has been initialized with the flag `InitFlags::FAN_NONBLOCK`, + /// thus making this method nonblocking. + pub fn read_events(&self) -> Result> { + let metadata_size = size_of::(); + const BUFSIZ: usize = 4096; + let mut buffer = [0u8; BUFSIZ]; + let mut events = Vec::new(); + let mut offset = 0; + + let nread = read(self.fd.as_raw_fd(), &mut buffer)?; + + while (nread - offset) >= metadata_size { + let metadata = unsafe { + let mut metadata = + MaybeUninit::::uninit(); + ptr::copy_nonoverlapping( + buffer.as_ptr().add(offset), + metadata.as_mut_ptr() as *mut u8, + (BUFSIZ - offset).min(metadata_size), + ); + metadata.assume_init() + }; + + let fd = (metadata.fd != libc::FAN_NOFD).then(|| unsafe { + OwnedFd::from_raw_fd(metadata.fd) + }); + + events.push(FanotifyEvent { + version: metadata.vers, + mask: MaskFlags::from_bits_truncate(metadata.mask), + fd, + pid: metadata.pid, + }); + + offset += metadata.event_len as usize; + } + + Ok(events) + } + + /// Write an event response on the fanotify group. + /// + /// Returns a Result containing either `()` on success or errno otherwise. + /// + /// # Errors + /// + /// Possible errors can be those that are explicitly listed in + /// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in + /// addition to the possible errors caused by `write` call. + /// In particular, `EAGAIN` or `EWOULDBLOCK` is returned when no event is + /// available on a group that has been initialized with the flag + /// `InitFlags::FAN_NONBLOCK`, thus making this method nonblocking. + pub fn write_response(&self, response: FanotifyResponse) -> Result<()> { + let response_value = match response.response { + Response::Allow => libc::FAN_ALLOW, + Response::Deny => libc::FAN_DENY, + }; + let resp = libc::fanotify_response { + fd: response.fd.as_raw_fd(), + response: response_value, + }; + write( + self.fd.as_fd(), + unsafe { + std::slice::from_raw_parts( + (&resp as *const _) as *const u8, + size_of::(), + ) + }, + )?; + Ok(()) + } +} + +impl FromRawFd for Fanotify { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Fanotify { fd: OwnedFd::from_raw_fd(fd) } + } +} + +impl AsFd for Fanotify { + fn as_fd(&'_ self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 276d4760fd..7c5aa0692a 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -30,6 +30,12 @@ feature! { pub mod eventfd; } +#[cfg(target_os = "linux")] +feature! { + #![feature = "fanotify"] + pub mod fanotify; +} + #[cfg(any( target_os = "android", target_os = "dragonfly", diff --git a/test/sys/mod.rs b/test/sys/mod.rs index ae4ff953fe..43bb0bfaae 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -43,6 +43,8 @@ mod test_wait; #[cfg(any(target_os = "android", target_os = "linux"))] mod test_epoll; #[cfg(target_os = "linux")] +mod test_fanotify; +#[cfg(target_os = "linux")] mod test_inotify; mod test_pthread; #[cfg(any( diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs new file mode 100644 index 0000000000..ff30935003 --- /dev/null +++ b/test/sys/test_fanotify.rs @@ -0,0 +1,150 @@ +use crate::*; +use nix::sys::fanotify::{ + Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, OFlags, + Response, +}; +use std::fs::{read_link, File, OpenOptions}; +use std::io::ErrorKind; +use std::io::{Read, Write}; +use std::os::fd::{AsFd, AsRawFd}; +use std::thread; + +#[test] +/// Run fanotify tests sequentially to avoid tmp files races +pub fn test_fanotify() { + require_capability!("test_fanotify", CAP_SYS_ADMIN); + + test_fanotify_notifications(); + test_fanotify_responses(); +} + +fn test_fanotify_notifications() { + let group = + Fanotify::init(InitFlags::FAN_CLASS_NOTIF, OFlags::O_RDONLY).unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD | MarkFlags::FAN_MARK_MOUNT, + MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE, + None, + Some(tempdir.path()), + ) + .unwrap(); + + let tempfile = tempdir.path().join("test"); + + // create test file + File::create(&tempfile).unwrap(); + + let [event] = &group.read_events().unwrap()[..] else { + panic!("should have read exactly one event"); + }; + assert!(event.has_compile_version()); + assert_eq!(event.mask, MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_WRITE); + let fd = event.fd.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); + + // modify test file + { + let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap(); + f.write_all(b"hello").unwrap(); + } + + let [event] = &group.read_events().unwrap()[..] else { + panic!("should have read exactly one event"); + }; + assert!(event.has_compile_version()); + assert_eq!( + event.mask, + MaskFlags::FAN_OPEN + | MaskFlags::FAN_MODIFY + | MaskFlags::FAN_CLOSE_WRITE + ); + let fd = event.fd.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); + + // read test file + { + let mut f = File::open(&tempfile).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + } + + let [event] = &group.read_events().unwrap()[..] else { + panic!("should have read exactly one event"); + }; + assert!(event.has_compile_version()); + assert_eq!( + event.mask, + MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE + ); + let fd = event.fd.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); +} + +fn test_fanotify_responses() { + let group = + Fanotify::init(InitFlags::FAN_CLASS_CONTENT, OFlags::O_RDONLY).unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD | MarkFlags::FAN_MARK_MOUNT, + MaskFlags::FAN_OPEN_PERM, + None, + Some(tempdir.path()), + ) + .unwrap(); + + let tempfile = tempdir.path().join("test"); + let tempfname = tempfile.clone(); + + let file_thread = thread::spawn(move || { + // first try, should fail + let Err(err) = File::create(&tempfile) else { + panic!("first open is denied, should return error"); + }; + assert_eq!(err.kind(), ErrorKind::PermissionDenied); + + // second try, should succeed + File::create(&tempfile).unwrap(); + }); + + // Deny the first open try + let [event] = &group.read_events().unwrap()[..] else { + panic!("should have read exactly one event"); + }; + assert!(event.has_compile_version()); + assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); + let fd = event.fd.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfname); + group + .write_response(FanotifyResponse { + fd: fd.as_fd(), + response: Response::Deny, + }) + .unwrap(); + + //// Allow the second open try + let [event] = &group.read_events().unwrap()[..] else { + panic!("should have read exactly one event"); + }; + assert!(event.has_compile_version()); + assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); + let fd = event.fd.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfname); + group + .write_response(FanotifyResponse { + fd: fd.as_fd(), + response: Response::Allow, + }) + .unwrap(); + + file_thread.join().unwrap(); +} From 3cc9a5199d6bfa0cd5590172a4d0638824a000e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 09:38:23 +0100 Subject: [PATCH 02/11] Review/fix enum comments --- src/sys/fanotify.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index 6ec03f0c2d..5c413b08d6 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -25,11 +25,11 @@ libc_bitflags! { FAN_ACCESS; /// File was modified. FAN_MODIFY; - /// Metadata changed. Since Linux 5.1. + /// Metadata has changed. Since Linux 5.1. FAN_ATTRIB; - /// Writtable file closed. + /// Writtable file was closed. FAN_CLOSE_WRITE; - /// Unwrittable file closed. + /// Unwrittable file was closed. FAN_CLOSE_NOWRITE; /// File was opened. FAN_OPEN; @@ -45,19 +45,19 @@ libc_bitflags! { FAN_DELETE_SELF; /// Self was moved. Since Linux 5.1. FAN_MOVE_SELF; - /// File was opened for exec. Since Linux 5.0. + /// File was opened for execution. Since Linux 5.0. FAN_OPEN_EXEC; - /// Event queued overflowed. + /// Event queue overflowed. FAN_Q_OVERFLOW; /// Filesystem error. Since Linux 5.16. FAN_FS_ERROR; - /// File open in perm check. + /// Permission to open file was requested. FAN_OPEN_PERM; - /// File accessed in perm check. + /// Permission to access file was requested. FAN_ACCESS_PERM; - /// File open/exec in perm check. Since Linux 5.0. + /// Permission to open file for execution was requested. Since Linux 5.0. FAN_OPEN_EXEC_PERM; /// Interested in child events. @@ -66,7 +66,7 @@ libc_bitflags! { /// File was renamed. Since Linux 5.17. FAN_RENAME; - /// Event occured against dir. + /// Event occurred against dir. FAN_ONDIR; /// Combination of `FAN_CLOSE_WRITE` and `FAN_CLOSE_NOWRITE`. From d5f0d4aeefdcf0f2565f6db3e7cbc0bf433775dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 09:45:07 +0100 Subject: [PATCH 03/11] Rename has_compile_version to check_version --- src/sys/fanotify.rs | 4 ++-- test/sys/test_fanotify.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index 5c413b08d6..e599bdcc89 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -170,7 +170,7 @@ pub struct FanotifyEvent { /// Version number for the structure. It must be compared to /// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime /// version does match. It can be done with the - /// `FanotifyEvent::has_compile_version` method. + /// `FanotifyEvent::check_version` method. pub version: u8, /// Mask flags of the events. pub mask: MaskFlags, @@ -186,7 +186,7 @@ pub struct FanotifyEvent { impl FanotifyEvent { /// Checks that compile fanotify API version is equal to the version of the /// event. - pub fn has_compile_version(&self) -> bool { + pub fn check_version(&self) -> bool { self.version == FANOTIFY_METADATA_VERSION } } diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs index ff30935003..b3d4ce9329 100644 --- a/test/sys/test_fanotify.rs +++ b/test/sys/test_fanotify.rs @@ -40,7 +40,7 @@ fn test_fanotify_notifications() { let [event] = &group.read_events().unwrap()[..] else { panic!("should have read exactly one event"); }; - assert!(event.has_compile_version()); + assert!(event.check_version()); assert_eq!(event.mask, MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_WRITE); let fd = event.fd.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); @@ -55,7 +55,7 @@ fn test_fanotify_notifications() { let [event] = &group.read_events().unwrap()[..] else { panic!("should have read exactly one event"); }; - assert!(event.has_compile_version()); + assert!(event.check_version()); assert_eq!( event.mask, MaskFlags::FAN_OPEN @@ -76,7 +76,7 @@ fn test_fanotify_notifications() { let [event] = &group.read_events().unwrap()[..] else { panic!("should have read exactly one event"); }; - assert!(event.has_compile_version()); + assert!(event.check_version()); assert_eq!( event.mask, MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE @@ -118,7 +118,7 @@ fn test_fanotify_responses() { let [event] = &group.read_events().unwrap()[..] else { panic!("should have read exactly one event"); }; - assert!(event.has_compile_version()); + assert!(event.check_version()); assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); let fd = event.fd.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); @@ -134,7 +134,7 @@ fn test_fanotify_responses() { let [event] = &group.read_events().unwrap()[..] else { panic!("should have read exactly one event"); }; - assert!(event.has_compile_version()); + assert!(event.check_version()); assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); let fd = event.fd.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); From db46b2efa2bc7fe8a8f5916392fcb212f463ac2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 09:56:58 +0100 Subject: [PATCH 04/11] Fix lint unsafe_block_in_unsafe_fn --- src/sys/fanotify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index e599bdcc89..7c95b6c872 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -354,7 +354,7 @@ impl Fanotify { impl FromRawFd for Fanotify { unsafe fn from_raw_fd(fd: RawFd) -> Self { - Fanotify { fd: OwnedFd::from_raw_fd(fd) } + Fanotify { fd: unsafe { OwnedFd::from_raw_fd(fd) }} } } From 44feea8de90ffb440b88607e622e6793fe8e96e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 11:01:46 +0100 Subject: [PATCH 05/11] Rename fanotify OFlags to EventFFlags --- src/fcntl.rs | 6 +++++- src/sys/fanotify.rs | 19 +++++++++++++++++-- test/sys/test_fanotify.rs | 8 +++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/fcntl.rs b/src/fcntl.rs index 7910cc9b7d..8f968fc2a9 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -64,7 +64,11 @@ libc_bitflags! { } } -#[cfg(any(feature = "fs", feature = "term"))] +#[cfg(any( + feature = "fs", + feature = "term", + all(feature = "fanotify", target_os = "linux") +))] libc_bitflags!( /// Configuration options for opened files. #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))] diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index 7c95b6c872..4ff1cd7c7b 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -12,6 +12,7 @@ use crate::{NixPath, Result}; use crate::errno::Errno; +use crate::fcntl::OFlag; use crate::unistd::{read, write}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::mem::{MaybeUninit, size_of}; @@ -102,7 +103,7 @@ libc_bitflags! { libc_bitflags! { /// File status flags for fanotify events file descriptors. - pub struct OFlags: libc::c_uint { + pub struct EventFFlags: libc::c_uint { /// Read only access. O_RDONLY as libc::c_uint; /// Write only access. @@ -126,6 +127,20 @@ libc_bitflags! { } } +impl TryFrom for EventFFlags { + type Error = Errno; + + fn try_from(o_flag: OFlag) -> Result { + EventFFlags::from_bits(o_flag.bits() as u32).ok_or(Errno::EINVAL) + } +} + +impl From for OFlag { + fn from(event_f_flags: EventFFlags) -> Self { + OFlag::from_bits_retain(event_f_flags.bits() as i32) + } +} + libc_bitflags! { /// Configuration options for [`fanotify_mark`](fn.fanotify_mark.html). pub struct MarkFlags: libc::c_uint { @@ -223,7 +238,7 @@ impl Fanotify { /// Returns a Result containing a Fanotify instance. /// /// For more information, see [fanotify_init(2)](https://man7.org/linux/man-pages/man7/fanotify_init.2.html). - pub fn init(flags: InitFlags, event_f_flags: OFlags) -> Result { + pub fn init(flags: InitFlags, event_f_flags: EventFFlags) -> Result { let res = Errno::result(unsafe { libc::fanotify_init(flags.bits(), event_f_flags.bits()) }); diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs index b3d4ce9329..c9fe1519a6 100644 --- a/test/sys/test_fanotify.rs +++ b/test/sys/test_fanotify.rs @@ -1,6 +1,6 @@ use crate::*; use nix::sys::fanotify::{ - Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, OFlags, + EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, Response, }; use std::fs::{read_link, File, OpenOptions}; @@ -20,7 +20,8 @@ pub fn test_fanotify() { fn test_fanotify_notifications() { let group = - Fanotify::init(InitFlags::FAN_CLASS_NOTIF, OFlags::O_RDONLY).unwrap(); + Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY) + .unwrap(); let tempdir = tempfile::tempdir().unwrap(); group @@ -88,7 +89,8 @@ fn test_fanotify_notifications() { fn test_fanotify_responses() { let group = - Fanotify::init(InitFlags::FAN_CLASS_CONTENT, OFlags::O_RDONLY).unwrap(); + Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY) + .unwrap(); let tempdir = tempfile::tempdir().unwrap(); group From 3d805d301e20e887e81a9c11168a7828c0ed5f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 11:23:59 +0100 Subject: [PATCH 06/11] Use existing function at_rawfd from fcntl --- src/fcntl.rs | 3 ++- src/sys/fanotify.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fcntl.rs b/src/fcntl.rs index 8f968fc2a9..becfb06ba2 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -198,7 +198,8 @@ libc_bitflags!( /// Computes the raw fd consumed by a function of the form `*at`. #[cfg(any( all(feature = "fs", not(target_os = "redox")), - all(feature = "process", any(target_os = "android", target_os = "linux")) + all(feature = "process", any(target_os = "android", target_os = "linux")), + all(feature = "fanotify", target_os = "linux") ))] pub(crate) fn at_rawfd(fd: Option) -> raw::c_int { fd.unwrap_or(libc::AT_FDCWD) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index 4ff1cd7c7b..a9eccc960e 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -12,7 +12,7 @@ use crate::{NixPath, Result}; use crate::errno::Errno; -use crate::fcntl::OFlag; +use crate::fcntl::{OFlag, at_rawfd}; use crate::unistd::{read, write}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::mem::{MaybeUninit, size_of}; @@ -274,7 +274,7 @@ impl Fanotify { self.fd.as_raw_fd(), flags.bits(), mask.bits(), - dirfd.unwrap_or(libc::AT_FDCWD), + at_rawfd(dirfd), p, ) })?; From 72472a10a43845e1ced6e59a7f77a1ce1ea985b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 14:12:44 +0100 Subject: [PATCH 07/11] Add missing feature guard for docs --- src/fcntl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fcntl.rs b/src/fcntl.rs index becfb06ba2..d327c4b22a 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -71,7 +71,7 @@ libc_bitflags! { ))] libc_bitflags!( /// Configuration options for opened files. - #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term", all(feature = "fanotify", target_os = "linux")))))] pub struct OFlag: c_int { /// Mask for the access mode of the file. O_ACCMODE; From bc174dcbee496379138829c3bf83fbc0a82fa671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 15:08:07 +0100 Subject: [PATCH 08/11] Change FanotifyEvent struct to a simple wrapper over libc structure --- src/sys/fanotify.rs | 66 +++++++++++++++++++++++++-------------- test/sys/test_fanotify.rs | 28 +++++++++++------ 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index a9eccc960e..e5abe05958 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -13,7 +13,7 @@ use crate::{NixPath, Result}; use crate::errno::Errno; use crate::fcntl::{OFlag, at_rawfd}; -use crate::unistd::{read, write}; +use crate::unistd::{close, read, write}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::mem::{MaybeUninit, size_of}; use std::ptr; @@ -178,31 +178,61 @@ libc_bitflags! { /// Compile version number of fanotify API. pub const FANOTIFY_METADATA_VERSION: u8 = libc::FANOTIFY_METADATA_VERSION; -#[derive(Debug)] /// Abstract over `libc::fanotify_event_metadata`, which represents an event /// received via `Fanotify::read_events`. -pub struct FanotifyEvent { +// Is not Clone due to fd field, to avoid use-after-close scenarios. +#[derive(Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +#[allow(missing_copy_implementations)] +pub struct FanotifyEvent(libc::fanotify_event_metadata); + +impl FanotifyEvent { /// Version number for the structure. It must be compared to /// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime /// version does match. It can be done with the /// `FanotifyEvent::check_version` method. - pub version: u8, + pub fn version(&self) -> u8 { + self.0.vers + } + + /// Checks that compile fanotify API version is equal to the version of the + /// event. + pub fn check_version(&self) -> bool { + self.version() == FANOTIFY_METADATA_VERSION + } + /// Mask flags of the events. - pub mask: MaskFlags, + pub fn mask(&self) -> MaskFlags { + MaskFlags::from_bits_truncate(self.0.mask) + } + /// The file descriptor of the event. If the value is `None` when reading /// from the fanotify group, this event is to notify that a group queue /// overflow occured. - pub fd: Option, + pub fn fd(&self) -> Option { + if self.0.fd == libc::FAN_NOFD { + None + } else { + // SAFETY: self.0.fd will be opened for the lifetime of `Self`, + // which is longer than the lifetime of the returned BorrowedFd, so + // it is safe. + Some(unsafe { BorrowedFd::borrow_raw(self.0.fd) }) + } + } + /// PID of the process that caused the event. TID in case flag /// `FAN_REPORT_TID` was set at group initialization. - pub pid: i32, + pub fn pid(&self) -> i32 { + self.0.pid + } } -impl FanotifyEvent { - /// Checks that compile fanotify API version is equal to the version of the - /// event. - pub fn check_version(&self) -> bool { - self.version == FANOTIFY_METADATA_VERSION +impl Drop for FanotifyEvent { + fn drop(&mut self) { + let e = close(self.0.fd); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; } } @@ -316,17 +346,7 @@ impl Fanotify { metadata.assume_init() }; - let fd = (metadata.fd != libc::FAN_NOFD).then(|| unsafe { - OwnedFd::from_raw_fd(metadata.fd) - }); - - events.push(FanotifyEvent { - version: metadata.vers, - mask: MaskFlags::from_bits_truncate(metadata.mask), - fd, - pid: metadata.pid, - }); - + events.push(FanotifyEvent(metadata)); offset += metadata.event_len as usize; } diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs index c9fe1519a6..720a2c5702 100644 --- a/test/sys/test_fanotify.rs +++ b/test/sys/test_fanotify.rs @@ -42,8 +42,12 @@ fn test_fanotify_notifications() { panic!("should have read exactly one event"); }; assert!(event.check_version()); - assert_eq!(event.mask, MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_WRITE); - let fd = event.fd.as_ref().unwrap(); + assert_eq!( + event.mask(), + MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_WRITE + ); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); @@ -58,12 +62,13 @@ fn test_fanotify_notifications() { }; assert!(event.check_version()); assert_eq!( - event.mask, + event.mask(), MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE_WRITE ); - let fd = event.fd.as_ref().unwrap(); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); @@ -79,10 +84,11 @@ fn test_fanotify_notifications() { }; assert!(event.check_version()); assert_eq!( - event.mask, + event.mask(), MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE ); - let fd = event.fd.as_ref().unwrap(); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); } @@ -121,8 +127,9 @@ fn test_fanotify_responses() { panic!("should have read exactly one event"); }; assert!(event.check_version()); - assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); - let fd = event.fd.as_ref().unwrap(); + assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfname); group @@ -137,8 +144,9 @@ fn test_fanotify_responses() { panic!("should have read exactly one event"); }; assert!(event.check_version()); - assert_eq!(event.mask, MaskFlags::FAN_OPEN_PERM); - let fd = event.fd.as_ref().unwrap(); + assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfname); group From d2e51b642c5699ef88bbdf54ae41ff59f2962b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 15:43:56 +0100 Subject: [PATCH 09/11] Change FanotifyResponse struct to a simple wrapper over libc structure --- src/sys/fanotify.rs | 54 ++++++++++++++++++++++----------------- test/sys/test_fanotify.rs | 12 +++------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index e5abe05958..daa62a30bc 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -14,8 +14,9 @@ use crate::{NixPath, Result}; use crate::errno::Errno; use crate::fcntl::{OFlag, at_rawfd}; use crate::unistd::{close, read, write}; -use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; +use std::marker::PhantomData; use std::mem::{MaybeUninit, size_of}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::ptr; libc_bitflags! { @@ -236,23 +237,36 @@ impl Drop for FanotifyEvent { } } -#[derive(Debug)] /// Abstraction over the structure to be sent to allow or deny a given event. -pub struct FanotifyResponse<'e> { - /// A borrow of the file descriptor from the structure `FanotifyEvent`. - pub fd: BorrowedFd<'e>, - /// Indication whether or not the permission is to be granted. - pub response: Response, +#[derive(Debug)] +#[repr(transparent)] +pub struct FanotifyResponse<'a> { + inner: libc::fanotify_response, + _borrowed_fd: PhantomData>, +} + +impl<'a> FanotifyResponse<'a> { + /// Create a new response. + pub fn new(fd: BorrowedFd<'a>, response: Response) -> Self { + Self { + inner: libc::fanotify_response { + fd: fd.as_raw_fd(), + response: response.bits(), + }, + _borrowed_fd: PhantomData, + } + } } -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -/// Response to be wrapped in `FanotifyResponse` and sent to the `Fanotify` -/// group to allow or deny an event. -pub enum Response { - /// Allow the event. - Allow, - /// Deny the event. - Deny, +libc_bitflags! { + /// Response to be wrapped in `FanotifyResponse` and sent to the `Fanotify` + /// group to allow or deny an event. + pub struct Response: u32 { + /// Allow the event. + FAN_ALLOW; + /// Deny the event. + FAN_DENY; + } } /// A fanotify group. This is also a file descriptor that can feed to other @@ -366,19 +380,11 @@ impl Fanotify { /// available on a group that has been initialized with the flag /// `InitFlags::FAN_NONBLOCK`, thus making this method nonblocking. pub fn write_response(&self, response: FanotifyResponse) -> Result<()> { - let response_value = match response.response { - Response::Allow => libc::FAN_ALLOW, - Response::Deny => libc::FAN_DENY, - }; - let resp = libc::fanotify_response { - fd: response.fd.as_raw_fd(), - response: response_value, - }; write( self.fd.as_fd(), unsafe { std::slice::from_raw_parts( - (&resp as *const _) as *const u8, + (&response.inner as *const _) as *const u8, size_of::(), ) }, diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs index 720a2c5702..9fd23395ae 100644 --- a/test/sys/test_fanotify.rs +++ b/test/sys/test_fanotify.rs @@ -6,7 +6,7 @@ use nix::sys::fanotify::{ use std::fs::{read_link, File, OpenOptions}; use std::io::ErrorKind; use std::io::{Read, Write}; -use std::os::fd::{AsFd, AsRawFd}; +use std::os::fd::AsRawFd; use std::thread; #[test] @@ -133,10 +133,7 @@ fn test_fanotify_responses() { let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfname); group - .write_response(FanotifyResponse { - fd: fd.as_fd(), - response: Response::Deny, - }) + .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY)) .unwrap(); //// Allow the second open try @@ -150,10 +147,7 @@ fn test_fanotify_responses() { let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfname); group - .write_response(FanotifyResponse { - fd: fd.as_fd(), - response: Response::Allow, - }) + .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW)) .unwrap(); file_thread.join().unwrap(); From fd0aa06ae47d88386f11cdb3a0ea5fe14edcbd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 16:03:37 +0100 Subject: [PATCH 10/11] Cast pointer with function 'cast' instead of 'as' --- src/sys/fanotify.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index daa62a30bc..d1d0b7f0db 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -354,7 +354,7 @@ impl Fanotify { MaybeUninit::::uninit(); ptr::copy_nonoverlapping( buffer.as_ptr().add(offset), - metadata.as_mut_ptr() as *mut u8, + metadata.as_mut_ptr().cast(), (BUFSIZ - offset).min(metadata_size), ); metadata.assume_init() @@ -384,7 +384,7 @@ impl Fanotify { self.fd.as_fd(), unsafe { std::slice::from_raw_parts( - (&response.inner as *const _) as *const u8, + (&response.inner as *const libc::fanotify_response).cast(), size_of::(), ) }, From 399a2dbbc896c0c34668be99d8c2936fe0093f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Deharbe?= Date: Mon, 20 Nov 2023 19:54:17 +0100 Subject: [PATCH 11/11] Add FAN_REPORT_PIDFD and FAN_REPORT_TID fanotify init flags --- src/sys/fanotify.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index d1d0b7f0db..a089d78df8 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -59,7 +59,8 @@ libc_bitflags! { FAN_OPEN_PERM; /// Permission to access file was requested. FAN_ACCESS_PERM; - /// Permission to open file for execution was requested. Since Linux 5.0. + /// Permission to open file for execution was requested. Since Linux + /// 5.0. FAN_OPEN_EXEC_PERM; /// Interested in child events. @@ -99,6 +100,11 @@ libc_bitflags! { FAN_UNLIMITED_QUEUE; /// Remove the limit of 8192 marks. FAN_UNLIMITED_MARKS; + + /// Make `FanotifyEvent::pid` return pidfd. Since Linux 5.15. + FAN_REPORT_PIDFD; + /// Make `FanotifyEvent::pid` return thread id. Since Linux 4.20. + FAN_REPORT_TID; } }