| //! 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::errno::Errno; |
| use crate::fcntl::{at_rawfd, OFlag}; |
| use crate::unistd::{close, read, write}; |
| use crate::{NixPath, Result}; |
| use std::marker::PhantomData; |
| use std::mem::{size_of, MaybeUninit}; |
| use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; |
| 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 has changed. Since Linux 5.1. |
| FAN_ATTRIB; |
| /// Writtable file was closed. |
| FAN_CLOSE_WRITE; |
| /// Unwrittable file was 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 execution. Since Linux 5.0. |
| FAN_OPEN_EXEC; |
| |
| /// Event queue overflowed. |
| FAN_Q_OVERFLOW; |
| /// Filesystem error. Since Linux 5.16. |
| FAN_FS_ERROR; |
| |
| /// Permission to open file was requested. |
| FAN_OPEN_PERM; |
| /// Permission to access file was requested. |
| FAN_ACCESS_PERM; |
| /// Permission to open file for execution was requested. 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 occurred 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 on the number of events in the event queue. |
| /// |
| /// Prior to Linux kernel 5.13, this limit was hardcoded to 16384. After |
| /// 5.13, one can change it via file `/proc/sys/fs/fanotify/max_queued_events`. |
| /// |
| /// See `fanotify(7)` for details about this limit. Use of this flag |
| /// requires the `CAP_SYS_ADMIN` capability. |
| FAN_UNLIMITED_QUEUE; |
| /// Remove the limit on the number of fanotify marks per user. |
| /// |
| /// Prior to Linux kernel 5.13, this limit was hardcoded to 8192 (per |
| /// group, not per user). After 5.13, one can change it via file |
| /// `/proc/sys/fs/fanotify/max_user_marks`. |
| /// |
| /// See `fanotify(7)` for details about this limit. Use of this flag |
| /// requires the `CAP_SYS_ADMIN` capability. |
| 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; |
| } |
| } |
| |
| libc_bitflags! { |
| /// File status flags for fanotify events file descriptors. |
| pub struct EventFFlags: 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; |
| } |
| } |
| |
| impl TryFrom<OFlag> for EventFFlags { |
| type Error = Errno; |
| |
| fn try_from(o_flag: OFlag) -> Result<Self> { |
| EventFFlags::from_bits(o_flag.bits() as u32).ok_or(Errno::EINVAL) |
| } |
| } |
| |
| impl From<EventFFlags> 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 { |
| /// 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; |
| |
| /// Abstract over `libc::fanotify_event_metadata`, which represents an event |
| /// received via `Fanotify::read_events`. |
| // 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 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 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 fn fd(&self) -> Option<BorrowedFd> { |
| 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 fn pid(&self) -> i32 { |
| self.0.pid |
| } |
| } |
| |
| impl Drop for FanotifyEvent { |
| fn drop(&mut self) { |
| if self.0.fd == libc::FAN_NOFD { |
| return; |
| } |
| let e = close(self.0.fd); |
| if !std::thread::panicking() && e == Err(Errno::EBADF) { |
| panic!("Closing an invalid file descriptor!"); |
| }; |
| } |
| } |
| |
| /// Abstraction over the structure to be sent to allow or deny a given event. |
| #[derive(Debug)] |
| #[repr(transparent)] |
| pub struct FanotifyResponse<'a> { |
| inner: libc::fanotify_response, |
| _borrowed_fd: PhantomData<BorrowedFd<'a>>, |
| } |
| |
| 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, |
| } |
| } |
| } |
| |
| 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 |
| /// 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: EventFFlags, |
| ) -> Result<Fanotify> { |
| 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<P: ?Sized + NixPath>( |
| &self, |
| flags: MarkFlags, |
| mask: MaskFlags, |
| dirfd: Option<RawFd>, |
| path: Option<&P>, |
| ) -> Result<()> { |
| let res = crate::with_opt_nix_path(path, |p| unsafe { |
| libc::fanotify_mark( |
| self.fd.as_raw_fd(), |
| flags.bits(), |
| mask.bits(), |
| at_rawfd(dirfd), |
| 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<Vec<FanotifyEvent>> { |
| let metadata_size = size_of::<libc::fanotify_event_metadata>(); |
| 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::<libc::fanotify_event_metadata>::uninit(); |
| ptr::copy_nonoverlapping( |
| buffer.as_ptr().add(offset), |
| metadata.as_mut_ptr().cast(), |
| (BUFSIZ - offset).min(metadata_size), |
| ); |
| metadata.assume_init() |
| }; |
| |
| events.push(FanotifyEvent(metadata)); |
| 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<()> { |
| write(self.fd.as_fd(), unsafe { |
| std::slice::from_raw_parts( |
| (&response.inner as *const libc::fanotify_response).cast(), |
| size_of::<libc::fanotify_response>(), |
| ) |
| })?; |
| Ok(()) |
| } |
| } |
| |
| impl FromRawFd for Fanotify { |
| unsafe fn from_raw_fd(fd: RawFd) -> Self { |
| Fanotify { |
| fd: unsafe { OwnedFd::from_raw_fd(fd) }, |
| } |
| } |
| } |
| |
| impl AsFd for Fanotify { |
| fn as_fd(&'_ self) -> BorrowedFd<'_> { |
| self.fd.as_fd() |
| } |
| } |