Skip to content

Commit 4547058

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

File tree

7 files changed

+525
-0
lines changed

7 files changed

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

0 commit comments

Comments
 (0)