Skip to content

Commit 63acd98

Browse files
committed
Add fanotify API wrappers
1 parent 6bacfe0 commit 63acd98

File tree

7 files changed

+527
-0
lines changed

7 files changed

+527
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ aio = ["pin-utils"]
4242
dir = ["fs"]
4343
env = []
4444
event = []
45+
fanotify = []
4546
feature = []
4647
fs = []
4748
hostname = []

changelog/2194.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added new fanotify API: wrappers for `fanotify_init` and `fanotify_mark`

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! * `dir` - Stuff relating to directory iteration
1313
//! * `env` - Manipulate environment variables
1414
//! * `event` - Event-driven APIs, like `kqueue` and `epoll`
15+
//! * `fanotify` - Linux's `fanotify` filesystem events monitoring API
1516
//! * `feature` - Query characteristics of the OS at runtime
1617
//! * `fs` - File system functionality
1718
//! * `hostname` - Get and set the system's hostname
@@ -53,6 +54,7 @@
5354
feature = "dir",
5455
feature = "env",
5556
feature = "event",
57+
feature = "fanotify",
5658
feature = "feature",
5759
feature = "fs",
5860
feature = "hostname",

src/sys/fanotify.rs

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
//! Monitoring API for filesystem events.
2+
//!
3+
//! Fanotify is a Linux-only API to monitor filesystems events.
4+
//!
5+
//! Additional capabilities compared to the `inotify` API include the ability to
6+
//! monitor all of the objects in a mounted filesystem, the ability to make
7+
//! access permission decisions, and the possibility to read or modify files
8+
//! before access by other applications.
9+
//!
10+
//! For more documentation, please read
11+
//! [fanotify(7)](https://man7.org/linux/man-pages/man7/fanotify.7.html).
12+
13+
use crate::{NixPath, Result};
14+
use crate::errno::Errno;
15+
use crate::unistd::{read, write};
16+
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
17+
use std::mem::{MaybeUninit, size_of};
18+
use std::ptr;
19+
20+
libc_bitflags! {
21+
/// Mask for defining which events shall be listened with
22+
/// [`fanotify_mark`](fn.fanotify_mark.html) and for querying notifications.
23+
pub struct MaskFlags: u64 {
24+
/// File was accessed.
25+
FAN_ACCESS;
26+
/// File was modified.
27+
FAN_MODIFY;
28+
/// Metadata changed. Since Linux 5.1.
29+
FAN_ATTRIB;
30+
/// Writtable file closed.
31+
FAN_CLOSE_WRITE;
32+
/// Unwrittable file closed.
33+
FAN_CLOSE_NOWRITE;
34+
/// File was opened.
35+
FAN_OPEN;
36+
/// File was moved from X. Since Linux 5.1.
37+
FAN_MOVED_FROM;
38+
/// File was moved to Y. Since Linux 5.1.
39+
FAN_MOVED_TO;
40+
/// Subfile was created. Since Linux 5.1.
41+
FAN_CREATE;
42+
/// Subfile was deleted. Since Linux 5.1.
43+
FAN_DELETE;
44+
/// Self was deleted. Since Linux 5.1.
45+
FAN_DELETE_SELF;
46+
/// Self was moved. Since Linux 5.1.
47+
FAN_MOVE_SELF;
48+
/// File was opened for exec. Since Linux 5.0.
49+
FAN_OPEN_EXEC;
50+
51+
/// Event queued overflowed.
52+
FAN_Q_OVERFLOW;
53+
/// Filesystem error. Since Linux 5.16.
54+
FAN_FS_ERROR;
55+
56+
/// File open in perm check.
57+
FAN_OPEN_PERM;
58+
/// File accessed in perm check.
59+
FAN_ACCESS_PERM;
60+
/// File open/exec in perm check. Since Linux 5.0.
61+
FAN_OPEN_EXEC_PERM;
62+
63+
/// Interested in child events.
64+
FAN_EVENT_ON_CHILD;
65+
66+
/// File was renamed. Since Linux 5.17.
67+
FAN_RENAME;
68+
69+
/// Event occured against dir.
70+
FAN_ONDIR;
71+
72+
/// Combination of `FAN_CLOSE_WRITE` and `FAN_CLOSE_NOWRITE`.
73+
FAN_CLOSE;
74+
/// Combination of `FAN_MOVED_FROM` and `FAN_MOVED_TO`.
75+
FAN_MOVE;
76+
}
77+
}
78+
79+
libc_bitflags! {
80+
/// Configuration options for [`fanotify_init`](fn.fanotify_init.html).
81+
pub struct InitFlags: libc::c_uint {
82+
/// Close-on-exec flag set on the file descriptor.
83+
FAN_CLOEXEC;
84+
/// Nonblocking flag set on the file descriptor.
85+
FAN_NONBLOCK;
86+
87+
/// Receipt of events notifications.
88+
FAN_CLASS_NOTIF;
89+
/// Receipt of events for permission decisions, after they contain final
90+
/// data.
91+
FAN_CLASS_CONTENT;
92+
/// Receipt of events for permission decisions, before they contain
93+
/// final data.
94+
FAN_CLASS_PRE_CONTENT;
95+
96+
/// Remove the limit of 16384 events for the event queue.
97+
FAN_UNLIMITED_QUEUE;
98+
/// Remove the limit of 8192 marks.
99+
FAN_UNLIMITED_MARKS;
100+
}
101+
}
102+
103+
libc_bitflags! {
104+
/// File status flags for fanotify events file descriptors.
105+
pub struct OFlags: libc::c_uint {
106+
/// Read only access.
107+
O_RDONLY as libc::c_uint;
108+
/// Write only access.
109+
O_WRONLY as libc::c_uint;
110+
/// Read and write access.
111+
O_RDWR as libc::c_uint;
112+
/// Support for files exceeded 2 GB.
113+
O_LARGEFILE as libc::c_uint;
114+
/// Close-on-exec flag for the file descriptor. Since Linux 3.18.
115+
O_CLOEXEC as libc::c_uint;
116+
/// Append mode for the file descriptor.
117+
O_APPEND as libc::c_uint;
118+
/// Synchronized I/O data integrity completion.
119+
O_DSYNC as libc::c_uint;
120+
/// No file last access time update.
121+
O_NOATIME as libc::c_uint;
122+
/// Nonblocking mode for the file descriptor.
123+
O_NONBLOCK as libc::c_uint;
124+
/// Synchronized I/O file integrity completion.
125+
O_SYNC as libc::c_uint;
126+
}
127+
}
128+
129+
libc_bitflags! {
130+
/// Configuration options for [`fanotify_mark`](fn.fanotify_mark.html).
131+
pub struct MarkFlags: libc::c_uint {
132+
/// Add the events to the marks.
133+
FAN_MARK_ADD;
134+
/// Remove the events to the marks.
135+
FAN_MARK_REMOVE;
136+
/// Don't follow symlinks, mark them.
137+
FAN_MARK_DONT_FOLLOW;
138+
/// Raise an error if filesystem to be marked is not a directory.
139+
FAN_MARK_ONLYDIR;
140+
/// Events added to or removed from the marks.
141+
FAN_MARK_IGNORED_MASK;
142+
/// Ignore mask shall survive modify events.
143+
FAN_MARK_IGNORED_SURV_MODIFY;
144+
/// Remove all marks.
145+
FAN_MARK_FLUSH;
146+
/// Do not pin inode object in the inode cache. Since Linux 5.19.
147+
FAN_MARK_EVICTABLE;
148+
/// Events added to or removed from the marks. Since Linux 6.0.
149+
FAN_MARK_IGNORE;
150+
151+
/// Default flag.
152+
FAN_MARK_INODE;
153+
/// Mark the mount specified by pathname.
154+
FAN_MARK_MOUNT;
155+
/// Mark the filesystem specified by pathname. Since Linux 4.20.
156+
FAN_MARK_FILESYSTEM;
157+
158+
/// Combination of `FAN_MARK_IGNORE` and `FAN_MARK_IGNORED_SURV_MODIFY`.
159+
FAN_MARK_IGNORE_SURV;
160+
}
161+
}
162+
163+
/// Compile version number of fanotify API.
164+
pub const FANOTIFY_METADATA_VERSION: u8 = libc::FANOTIFY_METADATA_VERSION;
165+
166+
#[derive(Debug)]
167+
/// Abstract over `libc::fanotify_event_metadata`, which represents an event
168+
/// received via `Fanotify::read_events`.
169+
pub struct FanotifyEvent {
170+
/// Version number for the structure. It must be compared to
171+
/// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime
172+
/// version does match. It can be done with the
173+
/// `FanotifyEvent::has_compile_version` method.
174+
pub version: u8,
175+
/// Mask flags of the events.
176+
pub mask: MaskFlags,
177+
/// The file descriptor of the event. If the value is `None` when reading
178+
/// from the fanotify group, this event is to notify that a group queue
179+
/// overflow occured.
180+
pub fd: Option<OwnedFd>,
181+
/// PID of the process that caused the event. TID in case flag
182+
/// `FAN_REPORT_TID` was set at group initialization.
183+
pub pid: i32,
184+
}
185+
186+
impl FanotifyEvent {
187+
/// Checks that compile fanotify API version is equal to the version of the
188+
/// event.
189+
pub fn has_compile_version(&self) -> bool {
190+
self.version == FANOTIFY_METADATA_VERSION
191+
}
192+
}
193+
194+
#[derive(Debug)]
195+
/// Abstraction over the structure to be sent to allow or deny a given event.
196+
pub struct FanotifyResponse<'e> {
197+
/// A borrow of the file descriptor from the structure `FanotifyEvent`.
198+
pub fd: BorrowedFd<'e>,
199+
/// Indication whether or not the permission is to be granted.
200+
pub response: Response,
201+
}
202+
203+
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
204+
/// Response to be wrapped in `FanotifyResponse` and sent to the `Fanotify`
205+
/// group to allow or deny an event.
206+
pub enum Response {
207+
/// Allow the event.
208+
Allow,
209+
/// Deny the event.
210+
Deny,
211+
}
212+
213+
/// A fanotify group. This is also a file descriptor that can feed to other
214+
/// interfaces consuming file descriptors.
215+
#[derive(Debug)]
216+
pub struct Fanotify {
217+
fd: OwnedFd,
218+
}
219+
220+
impl Fanotify {
221+
/// Initialize a new fanotify group.
222+
///
223+
/// Returns a Result containing a Fanotify instance.
224+
///
225+
/// For more information, see [fanotify_init(2)](https://man7.org/linux/man-pages/man7/fanotify_init.2.html).
226+
pub fn init(flags: InitFlags, event_f_flags: OFlags) -> Result<Fanotify> {
227+
let res = Errno::result(unsafe {
228+
libc::fanotify_init(flags.bits(), event_f_flags.bits())
229+
});
230+
res.map(|fd| Fanotify { fd: unsafe { OwnedFd::from_raw_fd(fd) }})
231+
}
232+
233+
/// Add, remove, or modify an fanotify mark on a filesystem object.
234+
/// If `dirfd` is `None`, `AT_FDCWD` is used.
235+
///
236+
/// Returns a Result containing either `()` on success or errno otherwise.
237+
///
238+
/// For more information, see [fanotify_mark(2)](https://man7.org/linux/man-pages/man7/fanotify_mark.2.html).
239+
pub fn mark<P: ?Sized + NixPath>(
240+
&self,
241+
flags: MarkFlags,
242+
mask: MaskFlags,
243+
dirfd: Option<RawFd>,
244+
path: Option<&P>,
245+
) -> Result<()> {
246+
fn with_opt_nix_path<P, T, F>(p: Option<&P>, f: F) -> Result<T>
247+
where
248+
P: ?Sized + NixPath,
249+
F: FnOnce(*const libc::c_char) -> T,
250+
{
251+
match p {
252+
Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
253+
None => Ok(f(std::ptr::null())),
254+
}
255+
}
256+
257+
let res = with_opt_nix_path(path, |p| unsafe {
258+
libc::fanotify_mark(
259+
self.fd.as_raw_fd(),
260+
flags.bits(),
261+
mask.bits(),
262+
dirfd.unwrap_or(libc::AT_FDCWD),
263+
p,
264+
)
265+
})?;
266+
267+
Errno::result(res).map(|_| ())
268+
}
269+
270+
/// Read incoming events from the fanotify group.
271+
///
272+
/// Returns a Result containing either a `Vec` of events on success or errno
273+
/// otherwise.
274+
///
275+
/// # Errors
276+
///
277+
/// Possible errors can be those that are explicitly listed in
278+
/// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in
279+
/// addition to the possible errors caused by `read` call.
280+
/// In particular, `EAGAIN` is returned when no event is available on a
281+
/// group that has been initialized with the flag `InitFlags::FAN_NONBLOCK`,
282+
/// thus making this method nonblocking.
283+
pub fn read_events(&self) -> Result<Vec<FanotifyEvent>> {
284+
let metadata_size = size_of::<libc::fanotify_event_metadata>();
285+
const BUFSIZ: usize = 4096;
286+
let mut buffer = [0u8; BUFSIZ];
287+
let mut events = Vec::new();
288+
let mut offset = 0;
289+
290+
let nread = read(self.fd.as_raw_fd(), &mut buffer)?;
291+
292+
while (nread - offset) >= metadata_size {
293+
let metadata = unsafe {
294+
let mut metadata =
295+
MaybeUninit::<libc::fanotify_event_metadata>::uninit();
296+
ptr::copy_nonoverlapping(
297+
buffer.as_ptr().add(offset),
298+
metadata.as_mut_ptr() as *mut u8,
299+
(BUFSIZ - offset).min(metadata_size),
300+
);
301+
metadata.assume_init()
302+
};
303+
304+
let fd = (metadata.fd != libc::FAN_NOFD).then(|| unsafe {
305+
OwnedFd::from_raw_fd(metadata.fd)
306+
});
307+
308+
events.push(FanotifyEvent {
309+
version: metadata.vers,
310+
mask: MaskFlags::from_bits_truncate(metadata.mask),
311+
fd,
312+
pid: metadata.pid,
313+
});
314+
315+
offset += metadata.event_len as usize;
316+
}
317+
318+
Ok(events)
319+
}
320+
321+
/// Write an event response on the fanotify group.
322+
///
323+
/// Returns a Result containing either `()` on success or errno otherwise.
324+
///
325+
/// # Errors
326+
///
327+
/// Possible errors can be those that are explicitly listed in
328+
/// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in
329+
/// addition to the possible errors caused by `write` call.
330+
/// In particular, `EAGAIN` or `EWOULDBLOCK` is returned when no event is
331+
/// available on a group that has been initialized with the flag
332+
/// `InitFlags::FAN_NONBLOCK`, thus making this method nonblocking.
333+
pub fn write_response(&self, response: FanotifyResponse) -> Result<()> {
334+
let response_value = match response.response {
335+
Response::Allow => libc::FAN_ALLOW,
336+
Response::Deny => libc::FAN_DENY,
337+
};
338+
let resp = libc::fanotify_response {
339+
fd: response.fd.as_raw_fd(),
340+
response: response_value,
341+
};
342+
write(
343+
self.fd.as_fd(),
344+
unsafe {
345+
std::slice::from_raw_parts(
346+
(&resp as *const _) as *const u8,
347+
size_of::<libc::fanotify_response>(),
348+
)
349+
},
350+
)?;
351+
Ok(())
352+
}
353+
}
354+
355+
impl FromRawFd for Fanotify {
356+
unsafe fn from_raw_fd(fd: RawFd) -> Self {
357+
Fanotify { fd: OwnedFd::from_raw_fd(fd) }
358+
}
359+
}
360+
361+
impl AsFd for Fanotify {
362+
fn as_fd(&'_ self) -> BorrowedFd<'_> {
363+
self.fd.as_fd()
364+
}
365+
}

0 commit comments

Comments
 (0)