Skip to content

Commit 126db91

Browse files
committed
Added information record support to fanotify
1 parent 33efb1a commit 126db91

File tree

2 files changed

+237
-2
lines changed

2 files changed

+237
-2
lines changed

src/sys/fanotify.rs

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,46 @@ libc_bitflags! {
118118
FAN_REPORT_PIDFD;
119119
/// Make `FanotifyEvent::pid` return thread id. Since Linux 4.20.
120120
FAN_REPORT_TID;
121+
122+
/// Allows the receipt of events which contain additional information
123+
/// about the underlying filesystem object correlated to an event.
124+
///
125+
/// This will make `FanotifyEvent::fd` return `FAN_NOFD`.
126+
/// This should be used with `Fanotify::read_events_with_info_records` to
127+
/// recieve `FanotifyInfoRecord::Fid` info records.
128+
/// Since Linux 5.1
129+
FAN_REPORT_FID;
130+
131+
/// Allows the receipt of events which contain additional information
132+
/// about the underlying filesystem object correlated to an event.
133+
///
134+
/// This will make `FanotifyEvent::fd` return `FAN_NOFD`.
135+
/// This should be used with `Fanotify::read_events_with_info_records` to
136+
/// recieve `FanotifyInfoRecord::Fid` info records.
137+
///
138+
/// An additional event of `FAN_EVENT_INFO_TYPE_DFID` will also be received,
139+
/// encapsulating information about the target directory (or parent directory of a file)
140+
/// Since Linux 5.9
141+
FAN_REPORT_DIR_FID;
142+
143+
/// Events for fanotify groups initialized with this flag will contain additional
144+
/// information about the child correlated with directory entry modification events.
145+
/// This flag must be provided in conjunction with the flags `FAN_REPORT_FID`,
146+
/// `FAN_REPORT_DIR_FID` and `FAN_REPORT_NAME`.
147+
/// Since Linux 5.17
148+
FAN_REPORT_TARGET_FID;
149+
150+
/// Events for fanotify groups initialized with this flag will contain additional
151+
/// information about the name of the directory entry correlated to an event. This
152+
/// flag must be provided in conjunction with the flag `FAN_REPORT_DIR_FID`.
153+
/// Since Linux 5.9
154+
FAN_REPORT_NAME;
155+
156+
/// This is a synonym for `FAN_REPORT_DIR_FD | FAN_REPORT_NAME`.
157+
FAN_REPORT_DFID_NAME;
158+
159+
/// This is a synonym for `FAN_REPORT_DIR_FD | FAN_REPORT_NAME | FAN_REPORT_TARGET_FID`.
160+
FAN_REPORT_DFID_NAME_TARGET;
121161
}
122162
}
123163

@@ -206,6 +246,33 @@ pub const FANOTIFY_METADATA_VERSION: u8 = libc::FANOTIFY_METADATA_VERSION;
206246
#[allow(missing_copy_implementations)]
207247
pub struct FanotifyEvent(libc::fanotify_event_metadata);
208248

249+
/// After a [`libc::fanotify_event_metadata`], there can be 0 or more event_info
250+
/// structs depending on which InitFlags were used in [`Fanotify::init`].
251+
// Is not Clone due to pidfd in `libc::fanotify_event_info_pidfd`
252+
// Other fanotify_event_info records are not implemented as they don't exist in
253+
// the libc crate yet.
254+
#[derive(Debug, Eq, Hash, PartialEq)]
255+
#[allow(missing_copy_implementations)]
256+
pub enum FanotifyInfoRecord {
257+
/// A [`libc::fanotify_event_info_fid`] event was recieved, usually as
258+
/// a result of passing [`InitFlags::FAN_REPORT_FID`] or [`InitFlags::FAN_REPORT_DIR_FID`]
259+
/// into [`Fanotify::init`]. The containing struct includes a `file_handle` for
260+
/// use with `open_by_handle_at(2)`.
261+
Fid(libc::fanotify_event_info_fid),
262+
263+
/// A [`libc::FAN_FS_ERROR`] event was received. This event occurs when
264+
/// a filesystem event is detected. Only a single [`libc::FAN_FS_ERROR`] is
265+
/// stored per filesystem at once, extra error messages are suppressed and
266+
/// accounted for in the error_count field.
267+
Error(libc::fanotify_event_info_error),
268+
269+
/// A [`libc::fanotify_event_info_pidfd`] event was recieved, usually as
270+
/// a result of passing [`InitFlags::FAN_REPORT_PIDFD`] into [`Fanotify::init`].
271+
/// The containing struct includes a `pidfd` for reliably determining
272+
/// whether the process responsible for generating an event has been recycled or terminated
273+
Pidfd(libc::fanotify_event_info_pidfd),
274+
}
275+
209276
impl FanotifyEvent {
210277
/// Version number for the structure. It must be compared to
211278
/// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime
@@ -341,6 +408,21 @@ impl Fanotify {
341408
Errno::result(res).map(|_| ())
342409
}
343410

411+
fn get_struct<T>(&self, buffer: &[u8; 4096], offset: usize) -> T {
412+
let struct_size = size_of::<T>();
413+
let struct_obj = unsafe {
414+
let mut struct_obj = MaybeUninit::<T>::uninit();
415+
std::ptr::copy_nonoverlapping(
416+
buffer.as_ptr().add(offset),
417+
struct_obj.as_mut_ptr().cast(),
418+
(4096 - offset).min(struct_size),
419+
);
420+
struct_obj.assume_init()
421+
};
422+
423+
struct_obj
424+
}
425+
344426
/// Read incoming events from the fanotify group.
345427
///
346428
/// Returns a Result containing either a `Vec` of events on success or errno
@@ -382,6 +464,99 @@ impl Fanotify {
382464
Ok(events)
383465
}
384466

467+
/// Read incoming events and information records from the fanotify group.
468+
///
469+
/// Returns a Result containing either a `Vec` of events and information records on success or errno
470+
/// otherwise.
471+
///
472+
/// # Errors
473+
///
474+
/// Possible errors can be those that are explicitly listed in
475+
/// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in
476+
/// addition to the possible errors caused by `read` call.
477+
/// In particular, `EAGAIN` is returned when no event is available on a
478+
/// group that has been initialized with the flag `InitFlags::FAN_NONBLOCK`,
479+
/// thus making this method nonblocking.
480+
pub fn read_events_with_info_records(
481+
&self,
482+
) -> Result<Vec<(FanotifyEvent, Vec<FanotifyInfoRecord>)>> {
483+
let metadata_size = size_of::<libc::fanotify_event_metadata>();
484+
const BUFSIZ: usize = 4096;
485+
let mut buffer = [0u8; BUFSIZ];
486+
let mut events = Vec::new();
487+
let mut offset = 0;
488+
489+
let nread = read(&self.fd, &mut buffer)?;
490+
491+
while (nread - offset) >= metadata_size {
492+
let metadata = unsafe {
493+
let mut metadata =
494+
MaybeUninit::<libc::fanotify_event_metadata>::uninit();
495+
std::ptr::copy_nonoverlapping(
496+
buffer.as_ptr().add(offset),
497+
metadata.as_mut_ptr().cast(),
498+
(BUFSIZ - offset).min(metadata_size),
499+
);
500+
metadata.assume_init()
501+
};
502+
503+
let mut remaining_len = metadata.event_len;
504+
let mut info_records = Vec::new();
505+
let mut current_event_offset = offset + metadata_size;
506+
507+
while remaining_len > 0 {
508+
let header = self
509+
.get_struct::<libc::fanotify_event_info_header>(
510+
&buffer,
511+
current_event_offset,
512+
);
513+
514+
let info_record = match header.info_type {
515+
libc::FAN_EVENT_INFO_TYPE_FID => {
516+
let event_fid = self
517+
.get_struct::<libc::fanotify_event_info_fid>(
518+
&buffer,
519+
current_event_offset,
520+
);
521+
Some(FanotifyInfoRecord::Fid(event_fid))
522+
}
523+
libc::FAN_EVENT_INFO_TYPE_ERROR => {
524+
let error_fid = self
525+
.get_struct::<libc::fanotify_event_info_error>(
526+
&buffer,
527+
current_event_offset,
528+
);
529+
Some(FanotifyInfoRecord::Error(error_fid))
530+
}
531+
libc::FAN_EVENT_INFO_TYPE_PIDFD => {
532+
let error_fid = self
533+
.get_struct::<libc::fanotify_event_info_pidfd>(
534+
&buffer,
535+
current_event_offset,
536+
);
537+
Some(FanotifyInfoRecord::Pidfd(error_fid))
538+
}
539+
// Ignore unsupported events
540+
_ => None,
541+
};
542+
543+
if let Some(record) = info_record {
544+
info_records.push(record);
545+
}
546+
547+
remaining_len -= header.len as u32;
548+
current_event_offset += header.len as usize;
549+
}
550+
551+
// libc::fanotify_event_info_header
552+
553+
events.push((FanotifyEvent(metadata), info_records));
554+
offset += metadata.event_len as usize;
555+
}
556+
557+
Ok(events)
558+
}
559+
385560
/// Write an event response on the fanotify group.
386561
///
387562
/// Returns a Result containing either `()` on success or errno otherwise.
@@ -443,4 +618,4 @@ impl Fanotify {
443618
fd
444619
}
445620
}
446-
}
621+
}

test/sys/test_fanotify.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use nix::errno::Errno;
33
use nix::fcntl::AT_FDCWD;
44
use nix::sys::fanotify::{
55
EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags,
6-
Response,
6+
Response, FanotifyInfoRecord
77
};
88
use std::fs::{read_link, read_to_string, File, OpenOptions};
99
use std::io::ErrorKind;
@@ -18,6 +18,7 @@ pub fn test_fanotify() {
1818

1919
test_fanotify_notifications();
2020
test_fanotify_responses();
21+
test_fanotify_notifications_with_info_records();
2122
test_fanotify_overflow();
2223
}
2324

@@ -84,6 +85,65 @@ fn test_fanotify_notifications() {
8485
assert_eq!(path, tempfile);
8586
}
8687

88+
fn test_fanotify_notifications_with_info_records() {
89+
let group =
90+
Fanotify::init(InitFlags::FAN_CLASS_NOTIF | InitFlags::FAN_REPORT_FID, EventFFlags::O_RDONLY)
91+
.unwrap();
92+
let tempdir = tempfile::tempdir().unwrap();
93+
let tempfile = tempdir.path().join("test");
94+
OpenOptions::new()
95+
.write(true)
96+
.create_new(true)
97+
.open(&tempfile)
98+
.unwrap();
99+
100+
group
101+
.mark(
102+
MarkFlags::FAN_MARK_ADD,
103+
MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE,
104+
AT_FDCWD,
105+
Some(&tempfile),
106+
)
107+
.unwrap();
108+
109+
// modify test file
110+
{
111+
let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap();
112+
f.write_all(b"hello").unwrap();
113+
}
114+
115+
let mut events = group.read_events_with_info_records().unwrap();
116+
assert_eq!(events.len(), 1, "should have read exactly one event");
117+
let (event, info_records) = events.pop().unwrap();
118+
assert_eq!(info_records.len(), 1, "should have read exactly one info record");
119+
assert!(event.check_version());
120+
assert_eq!(
121+
event.mask(),
122+
MaskFlags::FAN_OPEN
123+
| MaskFlags::FAN_MODIFY
124+
| MaskFlags::FAN_CLOSE_WRITE
125+
);
126+
127+
assert!(matches!(info_records[0], FanotifyInfoRecord::Fid { .. }), "info record should be an fid record");
128+
129+
// read test file
130+
{
131+
let mut f = File::open(&tempfile).unwrap();
132+
let mut s = String::new();
133+
f.read_to_string(&mut s).unwrap();
134+
}
135+
136+
let mut events = group.read_events_with_info_records().unwrap();
137+
assert_eq!(events.len(), 1, "should have read exactly one event");
138+
let (event, info_records) = events.pop().unwrap();
139+
assert_eq!(info_records.len(), 1, "should have read exactly one info record");
140+
assert!(event.check_version());
141+
assert_eq!(
142+
event.mask(),
143+
MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE
144+
);
145+
}
146+
87147
fn test_fanotify_responses() {
88148
let group =
89149
Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY)

0 commit comments

Comments
 (0)