├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── demo └── with_poll │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── demo.gif │ ├── run.sh │ └── src │ └── main.rs ├── profile ├── Cargo.toml └── src │ └── main.rs ├── src ├── high_level.rs ├── lib.rs └── low_level.rs └── tests ├── high_level.rs └── low_level.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: sudo -E env PATH=$PATH:$(which cargo) cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /src/main.rs 4 | /profile/target -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [0.3.1] - 2024-02-08 9 | 10 | No other changes 11 | 12 | ## [0.3.1-rc3] - 2024-02-08 13 | 14 | Implement `Send`, `Sync`, and `Clone` for `Fanotify`. 15 | 16 | ### Added 17 | Added implementations of the `Send`, `Sync`, and `Clone` traits for better ergonomics on the `Fanotify` type. 18 | 19 | ## [0.3.1-rc2] - 2024-02-05 20 | 21 | Rename `Fanotify` functions and stop eating registration errors. 22 | 23 | ### Added 24 | Added `FanotifyBuilder` to provide finer-grained control on what options are supplied to the libc call without directly using the low_level methods. 25 | 26 | ### Changed 27 | Rename `Fanotify::new_with_blocking` and `Fanotify::new_with_nonblocking` 28 | The above functions now bubble up registration errors 29 | 30 | ## [0.3.1-rc1] - 2024-02-04 31 | 32 | Big update and refactor. The overral structure remains the same, but includes a number of fixes included in outstanding PRs. The update have been checked against current test cases and the PoC, but need review before release. 33 | 34 | ### Added 35 | Implemented `AsFd` for `Fanotify` to allow borrowing of the internal file descriptor (e.g. for polling) 36 | 37 | ### Changed 38 | Widened the implementation of `FanotifyPath` to all implementors of `AsRef`, but removed the direct implementation for `String` due to conflict. 39 | Update dependencies, and removed the dependency on lazy_static. 40 | Updated the crate to 2021 edition. 41 | Changed type of PID in the `Event` type as `pid_t` is generally implemented as `int` in libc implementations. 42 | Changed `to_fan_class` to to copy instead of borrow as the type is `Copy`. 43 | Renamed `low_level::fanotify_response` to `low_level::FanotifyResponse` to keep with Rust's naming convention. 44 | Removed crate definitions of some library flags, and replaced with re-exports from `libc`. 45 | Removed setting that forced inclusion of debug symbols in release mode. 46 | 47 | ### Fixed 48 | Fixed the type for calling `fanotify_mark` on aarch64 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fanotify-rs" 3 | version = "0.3.1" 4 | authors = ["zhanglei ", "n01e0 ", "Philip Woolford "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "The high-level/low-level implementation of Linux Fanotify." 8 | repository = "https://github.com/ZhangLei-cn/fanotify-rs" 9 | readme = "README.md" 10 | 11 | [lib] 12 | name = "fanotify" 13 | path = "src/lib.rs" 14 | doc = true 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | enum-iterator = "1.5" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fanotify-rs 2 | The high-level/low-level implementation of Linux Fanotify. 3 | 4 | [![rust-doc](https://docs.rs/fanotify-rs/badge.svg)](https://docs.rs/fanotify-rs/) 5 | [![Github Action](https://github.com/ZhangLei-cn/fanotify-rs/workflows/Rust/badge.svg)](https://github.com/ZhangLei-cn/fanotify-rs/actions) 6 | [![crates.io](https://img.shields.io/crates/v/fanotify-rs.svg)](https://crates.io/crates/fanotify-rs) 7 | -------------------------------------------------------------------------------- /demo/with_poll/.gitignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | /target 3 | -------------------------------------------------------------------------------- /demo/with_poll/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "with_poll" 3 | version = "0.1.0" 4 | authors = ["n01e0 "] 5 | edition = "2018" 6 | description = "fanotify-rs with poll" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | clap = "4.4.18" 12 | fanotify-rs = { path = "../../" } 13 | nix = {version = "0.27.1", features = ["poll"] } 14 | -------------------------------------------------------------------------------- /demo/with_poll/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | ![](https://github.com/n01e0/fanotify-rs/blob/master/demo/with_poll/demo.gif?raw=true) 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ sudo ./run.sh 9 | ``` 10 | 11 | ### Steps: 12 | 13 | 1. Run `run.sh` as root 14 | ```bash 15 | sudo ./run.sh 16 | ``` 17 | 2. Navigate to new temporary folder 18 | ```bash 19 | `pwd`/tmp 20 | ``` 21 | 3. Create or change files in `tmp` folder to see output on console running `run.sh` -------------------------------------------------------------------------------- /demo/with_poll/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Percivalll/fanotify-rs/22f4dc4b5d4c2b67915b41cd44fa5d2aba506f71/demo/with_poll/demo.gif -------------------------------------------------------------------------------- /demo/with_poll/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cargo build --release 3 | mkdir tmp 2>/dev/null 4 | sudo umount tmp 5 | sudo mount -t tmpfs tmpfs tmp 6 | sudo ./target/release/with_poll `pwd`/tmp 7 | -------------------------------------------------------------------------------- /demo/with_poll/src/main.rs: -------------------------------------------------------------------------------- 1 | use fanotify::high_level::*; 2 | use nix::poll::{poll, PollFd, PollFlags}; 3 | use std::os::fd::AsFd; 4 | 5 | fn main() { 6 | let app = clap::Command::new("with_poll") 7 | .arg(clap::Arg::new("path").index(1).required(true)) 8 | .get_matches(); 9 | 10 | let fd = Fanotify::new_with_nonblocking(FanotifyMode::CONTENT); 11 | fd.add_mountpoint( 12 | FAN_OPEN_EXEC | FAN_CLOSE_WRITE, 13 | app.get_one::("path") 14 | .expect("We can unwrap here as clap enforces the existence of `path`"), 15 | ) 16 | .unwrap(); 17 | 18 | let fd_handle = fd.as_fd(); 19 | let mut fds = [PollFd::new(&fd_handle, PollFlags::POLLIN)]; 20 | loop { 21 | let poll_num = poll(&mut fds, -1).unwrap(); 22 | if poll_num > 0 { 23 | for event in fd.read_event() { 24 | println!("{:#?}", event); 25 | fd.send_response(event.fd, FanotifyResponse::Allow); 26 | } 27 | } else { 28 | eprintln!("poll_num <= 0!"); 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /profile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profile" 3 | version = "0.1.0" 4 | authors = ["Zhanglei-cn "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | libc = "0.2" 11 | fanotify-rs = {"path" = "../"} -------------------------------------------------------------------------------- /profile/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::time::SystemTime; 3 | use std::thread; 4 | use fanotify::high_level::*; 5 | 6 | fn monitor() { 7 | let fty = Fanotify::new_with_blocking(FanotifyMode::NOTIF); 8 | let _ = fty.add_path(FAN_CLOSE_WRITE | FAN_EVENT_ON_CHILD | FAN_ONDIR, "/tmp"); 9 | loop { 10 | let _ = fty.read_event(); 11 | } 12 | } 13 | 14 | fn main() { 15 | let _thread_handle = thread::spawn(|| monitor()); 16 | let start_time = SystemTime::now(); 17 | for i in 0..1000000 { 18 | let _ = File::create(format!("/tmp/{}", i)); 19 | } 20 | let duration = start_time 21 | .elapsed().unwrap(); 22 | println!("QPS:{:?}", 1000000 / duration.as_secs()); 23 | } -------------------------------------------------------------------------------- /src/high_level.rs: -------------------------------------------------------------------------------- 1 | use crate::low_level::{ 2 | close_fd, fanotify_init, fanotify_mark, fanotify_read, FanotifyEventMetadata, AT_FDCWD, 3 | FAN_ALLOW, FAN_CLASS_CONTENT, FAN_CLASS_NOTIF, FAN_CLASS_PRE_CONTENT, FAN_CLOEXEC, FAN_DENY, 4 | FAN_MARK_ADD, FAN_MARK_FLUSH, FAN_MARK_MOUNT, FAN_MARK_REMOVE, FAN_NONBLOCK, O_CLOEXEC, 5 | O_RDONLY, 6 | }; 7 | use crate::FanotifyPath; 8 | use enum_iterator::{all, Sequence}; 9 | use std::fs::read_link; 10 | use std::io::Error; 11 | use std::os::fd::{AsFd, BorrowedFd}; 12 | 13 | pub use crate::low_level::{ 14 | FAN_ACCESS, FAN_ACCESS_PERM, FAN_ATTRIB, FAN_CLOSE, FAN_CLOSE_NOWRITE, FAN_CLOSE_WRITE, 15 | FAN_CREATE, FAN_DELETE, FAN_DELETE_SELF, FAN_EVENT_ON_CHILD, FAN_MODIFY, FAN_MOVE, 16 | FAN_MOVED_FROM, FAN_MOVED_TO, FAN_MOVE_SELF, FAN_ONDIR, FAN_OPEN, FAN_OPEN_EXEC, 17 | FAN_OPEN_EXEC_PERM, FAN_OPEN_PERM, 18 | }; 19 | 20 | pub struct Fanotify { 21 | fd: i32, 22 | } 23 | 24 | // SAFETY: the `fanotify_*` functions are thread safe, and file descriptors are safe for 25 | // sharing betweent threads if they are used in threadsafe functions 26 | unsafe impl Send for Fanotify {} 27 | unsafe impl Sync for Fanotify {} 28 | 29 | impl AsFd for Fanotify { 30 | fn as_fd(&self) -> BorrowedFd<'_> { 31 | unsafe { BorrowedFd::borrow_raw(self.fd) } 32 | } 33 | } 34 | 35 | impl From for Fanotify 36 | where 37 | T: Into, 38 | { 39 | fn from(raw: T) -> Fanotify { 40 | Fanotify { fd: raw.into() } 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone, Copy, Sequence, PartialEq)] 45 | pub enum FanEvent { 46 | Access = FAN_ACCESS as isize, 47 | AccessPerm = FAN_ACCESS_PERM as isize, 48 | Attrib = FAN_ATTRIB as isize, 49 | Close = FAN_CLOSE as isize, 50 | CloseNowrite = FAN_CLOSE_NOWRITE as isize, 51 | CloseWrite = FAN_CLOSE_WRITE as isize, 52 | Create = FAN_CREATE as isize, 53 | Delete = FAN_DELETE as isize, 54 | DeleteSelf = FAN_DELETE_SELF as isize, 55 | EventOnChild = FAN_EVENT_ON_CHILD as isize, 56 | Modify = FAN_MODIFY as isize, 57 | Move = FAN_MOVE as isize, 58 | MovedFrom = FAN_MOVED_FROM as isize, 59 | MovedTo = FAN_MOVED_TO as isize, 60 | MoveSelf = FAN_MOVE_SELF as isize, 61 | Ondir = FAN_ONDIR as isize, 62 | Open = FAN_OPEN as isize, 63 | OpenExec = FAN_OPEN_EXEC as isize, 64 | OpenExecPerm = FAN_OPEN_EXEC_PERM as isize, 65 | OpenPerm = FAN_OPEN_PERM as isize, 66 | } 67 | 68 | impl From for u64 { 69 | fn from(event: FanEvent) -> u64 { 70 | match event { 71 | FanEvent::Access => FAN_ACCESS, 72 | FanEvent::AccessPerm => FAN_ACCESS_PERM, 73 | FanEvent::Attrib => FAN_ATTRIB, 74 | FanEvent::Close => FAN_CLOSE, 75 | FanEvent::CloseNowrite => FAN_CLOSE_NOWRITE, 76 | FanEvent::CloseWrite => FAN_CLOSE_WRITE, 77 | FanEvent::Create => FAN_CREATE, 78 | FanEvent::Delete => FAN_DELETE, 79 | FanEvent::DeleteSelf => FAN_DELETE_SELF, 80 | FanEvent::EventOnChild => FAN_EVENT_ON_CHILD, 81 | FanEvent::Modify => FAN_MODIFY, 82 | FanEvent::Move => FAN_MOVE, 83 | FanEvent::MovedFrom => FAN_MOVED_FROM, 84 | FanEvent::MovedTo => FAN_MOVED_TO, 85 | FanEvent::MoveSelf => FAN_MOVE_SELF, 86 | FanEvent::Ondir => FAN_ONDIR, 87 | FanEvent::Open => FAN_OPEN, 88 | FanEvent::OpenExec => FAN_OPEN_EXEC, 89 | FanEvent::OpenExecPerm => FAN_OPEN_EXEC_PERM, 90 | FanEvent::OpenPerm => FAN_OPEN_PERM, 91 | } 92 | } 93 | } 94 | 95 | pub fn events_from_mask(mask: u64) -> Vec { 96 | all::() 97 | .filter(|flag| (mask & (*flag as u64)) != 0) 98 | .collect::>() 99 | } 100 | 101 | #[derive(Debug)] 102 | pub enum FanotifyResponse { 103 | Allow, 104 | Deny, 105 | } 106 | 107 | impl From for u32 { 108 | fn from(resp: FanotifyResponse) -> u32 { 109 | match resp { 110 | FanotifyResponse::Allow => FAN_ALLOW, 111 | FanotifyResponse::Deny => FAN_DENY, 112 | } 113 | } 114 | } 115 | 116 | #[derive(Debug)] 117 | pub struct Event { 118 | pub fd: i32, 119 | pub path: String, 120 | pub events: Vec, 121 | pub pid: i32, 122 | } 123 | 124 | impl From for Event { 125 | fn from(metadata: FanotifyEventMetadata) -> Self { 126 | let path = read_link(format!("/proc/self/fd/{}", metadata.fd)).unwrap_or_default(); 127 | Event { 128 | fd: metadata.fd, 129 | path: path.to_str().unwrap().to_string(), 130 | events: events_from_mask(metadata.mask), 131 | pid: metadata.pid, 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug, Copy, Clone)] 137 | #[repr(C)] 138 | pub enum FanotifyMode { 139 | PRECONTENT, 140 | CONTENT, 141 | NOTIF, 142 | } 143 | 144 | impl FanotifyMode { 145 | fn to_fan_class(self) -> u32 { 146 | match self { 147 | FanotifyMode::PRECONTENT => FAN_CLASS_PRE_CONTENT, 148 | FanotifyMode::CONTENT => FAN_CLASS_CONTENT, 149 | FanotifyMode::NOTIF => FAN_CLASS_NOTIF, 150 | } 151 | } 152 | } 153 | 154 | impl Fanotify { 155 | pub fn new_blocking(mode: FanotifyMode) -> Result { 156 | Ok(Fanotify { 157 | fd: fanotify_init( 158 | FAN_CLOEXEC | mode.to_fan_class(), 159 | (O_CLOEXEC | O_RDONLY) as u32, 160 | )?, 161 | }) 162 | } 163 | 164 | pub fn new_nonblocking(mode: FanotifyMode) -> Result { 165 | Ok(Fanotify { 166 | fd: fanotify_init( 167 | FAN_CLOEXEC | FAN_NONBLOCK | mode.to_fan_class(), 168 | (O_CLOEXEC | O_RDONLY) as u32, 169 | )?, 170 | }) 171 | } 172 | 173 | pub fn add_path(&self, mode: u64, path: &P) -> Result<(), Error> { 174 | fanotify_mark(self.fd, FAN_MARK_ADD, mode, AT_FDCWD, path)?; 175 | Ok(()) 176 | } 177 | 178 | pub fn add_mountpoint( 179 | &self, 180 | mode: u64, 181 | path: &P, 182 | ) -> Result<(), Error> { 183 | fanotify_mark(self.fd, FAN_MARK_ADD | FAN_MARK_MOUNT, mode, AT_FDCWD, path)?; 184 | Ok(()) 185 | } 186 | 187 | pub fn remove_path(&self, mode: u64, path: &P) -> Result<(), Error> { 188 | fanotify_mark(self.fd, FAN_MARK_REMOVE, mode, AT_FDCWD, path)?; 189 | Ok(()) 190 | } 191 | 192 | pub fn flush_path(&self, mode: u64, path: &P) -> Result<(), Error> { 193 | fanotify_mark(self.fd, FAN_MARK_FLUSH, mode, AT_FDCWD, path)?; 194 | Ok(()) 195 | } 196 | 197 | pub fn read_event(&self) -> Vec { 198 | let mut result = Vec::new(); 199 | let events = fanotify_read(self.fd); 200 | for metadata in events { 201 | let path = read_link(format!("/proc/self/fd/{}", metadata.fd)).unwrap_or_default(); 202 | let path = path.to_str().unwrap(); 203 | result.push(Event { 204 | fd: metadata.fd, 205 | path: String::from(path), 206 | events: events_from_mask(metadata.mask), 207 | pid: metadata.pid, 208 | }); 209 | close_fd(metadata.fd); 210 | } 211 | result 212 | } 213 | 214 | pub fn send_response>(&self, fd: T, resp: FanotifyResponse) { 215 | use crate::low_level::FanotifyResponse as LowLeveResponse; 216 | use libc::c_void; 217 | let response = LowLeveResponse { 218 | fd: fd.into(), 219 | response: resp.into(), 220 | }; 221 | unsafe { 222 | libc::write( 223 | self.fd, 224 | core::ptr::addr_of!(response) as *const c_void, 225 | std::mem::size_of::(), 226 | ); 227 | } 228 | } 229 | 230 | pub fn as_raw_fd(&self) -> i32 { 231 | self.fd 232 | } 233 | 234 | pub fn close(self) { 235 | close_fd(self.fd) 236 | } 237 | } 238 | 239 | impl Drop for Fanotify { 240 | fn drop(&mut self) { 241 | close_fd(self.fd); 242 | } 243 | } 244 | 245 | impl Clone for Fanotify { 246 | fn clone(&self) -> Self { 247 | Self { 248 | fd: unsafe { libc::dup(self.fd) }, 249 | } 250 | } 251 | } 252 | 253 | #[derive(Debug, Copy, Clone)] 254 | pub struct FanotifyBuilder { 255 | class: FanotifyMode, 256 | flags: u32, 257 | event_flags: u32, 258 | } 259 | 260 | impl FanotifyBuilder { 261 | pub fn new() -> Self { 262 | Self { 263 | class: FanotifyMode::NOTIF, 264 | flags: FAN_CLOEXEC, 265 | event_flags: O_CLOEXEC as u32, 266 | } 267 | } 268 | 269 | pub fn with_class(self, class: FanotifyMode) -> Self { 270 | Self { class, ..self } 271 | } 272 | 273 | pub fn with_flags(self, flags: u32) -> Self { 274 | Self { 275 | flags: FAN_CLOEXEC | flags, 276 | ..self 277 | } 278 | } 279 | 280 | pub fn with_event_flags(self, event_flags: u32) -> Self { 281 | Self { 282 | event_flags, 283 | ..self 284 | } 285 | } 286 | 287 | pub fn register(&self) -> Result { 288 | Ok(Fanotify { 289 | fd: fanotify_init(self.flags | self.class.to_fan_class(), self.event_flags)?, 290 | }) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod high_level; 2 | pub mod low_level; 3 | 4 | pub trait FanotifyPath { 5 | fn as_os_str(&self) -> &std::ffi::OsStr; 6 | } 7 | 8 | impl FanotifyPath for std::path::Path { 9 | fn as_os_str(&self) -> &std::ffi::OsStr { 10 | self.as_os_str() 11 | } 12 | } 13 | 14 | impl FanotifyPath for str { 15 | fn as_os_str(&self) -> &std::ffi::OsStr { 16 | std::ffi::OsStr::new(self) 17 | } 18 | } 19 | 20 | impl> FanotifyPath for T { 21 | fn as_os_str(&self) -> &std::ffi::OsStr { 22 | self.as_ref() 23 | } 24 | } -------------------------------------------------------------------------------- /src/low_level.rs: -------------------------------------------------------------------------------- 1 | use crate::FanotifyPath; 2 | use libc::{__s32, __u16, __u32, __u64, __u8}; 3 | use std::io::Error; 4 | use std::mem; 5 | use std::os::unix::ffi::OsStrExt; 6 | use std::slice; 7 | 8 | #[doc(hidden)] 9 | /// Re-export relevant libc constants 10 | pub use libc::{O_RDONLY, O_WRONLY, O_RDWR, O_LARGEFILE, O_CLOEXEC, O_APPEND, O_DSYNC, O_NOATIME, O_NONBLOCK, O_SYNC}; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | #[repr(C)] 14 | pub struct FanotifyEventMetadata { 15 | /// This is the length of the data for the current event and the 16 | /// offset to the next event in the buffer. Without 17 | /// `FAN_REPORT_FID`, the value of event_len is always 18 | /// FAN_EVENT_METADATA_LEN. With `FAN_REPORT_FID`, event_len also 19 | /// includes the variable length file identifier. 20 | pub event_len: __u32, 21 | /// This field holds a version number for the structure. It must 22 | /// be compared to FANOTIFY_METADATA_VERSION to verify that the 23 | /// structures returned at run time match the structures defined 24 | /// at compile time. In case of a mismatch, the application 25 | /// should abandon trying to use the fanotify file descriptor. 26 | pub vers: __u8, 27 | /// This field is not used. 28 | pub reserved: __u8, 29 | /// This is the length of the structure. The field was introduced 30 | /// to facilitate the implementation of optional headers per event 31 | /// type. No such optional headers exist in the current implemen‐ 32 | /// tation. 33 | pub metadata_len: __u16, 34 | /// This is a bit mask describing the event (see below). 35 | pub mask: __u64, 36 | /// This is an open file descriptor for the object being accessed,or FAN_NOFD if a queue overflow occurred. 37 | /// If the fanotify file descriptor has been initialized using `FAN_REPORT_FID`, 38 | /// applications should expect this value to be set to FAN_NOFDfor each event that is received. The file descriptor can be 39 | /// used to access the contents of the monitored file or directory. The reading application is responsible for closing this file descriptor. 40 | /// When calling fanotify_init(2), the caller may specify (via the event_f_flags argument) various file status flags that are to 41 | /// be set on the open file description that corresponds to this file descriptor. In addition, the (kernel-internal) FMODE_NONOTIFY file status flag is set on the open file description. 42 | /// This flag suppresses fanotify event generation. Hence, when the receiver of the fanotify event accesses the notified file or directory using this file descriptor, noadditional events will be created. 43 | pub fd: __s32, 44 | /// If flag FAN_REPORT_TID was set in fanotify_init(2), this is 45 | /// the TID of the thread that caused the event. Otherwise, this 46 | /// the PID of the process that caused the event. 47 | pub pid: __s32, 48 | } 49 | 50 | #[derive(Debug)] 51 | #[repr(C)] 52 | /// It is used to control file access. 53 | pub struct FanotifyResponse { 54 | pub fd: __s32, 55 | pub response: __u32, 56 | } 57 | 58 | /// Current platform sizeof of `FanotifyEventMetadata`. 59 | const FAN_EVENT_METADATA_LEN: usize = mem::size_of::(); 60 | 61 | /// This const is used to be compared to vers field of `FanotifyEventMetadata` to verify that the structures returned at run time match the structures defined at compile time. 62 | /// 63 | /// 64 | /// In case of a mismatch, the application should abandon trying to use the fanotify file descriptor. 65 | pub const FANOTIFY_METADATA_VERSION: u8 = 3; 66 | /// Allow the file operation. 67 | pub const FAN_ALLOW: u32 = 0x01; 68 | /// Deny the file operation. 69 | pub const FAN_DENY: u32 = 0x02; 70 | /// bit mask to create audit record for result 71 | pub const FAN_AUDIT: u32 = 0x10; 72 | /// Indicates a queue overflow. 73 | pub const FAN_NOFD: i32 = -1; 74 | /// The event queue exceeded the limit of 16384 entries. 75 | /// 76 | /// 77 | /// This limit can be overridden by specifying the `FAN_UNLIMITED_QUEUE` flag when calling `fanotify_init(2)`. 78 | pub const FAN_Q_OVERFLOW: u64 = 0x0000_4000; 79 | /// Set the close-on-exec flag `(FD_CLOEXEC) `on the new file descriptor. 80 | /// 81 | /// See the description of the `O_CLOEXEC flag` in `open(2)`. 82 | pub const FAN_CLOEXEC: u32 = 0x0000_0001; 83 | /// Enable the nonblocking flag `(O_NONBLOCK)` for the file descriptor. 84 | /// 85 | /// 86 | /// Reading from the file descriptor will not block.
87 | /// Instead, if no data is available, `read(2)` fails with the error `EAGAIN` 88 | pub const FAN_NONBLOCK: u32 = 0x0000_0002; 89 | /// This is the default value. It does not need to be specified. 90 | /// 91 | /// 92 | /// This value only allows the receipt of events notifying that a file has been accessed.
93 | /// Permission decisions before the file is accessed are not possible. 94 | pub const FAN_CLASS_NOTIF: u32 = 0x0000_0000; 95 | /// This value allows the receipt of events notifying that a file has been accessed and events for permission decisions if a file may be accessed. 96 | /// 97 | /// 98 | /// It is intended for event listeners that need to access files when they already contain their final content.
99 | /// This notification class might be used by malware detection programs, for example. 100 | pub const FAN_CLASS_CONTENT: u32 = 0x0000_0004; 101 | /// This value allows the receipt of events notifying that a file has been accessed and events for permission decisions if a file may be accessed.
102 | /// It is intended for event listeners that need to access files before they contain their final data.
103 | /// This notification class might be used by hierarchical storage managers, for example. 104 | pub const FAN_CLASS_PRE_CONTENT: u32 = 0x0000_0008; 105 | /// Remove the limit of 16384 events for the event queue.
106 | /// Use of this flag requires the `CAP_SYS_ADMIN` capability. 107 | pub const FAN_UNLIMITED_QUEUE: u32 = 0x0000_0010; 108 | /// Remove the limit of 8192 marks.
109 | /// Use of this flag requires the `CAP_SYS_ADMIN` capability. 110 | pub const FAN_UNLIMITED_MARKS: u32 = 0x0000_0020; 111 | /// `CONFIG_AUDIT_SYSCALL` 112 | pub const FAN_ENABLE_AUDIT: u32 = 0x0000_0040; 113 | 114 | /// Flags to determine fanotify event format 115 | /// event->pid is thread id 116 | pub const FAN_REPORT_TID: u32 = 0x0000_0100; 117 | /// Flags to determine fanotify event format 118 | /// report unique file id 119 | pub const FAN_REPORT_FID: u32 = 0x0000_0200; 120 | /// Flags to determine fanotify event format 121 | /// report unique directory id 122 | pub const FAN_REPORT_DIR_FID: u32 = 0x0000_0400; 123 | /// Flags to determine fanotify event format 124 | /// report events with name 125 | pub const FAN_REPORT_NAME: u32 = 0x0000_0800; 126 | /// Create an event when a file or directory is accessed (read). 127 | pub const FAN_ACCESS: u64 = 0x0000_0001; 128 | /// Create an event when a file is modified (write). 129 | pub const FAN_MODIFY: u64 = 0x0000_0002; 130 | /// Create an event when a metadata changed. 131 | pub const FAN_ATTRIB: u64 = 0x0000_0004; 132 | /// Create an event when a writable file is closed. 133 | pub const FAN_CLOSE_WRITE: u64 = 0x0000_0008; 134 | /// Create an event when a read-only file or directory is closed. 135 | pub const FAN_CLOSE_NOWRITE: u64 = 0x0000_0010; 136 | /// Create an event when a file or directory is opened. 137 | pub const FAN_OPEN: u64 = 0x0000_0020; 138 | /// Create an event when a file was moved from X 139 | pub const FAN_MOVED_FROM: u64 = 0x0000_0040; 140 | /// Create an event when a file was moved to Y 141 | pub const FAN_MOVED_TO: u64 = 0x0000_0080; 142 | /// Create an event when a file was moved 143 | pub const FAN_MOVE: u64 = FAN_MOVED_TO | FAN_MOVED_FROM; 144 | /// Create an event when a file created 145 | pub const FAN_CREATE: u64 = 0x0000_0100; 146 | /// Create an event when a file deleted 147 | pub const FAN_DELETE: u64 = 0x0000_0200; 148 | /// Create an event when a self was deleted 149 | pub const FAN_DELETE_SELF: u64 = 0x0000_0400; 150 | /// Create an event when self was moved 151 | pub const FAN_MOVE_SELF: u64 = 0x0000_0800; 152 | /// Create an event when file was opened for exec 153 | pub const FAN_OPEN_EXEC: u64 = 0x0000_1000; 154 | /// Create an event when a permission to open a file or directory is requested.
155 | /// An fanotify file descriptor created with `FAN_CLASS_PRE_CONTENT` or `FAN_CLASS_CONTENT` is required. 156 | pub const FAN_OPEN_PERM: u64 = 0x0001_0000; 157 | /// Create an event when a permission to read a file or directoryis requested.
158 | /// An fanotify file descriptor created with `FAN_CLASS_PRE_CONTENT` or `FAN_CLASS_CONTENT` is required. 159 | pub const FAN_ACCESS_PERM: u64 = 0x0002_0000; 160 | /// Create an event when a permission to open a file for exec is requested.
161 | /// An fanotify file descriptor created with `FAN_CLASS_PRE_CONTENT` or `FAN_CLASS_CONTENT` is required. 162 | pub const FAN_OPEN_EXEC_PERM: u64 = 0x0004_0000; 163 | /// Create events for directories—for example, when `opendir(3)`, `readdir(3)` (but see BUGS), and `closedir(3)` are called.
164 | /// Without this flag, events are created only for files.
165 | /// In the context of directory entry events, such as `FAN_CREATE,FAN_DELETE`, `FAN_MOVED_FROM`, and `FAN_MOVED_TO`, specifying the flag `FAN_ONDIR` is required in order to create events when subdirectory entries are modified (i.e., `mkdir(2)`/`rmdir(2)`). 166 | pub const FAN_ONDIR: u64 = 0x4000_0000; 167 | /// Events for the immediate children of marked directories shall be created. 168 | /// 169 | /// 170 | /// The flag has no effect when marking mounts and filesystems.
171 | /// Note that events are not generated for children of the subdirectories of marked directories.
172 | /// More specifically, the directory entry modification events `FAN_CREATE`, `FAN_DELETE`, `FAN_MOVED_FROM`, and `FAN_MOVED_TO` arenot generated for any entry modifications performed inside subdirectories of marked directories.
173 | /// Note that the events `FAN_DELETE_SELF` and `FAN_MOVE_SELF` are not generated for children of marked directories.
174 | /// To monitor complete directory trees it is necessary to mark the relevant mount or filesystem.
175 | pub const FAN_EVENT_ON_CHILD: u64 = 0x0800_0000; 176 | /// A file is closed `(FAN_CLOSE_WRITE|FAN_CLOSE_NOWRITE)`.
177 | pub const FAN_CLOSE: u64 = FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE; 178 | /// The events in mask will be added to the mark mask (or to the ignore mask). mask must be nonempty or the error `EINVAL` will occur. 179 | pub const FAN_MARK_ADD: u32 = 0x0000_0001; 180 | /// The events in argument mask will be removed from the mark mask (or from the ignore mask). mask must be nonempty or the error `EINVAL` will occur. 181 | pub const FAN_MARK_REMOVE: u32 = 0x0000_0002; 182 | /// Remove either all marks for filesystems, all marks for mounts,or all marks for directories and files from the fanotify group.
183 | /// If flags contains `FAN_MARK_MOUNT`, all marks for mounts are removed from the group.
184 | /// If flags contains `FAN_MARK_FILESYSTEM`, all marks for filesystems are removed from the group.
185 | /// Otherwise, all marks for directories and files are removed.
186 | /// No flag other than and at most one of the flags `FAN_MARK_MOUNT` or `FAN_MARK_FILESYSTEM` can be used in conjunction with `FAN_MARK_FLUSH`. mask is ignored. 187 | pub const FAN_MARK_FLUSH: u32 = 0x0000_0080; 188 | /// If pathname is a symbolic link, mark the link itself, rather than the file to which it refers.
189 | /// (By default,`fanotify_mark()` dereferences pathname if it is a symbolic link.) 190 | pub const FAN_MARK_DONT_FOLLOW: u32 = 0x0000_0004; 191 | /// If the filesystem object to be marked is not a directory, the error `ENOTDIR` shall be raised. 192 | pub const FAN_MARK_ONLYDIR: u32 = 0x0000_0008; 193 | /// Mark the inode specified by pathname.
194 | /// It is default way to mark. 195 | pub const FAN_MARK_INODE: u32 = 0x0000_0000; 196 | /// Mark the mount point specified by pathname. If pathname is not itself a mount point, the mount point containing pathname will be marked.
197 | /// All directories, subdirectories, and the contained files of the mount point will be monitored.
198 | /// The events which require the `fanotify_fd` file descriptor to have been initialized with the flag `FAN_REPORT_FID`, such as `FAN_CREATE`, `FAN_ATTRIB`, `FAN_MOVE`, and `FAN_DELETE_SELF`, cannot be provided as a mask when flags contains `FAN_MARK_MOUNT`.
199 | /// Attempting to do so will result in the error `EINVAL` being returned. 200 | pub const FAN_MARK_MOUNT: u32 = 0x0000_0010; 201 | /// Mark the filesystem specified by pathname.
202 | /// The filesystem containing pathname will be marked.
203 | /// All the contained files and directories of the filesystem from any mount point will be monitored. 204 | pub const FAN_MARK_FILESYSTEM: u32 = 0x0000_0100; 205 | /// The events in mask shall be added to or removed from the ignore mask. 206 | pub const FAN_MARK_IGNORED_MASK: u32 = 0x0000_0020; 207 | /// The ignore mask shall survive modify events.
208 | /// If this flag is not set, the ignore mask is cleared when a modify event occurs for the ignored file or directory. 209 | pub const FAN_MARK_IGNORED_SURV_MODIFY: u32 = 0x0000_0040; 210 | pub const AT_FDCWD: i32 = -100; 211 | pub const AT_SYMLINK_NOFOLLOW: i32 = 0x100; 212 | pub const AT_REMOVEDIR: i32 = 0x200; 213 | pub const AT_SYMLINK_FOLLOW: i32 = 0x400; 214 | pub const AT_NO_AUTOMOUNT: i32 = 0x800; 215 | pub const AT_EMPTY_PATH: i32 = 0x1000; 216 | 217 | /// Initializes a new fanotify group and returns a file descriptor for the event queue associated with the group.
218 | /// 219 | /// The file descriptor is used in calls to `fanotify_mark(2)` to specify the files, directories, mounts or filesystems for which fanotify events shall be created. 220 | /// These events are received by reading from the file descriptor.
221 | /// Some events are only informative, indicating that a file has been accessed. 222 | /// Other events can be used to determine whether another application is permitted to access a file or directory. 223 | /// Permission to access filesystem objects is granted by writing to the file descriptor. 224 | /// Multiple programs may be using the fanotify interface at the same time to monitor the same files.
225 | /// In the current implementation, the number of fanotify groups per user is limited to 128. This limit cannot be overridden. 226 | /// Calling `fanotify_init()` requires the `CAP_SYS_ADMIN` capability. 227 | /// This constraint might be relaxed in future versions of the API.
228 | /// Therefore, certain additional capability checks have been implemented as indicated below.
229 | /// The `flags` argument contains a multi-bit field defining the notification class of the listening application and further single bit fields specifying the behavior of the file descriptor.
230 | /// If multiple listeners for permission events exist, the notification class is used to establish the sequence in which the listeners receive the events.
231 | /// 232 | /// Only one of the following notification classes may be specified in `flags`:
233 | /// * `FAN_CLASS_PRE_CONTENT` 234 | /// * `FAN_CLASS_CONTENT` 235 | /// * `FAN_CLASS_NOTIF` 236 | /// 237 | /// Listeners with different notification classes will receive events in the order `FAN_CLASS_PRE_CONTENT`, `FAN_CLASS_CONTENT`, `FAN_CLASS_NOTIF`. 238 | /// The order of notification for listeners in the same notification class is undefined.
239 | /// The following bits can additionally be set in flags:
240 | /// * `FAN_CLOEXEC` 241 | /// * `FAN_NONBLOCK` 242 | /// * `FAN_UNLIMITED_QUEUE` 243 | /// * `FAN_UNLIMITED_MARKS` 244 | /// * `FAN_REPORT_TID` (since Linux 4.20) 245 | /// * `FAN_REPORT_FID` (since Linux 5.1) 246 | /// 247 | /// The `event_f_flags` argument defines the file status flags that will be set on the open file descriptions that are created for fanotify events.
248 | /// For details of these flags, see the description of the flags values in `open(2)`. `event_f_flags` includes a multi-bit field for the access mode.
249 | /// This field can take the following values: 250 | /// * `O_RDONLY` 251 | /// * `O_WRONLY` 252 | /// * `O_RDWR` 253 | /// 254 | /// Additional bits can be set in `event_f_flags`. The most useful values are: 255 | /// * `O_LARGEFILE` 256 | /// * `O_CLOEXEC` (since Linux 3.18) 257 | /// 258 | /// The following are also allowable: `O_APPEND`, `O_DSYNC`, `O_NOATIME`,`O_NONBLOCK`, and `O_SYNC`. Specifying any other flag in `event_f_flags` yields the error `EINVAL`. 259 | /// # Examples 260 | /// ``` 261 | /// use fanotify::low_level::*; 262 | /// let fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY as u32).unwrap(); 263 | /// assert!(fd > 0) 264 | /// ``` 265 | pub fn fanotify_init(flags: u32, event_f_flags: u32) -> Result { 266 | unsafe { 267 | match libc::fanotify_init(flags, event_f_flags) { 268 | -1 => { 269 | Err(Error::last_os_error()) 270 | } 271 | fd => { 272 | Ok(fd) 273 | } 274 | } 275 | } 276 | } 277 | /// Adds, removes, or modifies an fanotify mark on a filesystem object. 278 | // The caller must have read permission on the filesystem object that is to be marked. 279 | /// 280 | /// The `fanotify_fd` argument is a file descriptor returned by `fanotify_init()`. 281 | /// 282 | /// `flags` is a bit mask describing the modification to perform. It must include exactly one of the following values: 283 | /// * `FAN_MARK_ADD` 284 | /// * `FAN_MARK_REMOVE` 285 | /// * `FAN_MARK_FLUSH` 286 | /// 287 | /// If none of the values above is specified, or more than one is specified, the call fails with the error `EINVAL`. 288 | /// 289 | /// In addition, zero or more of the following values may be `ORed` into `flags`: 290 | /// * `FAN_MARK_DONT_FOLLOW` 291 | /// * `FAN_MARK_ONLYDIR` 292 | /// * `FAN_MARK_MOUNT` 293 | /// * `FAN_MARK_FILESYSTEM` (since Linux 4.20) 294 | /// * `FAN_MARK_IGNORED_MASK` 295 | /// * `FAN_MARK_IGNORED_SURV_MODIFY` 296 | /// 297 | /// 298 | /// `mask` defines which events shall be listened for (or which shall be ignored). It is a bit mask composed of the following values: 299 | /// * `FAN_ACCESS` 300 | /// * `FAN_MODIFY` 301 | /// * `FAN_CLOSE_WRITE` 302 | /// * `FAN_CLOSE_NOWRITE` 303 | /// * `FAN_OPEN` 304 | /// * `FAN_OPEN_EXEC` (since Linux 5.0) 305 | /// * `FAN_ATTRIB` (since Linux 5.1) 306 | /// * `FAN_CREATE` (since Linux 5.1) 307 | /// * `FAN_DELETE` (since Linux 5.1) 308 | /// * `FAN_DELETE_SELF` (since Linux 5.1) 309 | /// * `FAN_MOVED_FROM` (since Linux 5.1) 310 | /// * `FAN_MOVED_TO` (since Linux 5.1) 311 | /// * `FAN_MOVE_SELF` (since Linux 5.1) 312 | /// * `FAN_OPEN_PERM` 313 | /// * `FAN_OPEN_EXEC_PERM` (since Linux 5.0) 314 | /// * `FAN_ACCESS_PERM` 315 | /// * `FAN_ONDIR` 316 | /// * `FAN_EVENT_ON_CHILD` 317 | /// 318 | /// The following composed values are defined: 319 | /// * `FAN_CLOSE` 320 | /// * `FAN_MOVE` (since Linux 5.1) 321 | /// 322 | /// 323 | /// The filesystem object to be marked is determined by the file descriptor `dirfd` and the pathname specified in pathname: 324 | /// * If pathname is `NULL`, `dirfd` defines the filesystem object to be marked. 325 | /// * If pathname is `NULL`, and `dirfd` takes the special value `AT_FDCWD`,the current working directory is to be marked. 326 | /// * If pathname is absolute, it defines the filesystem object to be marked, and `dirfd` is ignored. 327 | /// * If pathname is relative, and `dirfd` does not have the value `AT_FDCWD`, then the filesystem object to be marked is determined by interpreting pathname relative the directory referred to by `dirfd.` 328 | /// * If pathname is relative, and `dirfd` has the value `AT_FDCWD`, then the filesystem object to be marked is determined by interpreting pathname relative the current working directory. 329 | /// # Examples 330 | /// ``` 331 | /// use fanotify::low_level::*; 332 | /// let fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY as u32).unwrap(); 333 | /// fanotify_mark(fd, FAN_MARK_ADD, FAN_OPEN | FAN_CLOSE, AT_FDCWD, "./").unwrap(); 334 | /// ``` 335 | pub fn fanotify_mark( 336 | fanotify_fd: i32, 337 | flags: u32, 338 | mask: u64, 339 | dirfd: i32, 340 | path: &P, 341 | ) -> Result<(), Error> { 342 | unsafe { 343 | let mut raw_path = path.as_os_str().as_bytes().to_vec(); 344 | raw_path.push(0u8); // data must be null terminated 345 | 346 | //make sure path is null terminated 347 | match libc::fanotify_mark( 348 | fanotify_fd, 349 | flags, 350 | mask, 351 | dirfd, 352 | raw_path.as_ptr().cast(), 353 | ) { 354 | 0 => { 355 | Ok(()) 356 | } 357 | _ => { 358 | Err(Error::last_os_error()) 359 | } 360 | } 361 | } 362 | } 363 | 364 | pub fn fanotify_read(fanotify_fd: i32) -> Vec { 365 | let mut vec = Vec::new(); 366 | let mut buffer = Box::new([0u8;FAN_EVENT_METADATA_LEN * 200]); 367 | unsafe { 368 | // Allocate a buffer to store up to 200 events 369 | 370 | let sizeof = libc::read(fanotify_fd, buffer.as_mut_ptr() as _, FAN_EVENT_METADATA_LEN * 200); 371 | if sizeof != libc::EAGAIN as isize && sizeof > 0 { 372 | let src = slice::from_raw_parts( 373 | buffer.as_ptr().cast::(), 374 | sizeof as usize / FAN_EVENT_METADATA_LEN, 375 | ); 376 | vec.extend_from_slice(src); 377 | } 378 | } 379 | vec 380 | } 381 | 382 | pub fn close_fd(fd: i32) { 383 | unsafe { 384 | libc::close(fd); 385 | } 386 | } -------------------------------------------------------------------------------- /tests/high_level.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn high_level_test() { 3 | use fanotify::high_level::{ 4 | Fanotify, FanotifyMode, FAN_ACCESS, FAN_CLOSE, FAN_EVENT_ON_CHILD, FAN_MODIFY, FAN_ONDIR, 5 | FAN_OPEN, 6 | }; 7 | use std::io::{Read, Write}; 8 | let ft = Fanotify::new_blocking(FanotifyMode::NOTIF).expect("Error regitering fanotify listener"); 9 | ft.add_path( 10 | FAN_ACCESS | FAN_CLOSE | FAN_EVENT_ON_CHILD | FAN_MODIFY | FAN_ONDIR | FAN_OPEN, 11 | "/tmp", 12 | ) 13 | .unwrap(); 14 | let handler = std::thread::spawn(|| { 15 | let mut tmp = std::fs::File::create("/tmp/fanotify_test").unwrap(); 16 | tmp.write_all(b"xxx").unwrap(); 17 | let mut tmp = std::fs::File::open("/tmp/fanotify_test").unwrap(); 18 | let mut res = String::new(); 19 | tmp.read_to_string(&mut res).unwrap(); 20 | assert_eq!(res, "xxx".to_string()); 21 | }); 22 | handler.join().unwrap(); 23 | } -------------------------------------------------------------------------------- /tests/low_level.rs: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------