From 2bdd18ba16711682f1f095d8e6422f56b04cbbc7 Mon Sep 17 00:00:00 2001 From: Philip Woolford Date: Thu, 6 Nov 2025 12:00:05 +1030 Subject: [PATCH 1/2] Push for test on remote system --- Cargo.toml | 4 +-- src/high_level.rs | 6 ++--- src/low_level.rs | 62 ++++++++++++++++++++++++----------------------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bd849e..51c2063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fanotify-rs" version = "0.3.2" authors = ["zhanglei ", "n01e0 ", "Philip Woolford "] -edition = "2021" +edition = "2024" license = "MIT OR Apache-2.0" description = "The high-level/low-level implementation of Linux Fanotify." repository = "https://github.com/ZhangLei-cn/fanotify-rs" @@ -15,4 +15,4 @@ doc = true [dependencies] libc = "0.2" -enum-iterator = "1.5" \ No newline at end of file +enum-iterator = "2.3.0" \ No newline at end of file diff --git a/src/high_level.rs b/src/high_level.rs index 1fe92a6..7ba64d6 100644 --- a/src/high_level.rs +++ b/src/high_level.rs @@ -217,9 +217,9 @@ impl Fanotify { Ok(()) } - pub fn read_event(&self) -> Vec { + pub fn read_event(&self) -> std::io::Result> { let mut result = Vec::new(); - let events = fanotify_read(self.fd); + let events = fanotify_read(self.fd)?; for metadata in events { let path = read_link(format!("/proc/self/fd/{}", metadata.fd)).unwrap_or_default(); let path = path.to_str().unwrap(); @@ -230,7 +230,7 @@ impl Fanotify { pid: metadata.pid, }); } - result + Ok(result) } pub fn send_response>(&self, fd: T, resp: FanotifyResponse) { diff --git a/src/low_level.rs b/src/low_level.rs index 52959c2..730c096 100644 --- a/src/low_level.rs +++ b/src/low_level.rs @@ -1,13 +1,16 @@ use crate::FanotifyPath; -use libc::{__s32, __u16, __u32, __u64, __u8}; -use std::io::Error; +use libc::{__s32, __u8, __u16, __u32, __u64}; +use std::io::{Error, ErrorKind}; use std::mem; use std::os::unix::ffi::OsStrExt; use std::slice; #[doc(hidden)] /// Re-export relevant libc constants -pub use libc::{O_RDONLY, O_WRONLY, O_RDWR, O_LARGEFILE, O_CLOEXEC, O_APPEND, O_DSYNC, O_NOATIME, O_NONBLOCK, O_SYNC}; +pub use libc::{ + O_APPEND, O_CLOEXEC, O_DSYNC, O_LARGEFILE, O_NOATIME, O_NONBLOCK, O_RDONLY, O_RDWR, O_SYNC, + O_WRONLY, +}; #[derive(Debug, Clone, Copy)] #[repr(C)] @@ -265,14 +268,10 @@ pub const AT_EMPTY_PATH: i32 = 0x1000; pub fn fanotify_init(flags: u32, event_f_flags: u32) -> Result { unsafe { match libc::fanotify_init(flags, event_f_flags) { - -1 => { - Err(Error::last_os_error()) - } - fd => { - Ok(fd) - } + -1 => Err(Error::last_os_error()), + fd => Ok(fd), } -} + } } /// Adds, removes, or modifies an fanotify mark on a filesystem object. // The caller must have read permission on the filesystem object that is to be marked. @@ -344,31 +343,34 @@ pub fn fanotify_mark( raw_path.push(0u8); // data must be null terminated //make sure path is null terminated - match libc::fanotify_mark( - fanotify_fd, - flags, - mask, - dirfd, - raw_path.as_ptr().cast(), - ) { - 0 => { - Ok(()) - } - _ => { - Err(Error::last_os_error()) - } + match libc::fanotify_mark(fanotify_fd, flags, mask, dirfd, raw_path.as_ptr().cast()) { + 0 => Ok(()), + _ => Err(Error::last_os_error()), } } } -pub fn fanotify_read(fanotify_fd: i32) -> Vec { +pub fn fanotify_read(fanotify_fd: i32) -> std::io::Result> { let mut vec = Vec::new(); - let mut buffer = Box::new([0u8;FAN_EVENT_METADATA_LEN * 200]); + let mut buffer = Box::new([0u8; FAN_EVENT_METADATA_LEN * 200]); unsafe { // Allocate a buffer to store up to 200 events - - let sizeof = libc::read(fanotify_fd, buffer.as_mut_ptr() as _, FAN_EVENT_METADATA_LEN * 200); - if sizeof != libc::EAGAIN as isize && sizeof > 0 { + + let sizeof = libc::read( + fanotify_fd, + buffer.as_mut_ptr() as _, + FAN_EVENT_METADATA_LEN * 200, + ); + + // Get the Error code fromt the OS, as we don't want to bubble up a `WouldBlock` in our API + if sizeof == -1 + && let errno = std::io::Error::last_os_error() + && !matches!(errno.kind(), ErrorKind::WouldBlock) + { + return Err(errno); + } + + if sizeof > 0 { let src = slice::from_raw_parts( buffer.as_ptr().cast::(), sizeof as usize / FAN_EVENT_METADATA_LEN, @@ -376,11 +378,11 @@ pub fn fanotify_read(fanotify_fd: i32) -> Vec { vec.extend_from_slice(src); } } - vec + Ok(vec) } pub fn close_fd(fd: i32) { unsafe { libc::close(fd); } -} \ No newline at end of file +} From 7fdcab779d2b8a2854d720390058c38c12816274 Mon Sep 17 00:00:00 2001 From: Philip Woolford Date: Tue, 11 Nov 2025 16:52:59 +1030 Subject: [PATCH 2/2] Return `std::io::Result`s from the low level APi and restructure the high level API to take bitflags for the fanotify mark mode, fanotify init flags, and the fd flags for the returned fanotify fd. --- Cargo.toml | 9 +- demo/with_poll/Cargo.toml | 2 +- demo/with_poll/src/main.rs | 17 +- src/high_level.rs | 140 +++++++++++++---- src/lib.rs | 3 +- src/low_level.rs | 311 ++++++++++++++++++++++++++++++++++--- tests/high_level.rs | 20 ++- 7 files changed, 432 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51c2063..3474455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fanotify-rs" -version = "0.3.2" +version = "0.4.0" authors = ["zhanglei ", "n01e0 ", "Philip Woolford "] edition = "2024" license = "MIT OR Apache-2.0" @@ -13,6 +13,11 @@ name = "fanotify" path = "src/lib.rs" doc = true +[features] +default = ["high-level"] +high-level = ["bitflags"] + [dependencies] libc = "0.2" -enum-iterator = "2.3.0" \ No newline at end of file +enum-iterator = "2.3" +bitflags = {version = "2.10", optional = true} diff --git a/demo/with_poll/Cargo.toml b/demo/with_poll/Cargo.toml index 63b43cf..57bde79 100644 --- a/demo/with_poll/Cargo.toml +++ b/demo/with_poll/Cargo.toml @@ -10,4 +10,4 @@ description = "fanotify-rs with poll" [dependencies] clap = "4.4.18" fanotify-rs = { path = "../../" } -nix = {version = "0.27.1", features = ["poll"] } +nix = {version = "0.30.1", features = ["poll"] } diff --git a/demo/with_poll/src/main.rs b/demo/with_poll/src/main.rs index e131fdb..eb2b5ba 100644 --- a/demo/with_poll/src/main.rs +++ b/demo/with_poll/src/main.rs @@ -2,25 +2,24 @@ use fanotify::high_level::*; use nix::poll::{poll, PollFd, PollFlags}; use std::os::fd::AsFd; -fn main() { +fn main() -> Result<(), Box> { let app = clap::Command::new("with_poll") .arg(clap::Arg::new("path").index(1).required(true)) .get_matches(); - let fd = Fanotify::new_with_nonblocking(FanotifyMode::CONTENT); + let fd = Fanotify::new_nonblocking(FanotifyMode::CONTENT)?; fd.add_mountpoint( - FAN_OPEN_EXEC | FAN_CLOSE_WRITE, + MarkMode::OpenExec | MarkMode::CloseWrite, app.get_one::("path") .expect("We can unwrap here as clap enforces the existence of `path`"), - ) - .unwrap(); + )?; let fd_handle = fd.as_fd(); - let mut fds = [PollFd::new(&fd_handle, PollFlags::POLLIN)]; + let mut fds = [PollFd::new(fd_handle, PollFlags::POLLIN)]; loop { - let poll_num = poll(&mut fds, -1).unwrap(); + let poll_num = poll(&mut fds, None::).unwrap(); if poll_num > 0 { - for event in fd.read_event() { + for event in fd.read_event()? { println!("{:#?}", event); fd.send_response(event.fd, FanotifyResponse::Allow); } @@ -29,4 +28,6 @@ fn main() { break; } } + + Ok(()) } diff --git a/src/high_level.rs b/src/high_level.rs index 7ba64d6..ee9895e 100644 --- a/src/high_level.rs +++ b/src/high_level.rs @@ -1,21 +1,11 @@ -use crate::low_level::{ - close_fd, fanotify_init, fanotify_mark, fanotify_read, FanotifyEventMetadata, AT_FDCWD, - FAN_ALLOW, FAN_CLASS_CONTENT, FAN_CLASS_NOTIF, FAN_CLASS_PRE_CONTENT, FAN_CLOEXEC, FAN_DENY, - FAN_MARK_ADD, FAN_MARK_FLUSH, FAN_MARK_MOUNT, FAN_MARK_REMOVE, FAN_NONBLOCK, O_CLOEXEC, - O_RDONLY, -}; use crate::FanotifyPath; -use enum_iterator::{all, Sequence}; +use bitflags::bitflags; +use enum_iterator::{Sequence, all}; use std::fs::read_link; use std::io::Error; use std::os::fd::{AsFd, BorrowedFd}; -pub use crate::low_level::{ - FAN_ACCESS, FAN_ACCESS_PERM, FAN_ATTRIB, FAN_CLOSE, FAN_CLOSE_NOWRITE, FAN_CLOSE_WRITE, - FAN_CREATE, FAN_DELETE, FAN_DELETE_SELF, FAN_EVENT_ON_CHILD, FAN_MODIFY, FAN_MOVE, - FAN_MOVED_FROM, FAN_MOVED_TO, FAN_MOVE_SELF, FAN_ONDIR, FAN_OPEN, FAN_OPEN_EXEC, - FAN_OPEN_EXEC_PERM, FAN_OPEN_PERM, -}; +use crate::low_level::*; pub struct Fanotify { fd: i32, @@ -41,6 +31,61 @@ where } } +bitflags! { + pub struct MarkMode: u64 { + const Access = FAN_ACCESS; + const Modify = FAN_MODIFY; + const Attributes = FAN_ATTRIB; + const CloseWrite = FAN_CLOSE_WRITE; + const CloseNoWrite = FAN_CLOSE_NOWRITE; + const Open = FAN_OPEN; + const MovedFrom = FAN_MOVED_FROM; + const MovedTo = FAN_MOVED_TO; + const Moved = FAN_MOVE; + const Create = FAN_CREATE; + const Delete = FAN_DELETE; + const DeleteSelf = FAN_DELETE_SELF; + const MoveSelf = FAN_MOVE_SELF; + const OpenExec = FAN_OPEN_EXEC; + const OpenPerm = FAN_OPEN_PERM; + const OpenExecPerm = FAN_OPEN_EXEC_PERM; + const OnDirectory = FAN_ONDIR; + const EventOnChild = FAN_EVENT_ON_CHILD; + const Close = FAN_CLOSE; + } +} + +bitflags! { + pub struct Flags: u32 { + const CloseOnExec = FAN_CLOEXEC; + const NonBlocking = FAN_NONBLOCK; + const NotificationClass = FAN_CLASS_NOTIF; + const ContentClass = FAN_CLASS_CONTENT; + const PreContentClass = FAN_CLASS_PRE_CONTENT; + const UnlimitedEventQueue = FAN_UNLIMITED_QUEUE; + const UnlimitedMarks = FAN_UNLIMITED_MARKS; + const Audit = FAN_ENABLE_AUDIT; + const ReportThreadID = FAN_REPORT_TID; + const ReportDirectoryID = FAN_REPORT_DIR_FID; + const ReportName = FAN_REPORT_NAME; + } +} + +bitflags! { + pub struct EventFlags: u32 { + const CloseOnExec = FAN_CLOEXEC; + const ReadOnly = O_RDONLY.cast_unsigned(); + const ReadWrite = O_RDWR.cast_unsigned(); + const WriteOnly = O_WRONLY.cast_unsigned(); + const LargeFile = O_LARGEFILE.cast_unsigned(); + const Append = O_APPEND.cast_unsigned(); + const MetadataSync = O_DSYNC.cast_unsigned(); + const NoAccessTime = O_NOATIME.cast_unsigned(); + const NonBlock = O_NONBLOCK.cast_unsigned(); + const Sync = O_SYNC.cast_unsigned(); + } +} + #[derive(Debug, Clone, Copy, Sequence, PartialEq)] pub enum FanEvent { Access = FAN_ACCESS as isize, @@ -119,6 +164,7 @@ pub struct Event { pub path: String, pub events: Vec, pub pid: i32, + pub additional_records: Vec, } impl Event { @@ -134,6 +180,7 @@ impl Event { path: self.path.clone(), events: self.events.clone(), pid: self.pid, + additional_records: self.additional_records.clone(), }) } } @@ -152,6 +199,7 @@ impl From for Event { path: path.to_str().unwrap().to_string(), events: events_from_mask(metadata.mask), pid: metadata.pid, + additional_records: vec![], } } } @@ -193,34 +241,67 @@ impl Fanotify { }) } - pub fn add_path(&self, mode: u64, path: &P) -> Result<(), Error> { - fanotify_mark(self.fd, FAN_MARK_ADD, mode, AT_FDCWD, path)?; + pub fn add_path( + &self, + mode: MarkMode, + path: &P, + ) -> Result<(), Error> { + fanotify_mark(self.fd, FAN_MARK_ADD, mode.bits(), AT_FDCWD, path)?; Ok(()) } pub fn add_mountpoint( &self, - mode: u64, + mode: MarkMode, path: &P, ) -> Result<(), Error> { - fanotify_mark(self.fd, FAN_MARK_ADD | FAN_MARK_MOUNT, mode, AT_FDCWD, path)?; + fanotify_mark( + self.fd, + FAN_MARK_ADD | FAN_MARK_MOUNT, + mode.bits(), + AT_FDCWD, + path, + )?; Ok(()) } - pub fn remove_path(&self, mode: u64, path: &P) -> Result<(), Error> { - fanotify_mark(self.fd, FAN_MARK_REMOVE, mode, AT_FDCWD, path)?; + pub fn add_filesystem( + &self, + mode: MarkMode, + path: &P, + ) -> Result<(), Error> { + fanotify_mark( + self.fd, + FAN_MARK_ADD | FAN_MARK_FILESYSTEM, + mode.bits(), + AT_FDCWD, + path, + )?; Ok(()) } - pub fn flush_path(&self, mode: u64, path: &P) -> Result<(), Error> { - fanotify_mark(self.fd, FAN_MARK_FLUSH, mode, AT_FDCWD, path)?; + pub fn remove_path( + &self, + mode: MarkMode, + path: &P, + ) -> Result<(), Error> { + fanotify_mark(self.fd, FAN_MARK_REMOVE, mode.bits(), AT_FDCWD, path)?; + Ok(()) + } + + pub fn flush_path( + &self, + mode: MarkMode, + path: &P, + ) -> Result<(), Error> { + fanotify_mark(self.fd, FAN_MARK_FLUSH, mode.bits(), AT_FDCWD, path)?; Ok(()) } pub fn read_event(&self) -> std::io::Result> { let mut result = Vec::new(); let events = fanotify_read(self.fd)?; - for metadata in events { + for (metadata, additional_records) in events { let path = read_link(format!("/proc/self/fd/{}", metadata.fd)).unwrap_or_default(); let path = path.to_str().unwrap(); result.push(Event { @@ -228,6 +309,7 @@ impl Fanotify { path: String::from(path), events: events_from_mask(metadata.mask), pid: metadata.pid, + additional_records, }); } Ok(result) @@ -292,16 +374,16 @@ impl FanotifyBuilder { Self { class, ..self } } - pub fn with_flags(self, flags: u32) -> Self { + pub fn with_flags(self, flags: Flags) -> Self { Self { - flags: FAN_CLOEXEC | flags, + flags: FAN_CLOEXEC | flags.bits(), ..self } } - pub fn with_event_flags(self, event_flags: u32) -> Self { + pub fn with_event_flags(self, event_flags: EventFlags) -> Self { Self { - event_flags, + event_flags: event_flags.bits(), ..self } } @@ -312,3 +394,9 @@ impl FanotifyBuilder { }) } } + +impl Default for FanotifyBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/src/lib.rs b/src/lib.rs index 821cfb2..305b173 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "high-level")] pub mod high_level; pub mod low_level; @@ -21,4 +22,4 @@ impl> FanotifyPath for T { fn as_os_str(&self) -> &std::ffi::OsStr { self.as_ref() } -} \ No newline at end of file +} diff --git a/src/low_level.rs b/src/low_level.rs index 730c096..2197f30 100644 --- a/src/low_level.rs +++ b/src/low_level.rs @@ -1,9 +1,9 @@ use crate::FanotifyPath; use libc::{__s32, __u8, __u16, __u32, __u64}; +use std::ffi::{CStr, CString}; use std::io::{Error, ErrorKind}; use std::mem; use std::os::unix::ffi::OsStrExt; -use std::slice; #[doc(hidden)] /// Re-export relevant libc constants @@ -50,6 +50,255 @@ pub struct FanotifyEventMetadata { pub pid: __s32, } +impl TryFrom<&[u8]> for FanotifyEventMetadata { + type Error = std::io::Error; + fn try_from(slice: &[u8]) -> Result { + if slice.len() < size_of::() { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain a FanotifyEventMetadata", + )); + } + + // SAFETY: we know that all bit paterns of `FanotifyEventMetadata` are the valid, + // and there is enough space in the buffer to contain the `FanotifyEventMetadata` + // so a memory cast and read is safe. + Ok(unsafe { slice.as_ptr().cast::().read_unaligned() }) + } +} + +/// The different types of fanotify information records that can be returned with an event. +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum FanoityEnventInfoType { + FID = 1, + DFIDName = 2, + DFID = 3, + PIDFD = 4, + Error = 5, +} + +/// The header indentifying what type of information event was generated by the fanotify subsystem, and the size of the struct (for the `fid` DST). +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FanotifyEventInfoHeader { + /// The type of information event that was generated by the fanotify subsystem. + info_type: FanoityEnventInfoType, + /// Padding to align the u16 length field. + _pad: __u8, + /// The size of the additional information record, including this header. + len: __u16, +} + +/// Container class to encapsulate the different types of fanotify information records that can be returned with an event +#[derive(Debug, Clone)] +#[repr(C)] +pub enum FanAdditionalRecords { + FanotifyEventInfoFID(FanotifyEventInfoFID), + FanotifyEventInfoDFIDName { + fid: FanotifyEventInfoFID, + name: CString, + }, + FanotifyEventInfoPIDFD(FanotifyEventInfoPIDFD), + FanotifyEventInfoError(FanotifyEventInfoError), +} + +impl FanAdditionalRecords { + pub fn size(&self) -> usize { + match self { + Self::FanotifyEventInfoFID(e) => e.header.len as usize, + Self::FanotifyEventInfoDFIDName { fid, name } => { + fid.header.len as usize + name.as_bytes_with_nul().len() + } + Self::FanotifyEventInfoPIDFD(e) => e.header.len as usize, + Self::FanotifyEventInfoError(e) => e.header.len as usize, + } + } + + pub fn from_slice(slice: &[u8]) -> std::io::Result<(Self, usize)> { + if slice.len() < size_of::() { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain an event info header record", + )); + }; + // SAFETY: We definitely have enough bytes so we can safely read. + match unsafe { *(slice.as_ptr().cast()) } { + FanoityEnventInfoType::FID | FanoityEnventInfoType::DFID => { + let (fid, size) = FanotifyEventInfoFID::from_slice(slice)?; + Ok((Self::FanotifyEventInfoFID(fid), size)) + } + FanoityEnventInfoType::DFIDName => { + let (fid, size) = FanotifyEventInfoFID::from_slice(slice)?; + if slice.len() < size { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain an the expected DFIDName record", + )); + } + // SAFETY: We definitely have enough bytes so we can safely read. + let slice = &slice[size..]; + let name: CString = unsafe { CStr::from_ptr(slice.as_ptr() as _) }.to_owned(); + let consumed_bytes = size + name.as_bytes_with_nul().len(); + Ok(( + Self::FanotifyEventInfoDFIDName { fid, name }, + consumed_bytes, + )) + } + FanoityEnventInfoType::PIDFD => Ok(( + Self::FanotifyEventInfoPIDFD(unsafe { + slice + .as_ptr() + .cast::() + .read_unaligned() + }), + size_of::(), + )), + FanoityEnventInfoType::Error => Ok(( + Self::FanotifyEventInfoError(unsafe { + slice + .as_ptr() + .cast::() + .read_unaligned() + }), + size_of::(), + )), + } + } +} + +/// Reporting event for File System Errors when `FAN_REPORT_FSERROR` is reported in the event mask +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FanotifyEventInfoError { + pub header: FanotifyEventInfoHeader, + pub error_code: __s32, + pub error_count: __u32, +} + +/// Additional information record containing a file descriptor for the target process. +#[derive(Debug)] +#[repr(C)] +pub struct FanotifyEventInfoPIDFD { + pub header: FanotifyEventInfoHeader, + pub pidfd: __s32, +} + +impl Clone for FanotifyEventInfoPIDFD { + fn clone(&self) -> Self { + unsafe { + Self { + header: self.header, + pidfd: libc::dup(self.pidfd), + } + } + } +} + +impl Drop for FanotifyEventInfoPIDFD { + fn drop(&mut self) { + unsafe { + libc::close(self.pidfd); + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct OpaqueFileHandle { + handle_bytes: u32, + handle_type: i32, + file_handle: Vec, +} + +impl OpaqueFileHandle { + fn from_slice(mut slice: &[u8]) -> Result<(Self, usize), Error> { + if slice.len() < 8 { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain a file handle", + )); + } + + let handle_bytes = u32::from_ne_bytes( + slice[..4] + .try_into() + .expect("We already checked the length, so this should never fail."), + ); + let min_size = handle_bytes as usize + size_of::() + size_of::(); + if slice.len() < min_size { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + format!( + "The buffer is too small to contain a file handle with the declared length {}", + handle_bytes + ), + )); + } + slice = &slice[4..]; + + let handle_type = i32::from_ne_bytes( + slice[..4] + .try_into() + .expect("We already checked the length, so this should never fail."), + ); + slice = &slice[4..]; + let file_handle = Vec::from(&slice[..handle_bytes as usize]); + + Ok(( + Self { + handle_bytes, + handle_type, + file_handle, + }, + handle_bytes as usize + size_of::() + size_of::(), + )) + } +} + +pub type KernelFSID = [libc::c_int; 2]; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FanotifyEventInfoFID { + pub header: FanotifyEventInfoHeader, + pub fsid: KernelFSID, + handle: OpaqueFileHandle, +} + +impl FanotifyEventInfoFID { + fn from_slice(slice: &[u8]) -> Result<(Self, usize), Error> { + if slice.len() < size_of::() + size_of::() { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain a FID record", + )); + } + + let ptr = slice.as_ptr().cast::(); + // SAFETY: We have already checked that there are enough bytes in the buffer + let (header, fsid) = unsafe { ((*ptr).header, (*ptr).fsid) }; + + if slice.len() < header.len as usize { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "The buffer is too small to contain the full FID record", + )); + } + + let (handle, handle_size) = OpaqueFileHandle::from_slice(&slice[12..])?; + + Ok(( + Self { + header, + fsid, + handle, + }, + size_of::() + size_of::() + handle_size, + )) + } +} + #[derive(Debug)] #[repr(C)] /// It is used to control file access. @@ -350,35 +599,49 @@ pub fn fanotify_mark( } } -pub fn fanotify_read(fanotify_fd: i32) -> std::io::Result> { - let mut vec = Vec::new(); - let mut buffer = Box::new([0u8; FAN_EVENT_METADATA_LEN * 200]); - unsafe { - // Allocate a buffer to store up to 200 events - - let sizeof = libc::read( +pub fn fanotify_read( + fanotify_fd: i32, +) -> std::io::Result)>> { + let mut events = Vec::new(); + let mut read_buffer = Box::new([0u8; FAN_EVENT_METADATA_LEN * 200]); + let read_len = unsafe { + libc::read( fanotify_fd, - buffer.as_mut_ptr() as _, + read_buffer.as_mut_ptr() as _, FAN_EVENT_METADATA_LEN * 200, - ); + ) + }; - // Get the Error code fromt the OS, as we don't want to bubble up a `WouldBlock` in our API - if sizeof == -1 - && let errno = std::io::Error::last_os_error() - && !matches!(errno.kind(), ErrorKind::WouldBlock) - { - return Err(errno); - } + if read_len == -1 + && let errno = std::io::Error::last_os_error() + && !matches!(errno.kind(), ErrorKind::WouldBlock) + { + return Err(errno); + } - if sizeof > 0 { - let src = slice::from_raw_parts( - buffer.as_ptr().cast::(), - sizeof as usize / FAN_EVENT_METADATA_LEN, - ); - vec.extend_from_slice(src); + let mut cursor = &read_buffer[..read_len as usize]; + while let Ok(event_metadata) = FanotifyEventMetadata::try_from(cursor) { + let mut extra_records = Vec::new(); + if event_metadata.event_len as usize > FAN_EVENT_METADATA_LEN { + // we have optional events, allocate a new vector to hold the additional info + let mut inner_cursor = + &cursor[FAN_EVENT_METADATA_LEN..event_metadata.event_len as usize]; + while inner_cursor.len() >= size_of::() { + // TODO: handle the path CStr sent after the info record for a `FAN_EVENT_INFO_TYPE_DFID_NAME` + if let Ok((additional_info, slice_consumed)) = + FanAdditionalRecords::from_slice(inner_cursor) + { + inner_cursor = &inner_cursor[slice_consumed..]; + extra_records.push(additional_info); + } else { + break; // this only happens when there wansn't enough space to read the events that should have been there, so we just stop here. + } + } } + events.push((event_metadata, extra_records)); + cursor = &cursor[event_metadata.event_len as usize..]; } - Ok(vec) + Ok(events) } pub fn close_fd(fd: i32) { diff --git a/tests/high_level.rs b/tests/high_level.rs index e1febe5..274e170 100644 --- a/tests/high_level.rs +++ b/tests/high_level.rs @@ -1,16 +1,20 @@ #[test] fn high_level_test() { - use fanotify::high_level::{ - Fanotify, FanotifyMode, FAN_ACCESS, FAN_CLOSE, FAN_EVENT_ON_CHILD, FAN_MODIFY, FAN_ONDIR, - FAN_OPEN, - }; + use fanotify::high_level::MarkMode; + use fanotify::high_level::{Fanotify, FanotifyMode}; use std::io::{Read, Write}; - let ft = Fanotify::new_blocking(FanotifyMode::NOTIF).expect("Error regitering fanotify listener"); + let ft = + Fanotify::new_blocking(FanotifyMode::NOTIF).expect("Error registering fanotify listener"); ft.add_path( - FAN_ACCESS | FAN_CLOSE | FAN_EVENT_ON_CHILD | FAN_MODIFY | FAN_ONDIR | FAN_OPEN, + MarkMode::Access + | MarkMode::Close + | MarkMode::EventOnChild + | MarkMode::Modify + | MarkMode::OnDirectory + | MarkMode::Open, "/tmp", ) - .unwrap(); + .unwrap(); let handler = std::thread::spawn(|| { let mut tmp = std::fs::File::create("/tmp/fanotify_test").unwrap(); tmp.write_all(b"xxx").unwrap(); @@ -20,4 +24,4 @@ fn high_level_test() { assert_eq!(res, "xxx".to_string()); }); handler.join().unwrap(); -} \ No newline at end of file +}