|
| 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