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