initial commit
This commit is contained in:
commit
936bc7c43d
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/target
|
||||
/data
|
||||
|
||||
.idea
|
||||
.vscode
|
446
Cargo.lock
generated
Normal file
446
Cargo.lock
generated
Normal file
@ -0,0 +1,446 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fanotify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fanotify-demo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"fanotify",
|
||||
"log",
|
||||
"nix",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"fanotify",
|
||||
"fanotify-demo",
|
||||
]
|
14
fanotify-demo/Cargo.toml
Normal file
14
fanotify-demo/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "fanotify-demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
nix = { version = "0.29", features = ["signal", "user"] }
|
||||
fanotify = { path = "../fanotify" }
|
||||
|
||||
thiserror = "2"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
265
fanotify-demo/src/main.rs
Normal file
265
fanotify-demo/src/main.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::BitOr;
|
||||
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
|
||||
|
||||
use ::fanotify::{consts::*, *};
|
||||
use clap::Parser;
|
||||
use log::*;
|
||||
use nix::sys::signal::{SaFlags, SigAction, SigSet, Signal};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("fanotify failed: {0}")]
|
||||
FanotifyError(#[from] ::fanotify::Error),
|
||||
|
||||
#[error("nix failed: {0}")]
|
||||
Errno(#[from] nix::Error),
|
||||
}
|
||||
|
||||
extern "C" fn interrupt_handler(_: i32) {
|
||||
info!("exiting");
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[clap(about="fanotify demo", long_about="
|
||||
monitor filesystem changes demo
|
||||
|
||||
to use as storage provider:
|
||||
--providers PROVIDER_COMMAND specify what command can write to file, like you can simulate it with tee
|
||||
--init-flags FAN_CLASS_PRE_CONTENT this is needed to instruct fanotify send permission events and wait for response
|
||||
--mask-flags FAN_CLOSE_WRITE to know that storage provider has done writing into the file
|
||||
--mask-flags FAN_OPEN_PERM setup permission notification
|
||||
--mask-flags FAN_ACCESS_PERM setup permission notification
|
||||
--mask-flags FAN_ON_DIR create events for directories itself
|
||||
--mask-flags FAN_EVENT_ON_CHILD create events for direct children
|
||||
")]
|
||||
struct Args {
|
||||
#[clap(required(true))]
|
||||
path: Vec<String>,
|
||||
|
||||
#[clap(long, short, default_values_t=default_whitelist())]
|
||||
whitelist: Vec<String>,
|
||||
#[clap(long, short, default_values_t=default_providers())]
|
||||
providers: Vec<String>,
|
||||
|
||||
#[clap(long, short)]
|
||||
init_flags: Vec<String>,
|
||||
#[clap(long, short, default_values_t=default_event_f_flags())]
|
||||
event_f_flags: Vec<String>,
|
||||
#[clap(long, short, default_values_t=default_mask_flags())]
|
||||
mask_flags: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_whitelist() -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn default_providers() -> Vec<String> {
|
||||
vec!["tee".to_string()]
|
||||
}
|
||||
|
||||
fn default_event_f_flags() -> Vec<String> {
|
||||
vec!["O_RDWR", "O_LARGEFILE"]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn default_mask_flags() -> Vec<String> {
|
||||
vec![
|
||||
"FAN_ACCESS",
|
||||
"FAN_OPEN",
|
||||
"FAN_CLOSE",
|
||||
"FAN_ONDIR",
|
||||
"FAN_EVENT_ON_CHILD",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn reduce_flags<
|
||||
S: Into<String>,
|
||||
I: IntoIterator<Item = S>,
|
||||
F: bitflags::Flags + BitOr<Output = F> + Debug,
|
||||
>(
|
||||
iter: I,
|
||||
) -> F {
|
||||
iter.into_iter()
|
||||
.map(|s| {
|
||||
let s: String = s.into();
|
||||
let Some(flag) = F::from_name(&s.as_str()) else {
|
||||
panic!("invalid value for flag: {}", s)
|
||||
};
|
||||
trace!("flag {} is parted into {:?}", s, flag);
|
||||
|
||||
flag
|
||||
})
|
||||
.reduce(|a, b| a | b)
|
||||
.unwrap_or(F::empty())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
env_logger::Builder::from_default_env().init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let init_flags: InitFlags = reduce_flags(&args.init_flags);
|
||||
let event_f_flags: EventFFlags = reduce_flags(&args.event_f_flags);
|
||||
let mask_flags: MaskFlags = reduce_flags(&args.mask_flags);
|
||||
|
||||
info!("init flag: {:x} {:?}", init_flags.bits(), init_flags);
|
||||
info!(
|
||||
"event fd flag: {:x} {:?}",
|
||||
event_f_flags.bits(),
|
||||
event_f_flags
|
||||
);
|
||||
info!("mask flag: {:x} {:?}", mask_flags.bits(), mask_flags);
|
||||
|
||||
let fan = Fanotify::init(init_flags, event_f_flags)?;
|
||||
for path in args.path {
|
||||
debug!("marking path: {path}");
|
||||
fan.mark(MarkFlags::FAN_MARK_ADD, mask_flags, None, Some(&path))?;
|
||||
info!("path marked: {path}");
|
||||
}
|
||||
unsafe {
|
||||
nix::sys::signal::sigaction(
|
||||
nix::sys::signal::SIGINT,
|
||||
&SigAction::new(
|
||||
nix::sys::signal::SigHandler::Handler(interrupt_handler),
|
||||
SaFlags::SA_RESETHAND,
|
||||
SigSet::from(Signal::SIGINT),
|
||||
),
|
||||
)?
|
||||
};
|
||||
info!("interrupt handler is set");
|
||||
|
||||
let whitelist = args.whitelist;
|
||||
let storage_provider = args.providers;
|
||||
|
||||
let mut ready = HashSet::new();
|
||||
let mut bufferdfds: HashMap<std::path::PathBuf, Vec<OwnedFd>> = HashMap::new();
|
||||
let mut arg0map = HashMap::new();
|
||||
loop {
|
||||
let mut events = fan.read_events()?;
|
||||
if events.len() == 0 {
|
||||
assert!(init_flags & InitFlags::FAN_NONBLOCK == InitFlags::FAN_NONBLOCK);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
for event in events.iter_mut() {
|
||||
let Some(fd) = event.fd() else {
|
||||
warn!("queue full");
|
||||
continue;
|
||||
};
|
||||
let path = match std::fs::read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to read fd link for fd {}: {:?}",
|
||||
fd.as_raw_fd(),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let cmdline_raw = match std::fs::read(format!("/proc/{}/cmdline", event.pid())) {
|
||||
Ok(raw) => raw,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to read pid cmdline for fd {}: {:?}",
|
||||
event.pid(),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let cmdline = if cmdline_raw.len() > 0 {
|
||||
Some(
|
||||
cmdline_raw
|
||||
.split(|&b| b == 0)
|
||||
.map(|v| String::from_utf8_lossy(v))
|
||||
.collect::<Vec<Cow<str>>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
trace!(
|
||||
"++++++++= {:?} {} {:?} {:?}",
|
||||
fd,
|
||||
event.pid(),
|
||||
event.mask(),
|
||||
path
|
||||
);
|
||||
let arg0 = if let Some(cmdline) = cmdline.as_ref() {
|
||||
for (idx, arg) in cmdline.iter().enumerate() {
|
||||
trace!(" - {}: {}", idx, arg);
|
||||
}
|
||||
|
||||
let arg0 = cmdline[0].to_string();
|
||||
|
||||
arg0map.insert(event.pid(), arg0.clone());
|
||||
|
||||
arg0
|
||||
} else if arg0map.contains_key(&event.pid()) {
|
||||
arg0map[&event.pid()].clone()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
match event.mask() {
|
||||
MaskFlags::FAN_ACCESS_PERM
|
||||
| MaskFlags::FAN_OPEN_PERM
|
||||
| MaskFlags::FAN_OPEN_EXEC_PERM => {
|
||||
let allowed = match std::fs::metadata(&path) {
|
||||
Ok(metadata) => {
|
||||
// is a directory or filled with content
|
||||
metadata.is_dir() || ready.contains(&path)
|
||||
}
|
||||
Err(error) => {
|
||||
error.kind() == std::io::ErrorKind::NotFound
|
||||
}
|
||||
};
|
||||
if allowed || whitelist.contains(&arg0) || storage_provider.contains(&arg0) {
|
||||
info!("<<<<< {} allowed", fd.as_raw_fd());
|
||||
if let Err(err) =
|
||||
fan.write_response(FanotifyResponse::new(fd, Response::FAN_ALLOW))
|
||||
{
|
||||
warn!("write response for {} failed: {}", fd.as_raw_fd(), err);
|
||||
}
|
||||
} else {
|
||||
let fd = event.forget_fd();
|
||||
info!("<<<<< {} defered", fd.as_raw_fd());
|
||||
if let Some(fds) = bufferdfds.get_mut(&path) {
|
||||
fds.push(fd);
|
||||
} else {
|
||||
bufferdfds.insert(path, vec![fd]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
MaskFlags::FAN_CLOSE_WRITE => {
|
||||
if storage_provider.contains(&arg0) {
|
||||
ready.insert(path.clone());
|
||||
if let Some(fds) = bufferdfds.remove(&path) {
|
||||
for fd in fds {
|
||||
if let Err(err) = fan.write_response(FanotifyResponse::new(
|
||||
fd.as_fd(),
|
||||
Response::FAN_ALLOW,
|
||||
)) {
|
||||
warn!("write response for {} failed: {}", fd.as_raw_fd(), err);
|
||||
}
|
||||
info!(">>>>> {} allowed(defer)", fd.as_raw_fd());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
fanotify/Cargo.toml
Normal file
9
fanotify/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "fanotify"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
thiserror = "2"
|
||||
bitflags = "2"
|
5
fanotify/README
Normal file
5
fanotify/README
Normal file
@ -0,0 +1,5 @@
|
||||
rust style fanotify wrapper
|
||||
|
||||
------------------------------------------
|
||||
|
||||
this crate wraps around calls and consts of fanotify(7) from libc crate.
|
116
fanotify/src/consts.rs
Normal file
116
fanotify/src/consts.rs
Normal file
@ -0,0 +1,116 @@
|
||||
pub use libc::{
|
||||
FAN_ACCESS, FAN_ACCESS_PERM, FAN_ALLOW, FAN_ATTRIB, FAN_AUDIT, FAN_CLASS_CONTENT,
|
||||
FAN_CLASS_NOTIF, FAN_CLASS_PRE_CONTENT, FAN_CLOEXEC, FAN_CLOSE, FAN_CLOSE_NOWRITE,
|
||||
FAN_CLOSE_WRITE, FAN_CREATE, FAN_DELETE, FAN_DELETE_SELF, FAN_DENY, FAN_ENABLE_AUDIT,
|
||||
FAN_EPIDFD, FAN_EVENT_INFO_TYPE_DFID, FAN_EVENT_INFO_TYPE_DFID_NAME, FAN_EVENT_INFO_TYPE_ERROR,
|
||||
FAN_EVENT_INFO_TYPE_FID, FAN_EVENT_INFO_TYPE_NEW_DFID_NAME, FAN_EVENT_INFO_TYPE_OLD_DFID_NAME,
|
||||
FAN_EVENT_INFO_TYPE_PIDFD, FAN_EVENT_ON_CHILD, FAN_FS_ERROR, FAN_INFO, FAN_MARK_ADD,
|
||||
FAN_MARK_DONT_FOLLOW, FAN_MARK_EVICTABLE, FAN_MARK_FILESYSTEM, FAN_MARK_FLUSH, FAN_MARK_IGNORE,
|
||||
FAN_MARK_IGNORE_SURV, FAN_MARK_IGNORED_MASK, FAN_MARK_IGNORED_SURV_MODIFY, FAN_MARK_INODE,
|
||||
FAN_MARK_MOUNT, FAN_MARK_ONLYDIR, FAN_MARK_REMOVE, FAN_MODIFY, FAN_MOVE, FAN_MOVE_SELF,
|
||||
FAN_MOVED_FROM, FAN_MOVED_TO, FAN_NOFD, FAN_NONBLOCK, FAN_NOPIDFD, FAN_ONDIR, FAN_OPEN,
|
||||
FAN_OPEN_EXEC, FAN_OPEN_EXEC_PERM, FAN_OPEN_PERM, FAN_Q_OVERFLOW, FAN_RENAME,
|
||||
FAN_REPORT_DFID_NAME, FAN_REPORT_DFID_NAME_TARGET, FAN_REPORT_DIR_FID, FAN_REPORT_FID,
|
||||
FAN_REPORT_NAME, FAN_REPORT_PIDFD, FAN_REPORT_TARGET_FID, FAN_REPORT_TID,
|
||||
FAN_RESPONSE_INFO_AUDIT_RULE, FAN_RESPONSE_INFO_NONE, FAN_UNLIMITED_MARKS, FAN_UNLIMITED_QUEUE,
|
||||
FANOTIFY_METADATA_VERSION,
|
||||
};
|
||||
|
||||
pub use libc::{
|
||||
O_APPEND, O_CLOEXEC, O_DSYNC, O_LARGEFILE, O_NOATIME, O_NONBLOCK, O_RDONLY, O_RDWR, O_SYNC,
|
||||
O_WRONLY,
|
||||
};
|
||||
|
||||
// NOTE: the definitions is handwritten in 2025-04-03, on Debian trixie(testing) 6.12.20-1 x86_64
|
||||
// it may need updating for future kernel updates, and pls update this comment for maintainence
|
||||
fa_bitflags! {
|
||||
pub struct InitFlags: u32 {
|
||||
/*
|
||||
one of three classes:
|
||||
- notification-only: get notified when file or directory is accessed
|
||||
- content-access: get notified and also check permission when content is ready. usually used by security softwares.
|
||||
- pre-content-access: get notified and also check permission BEFORE content is ready. usually used by storage managers.
|
||||
*/
|
||||
FAN_CLASS_NOTIF;
|
||||
FAN_CLASS_CONTENT;
|
||||
FAN_CLASS_PRE_CONTENT;
|
||||
|
||||
// additional flags
|
||||
FAN_CLOEXEC;
|
||||
FAN_NONBLOCK;
|
||||
FAN_UNLIMITED_QUEUE;
|
||||
FAN_UNLIMITED_MARKS;
|
||||
FAN_ENABLE_AUDIT; // Linux 4.15
|
||||
|
||||
FAN_REPORT_TID; // Linux 4.20
|
||||
FAN_REPORT_FID; // Linux 5.1
|
||||
FAN_REPORT_DIR_FID; // Linux 5.9
|
||||
FAN_REPORT_NAME; // Linux 5.9
|
||||
FAN_REPORT_DFID_NAME; // Linux 5.9, FAN_REPORT_DIR_FID|FAN_REPORT_NAME
|
||||
FAN_REPORT_TARGET_FID; // Linux 5.17 / 5.15.154 / 5.10.220
|
||||
FAN_REPORT_DFID_NAME_TARGET; // Linux 5.17 / 5.15.154 / 5.10.220, FAN_REPORT_DFID_NAME|FAN_REPORT_FID|FAN_REPORT_TARGET_FID
|
||||
FAN_REPORT_PIDFD; // Linux 5.15 / 5.10.220
|
||||
}
|
||||
|
||||
pub struct EventFFlags: ~u32 {
|
||||
O_RDONLY;
|
||||
O_WRONLY;
|
||||
O_RDWR;
|
||||
|
||||
O_LARGEFILE; // file size limit 2G+
|
||||
O_CLOEXEC; // Linux 3.18
|
||||
|
||||
O_APPEND;
|
||||
O_DSYNC;
|
||||
O_NOATIME;
|
||||
O_NONBLOCK;
|
||||
O_SYNC;
|
||||
}
|
||||
|
||||
pub struct MarkFlags: u32 {
|
||||
// operation, choose exactly one
|
||||
FAN_MARK_ADD;
|
||||
FAN_MARK_REMOVE;
|
||||
FAN_MARK_FLUSH;
|
||||
|
||||
// additional flags
|
||||
FAN_MARK_DONT_FOLLOW;
|
||||
FAN_MARK_ONLYDIR;
|
||||
FAN_MARK_MOUNT;
|
||||
FAN_MARK_FILESYSTEM; // Linux 4.20
|
||||
FAN_MARK_IGNORED_MASK;
|
||||
FAN_MARK_IGNORE; // Linux 6.0 / 5.15.154 / 5.10.220
|
||||
FAN_MARK_IGNORED_SURV_MODIFY;
|
||||
FAN_MARK_IGNORE_SURV; // Linux 6.0 / 5.15.154 / 5.10.220, FAN_MARK_IGNORE|FAN_MARK_IGNORED_SURV_MODIFY
|
||||
FAN_MARK_EVICTABLE; // Linux 5.19 / 5.15.154 / 5.10.220
|
||||
}
|
||||
|
||||
pub struct MaskFlags: u64 {
|
||||
FAN_ACCESS;
|
||||
FAN_MODIFY;
|
||||
FAN_CLOSE_WRITE;
|
||||
FAN_CLOSE_NOWRITE;
|
||||
FAN_CLOSE; // FAN_CLOSE_WRITE|FAN_CLOSE_NOWRITE
|
||||
FAN_OPEN;
|
||||
FAN_OPEN_EXEC; // Linux 5.0
|
||||
FAN_ATTRIB; // Linux 5.1
|
||||
FAN_CREATE; // Linux 5.1
|
||||
FAN_DELETE; // Linux 5.1
|
||||
FAN_DELETE_SELF; // Linux 5.1
|
||||
FAN_FS_ERROR; // Linux 5.16 / 5.15.154 / 5.10.220
|
||||
FAN_MOVED_FROM; // Linux 5.1
|
||||
FAN_MOVED_TO; // Linux 5.1
|
||||
FAN_MOVE; // Linux 5.1, FAN_MOVED_FROM|FAN_MOVED_TO
|
||||
FAN_RENAME; // Linux 5.17 / 5.15.154 / 5.10.220
|
||||
FAN_MOVE_SELF; // Linux 5.1
|
||||
|
||||
// Permissions, need FAN_CLASS_CONTENT or FAN_CLASS_PRE_CONTENT on init
|
||||
FAN_OPEN_PERM;
|
||||
FAN_ACCESS_PERM;
|
||||
FAN_OPEN_EXEC_PERM; // Linux 5.0
|
||||
|
||||
// Flags
|
||||
FAN_ONDIR; // enable events on directories
|
||||
FAN_EVENT_ON_CHILD; // enable events on direct
|
||||
}
|
||||
}
|
72
fanotify/src/error.rs
Normal file
72
fanotify/src/error.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
pub struct Errno {
|
||||
raw_errno: i32,
|
||||
}
|
||||
|
||||
impl Errno {
|
||||
pub fn new(errno: i32) -> Self {
|
||||
Self { raw_errno: errno }
|
||||
}
|
||||
pub fn errno() -> Self {
|
||||
Self::new(unsafe { *libc::__errno_location() } )
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Into<i32>> From<E> for Errno {
|
||||
fn from(value: E) -> Self {
|
||||
Self {
|
||||
raw_errno: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Errno {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = unsafe {
|
||||
let cstr = libc::strerror(self.raw_errno);
|
||||
let len = libc::strlen(cstr);
|
||||
|
||||
let mut buffer = Vec::with_capacity(len);
|
||||
buffer.set_len(len);
|
||||
|
||||
libc::strncpy(buffer.as_mut_ptr() as *mut i8, cstr, len);
|
||||
|
||||
String::from_utf8(buffer)
|
||||
};
|
||||
let result = if let Ok(str) = str {
|
||||
f.write_str(&str)
|
||||
} else {
|
||||
f.write_str(&format!("Unknown error {}", self.raw_errno))
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Errno {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Errno")
|
||||
.field("errno", &self.raw_errno)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Errno {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Errno;
|
||||
|
||||
#[test]
|
||||
fn test_errno() {
|
||||
assert_eq!(
|
||||
Errno::new(libc::ENOENT).to_string(),
|
||||
"No such file or directory"
|
||||
);
|
||||
assert_eq!(Errno::new(0).to_string(), "Success");
|
||||
}
|
||||
}
|
188
fanotify/src/fanotify.rs
Normal file
188
fanotify/src/fanotify.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use std::{
|
||||
ffi::CString,
|
||||
mem::MaybeUninit,
|
||||
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
|
||||
ptr::null,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
consts::{EventFFlags, InitFlags, MarkFlags, MaskFlags},
|
||||
error::Errno,
|
||||
};
|
||||
|
||||
pub struct Fanotify {
|
||||
fd: OwnedFd,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("cannot convert path into c string: {0}")]
|
||||
PathError(#[from] std::ffi::NulError),
|
||||
|
||||
#[error("fanotify returned errno: {0}")]
|
||||
FanotifyError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl Fanotify {
|
||||
pub fn init(init_flags: InitFlags, event_fd_flags: EventFFlags) -> Result<Self, Error> {
|
||||
Self::try_init(init_flags, event_fd_flags)
|
||||
}
|
||||
pub fn try_init(init_flags: InitFlags, event_fd_flags: EventFFlags) -> Result<Self, Error> {
|
||||
let fd = unsafe {
|
||||
let ret = libc::fanotify_init(init_flags.bits(), event_fd_flags.bits());
|
||||
if ret == -1 {
|
||||
return Err(Error::FanotifyError(Errno::errno()));
|
||||
}
|
||||
OwnedFd::from_raw_fd(ret)
|
||||
};
|
||||
Ok(Self { fd })
|
||||
}
|
||||
|
||||
pub fn mark<P: Into<String>>(
|
||||
&self,
|
||||
operation: MarkFlags,
|
||||
mask: MaskFlags,
|
||||
dirfd: Option<BorrowedFd>,
|
||||
path: Option<P>,
|
||||
) -> Result<(), Error> {
|
||||
let dirfd = match dirfd {
|
||||
Some(fd) => fd.as_raw_fd(),
|
||||
None => libc::AT_FDCWD,
|
||||
};
|
||||
let result = unsafe {
|
||||
// hold it here to prevent drop. don't merge two matches
|
||||
if let Some(path) = path {
|
||||
let path: String = path.into();
|
||||
let cstr = CString::new(path)?;
|
||||
libc::fanotify_mark(
|
||||
self.fd.as_raw_fd(),
|
||||
operation.bits(),
|
||||
mask.bits(),
|
||||
dirfd,
|
||||
cstr.as_ptr(),
|
||||
)
|
||||
} else {
|
||||
libc::fanotify_mark(
|
||||
self.fd.as_raw_fd(),
|
||||
operation.bits(),
|
||||
mask.bits(),
|
||||
dirfd,
|
||||
null(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if result != 0 {
|
||||
return Err(Error::FanotifyError(Errno::errno()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_events(&self) -> Result<Vec<Event>, Error> {
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const EVENT_SIZE: usize = size_of::<libc::fanotify_event_metadata>();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut result = Vec::new();
|
||||
unsafe {
|
||||
let nread = libc::read(self.fd.as_raw_fd(), buffer.as_mut_ptr().cast(), BUFFER_SIZE);
|
||||
if nread < 0 {
|
||||
return Err(Error::FanotifyError(Errno::new(nread as i32)));
|
||||
}
|
||||
let nread = nread as usize;
|
||||
let mut offset = 0;
|
||||
while offset + EVENT_SIZE <= nread {
|
||||
let mut event: MaybeUninit<libc::fanotify_event_metadata> = MaybeUninit::uninit();
|
||||
std::ptr::copy(
|
||||
buffer.as_ptr().add(offset),
|
||||
event.as_mut_ptr().cast(),
|
||||
EVENT_SIZE,
|
||||
);
|
||||
|
||||
result.push(Event(event.assume_init()));
|
||||
|
||||
offset += EVENT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn write_response(&self, response: Response) -> Result<(), Errno> {
|
||||
let n = unsafe {
|
||||
libc::write(
|
||||
self.fd.as_raw_fd(),
|
||||
(&response.inner as *const libc::fanotify_response).cast(),
|
||||
size_of::<libc::fanotify_response>(),
|
||||
)
|
||||
};
|
||||
if n == -1 {
|
||||
return Err(Errno::errno());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Event(pub libc::fanotify_event_metadata);
|
||||
|
||||
impl Event {
|
||||
// compatible to nix::sys::fanotify::FanotifyEvent
|
||||
pub fn metadata_version(&self) -> u8 {
|
||||
self.0.vers
|
||||
}
|
||||
pub fn check_metadata_version(&self) -> bool {
|
||||
self.0.vers == libc::FANOTIFY_METADATA_VERSION
|
||||
}
|
||||
pub fn fd(&self) -> Option<BorrowedFd> {
|
||||
if self.0.fd == libc::FAN_NOFD {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { BorrowedFd::borrow_raw(self.0.fd) })
|
||||
}
|
||||
}
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.0.pid
|
||||
}
|
||||
pub fn mask(&self) -> MaskFlags {
|
||||
MaskFlags::from_bits_truncate(self.0.mask)
|
||||
}
|
||||
|
||||
// sometimes we don't want to close the fd immediately, so we forget about it, store it somewhere, and drop it later
|
||||
// it is safe to just call this method without store it in variable, it will be dropped immediately due to the nature of rust
|
||||
pub fn forget_fd(&mut self) -> OwnedFd {
|
||||
let fd = self.0.fd;
|
||||
self.0.fd = libc::FAN_NOFD;
|
||||
|
||||
unsafe { OwnedFd::from_raw_fd(fd) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Event {
|
||||
fn drop(&mut self) {
|
||||
if self.0.fd == libc::FAN_NOFD {
|
||||
return;
|
||||
}
|
||||
|
||||
let e = unsafe { libc::close(self.0.fd) };
|
||||
if !std::thread::panicking() && e == libc::EBADF {
|
||||
panic!("Closing an invalid file descriptor!");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Response {
|
||||
inner: libc::fanotify_response,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub const FAN_ALLOW: u32 = libc::FAN_ALLOW;
|
||||
pub const FAN_DENY: u32 = libc::FAN_DENY;
|
||||
pub fn new(fd: BorrowedFd, response: u32) -> Self {
|
||||
Self {
|
||||
inner: libc::fanotify_response {
|
||||
fd: fd.as_raw_fd(),
|
||||
response,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
10
fanotify/src/lib.rs
Normal file
10
fanotify/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod error;
|
||||
pub mod fanotify;
|
||||
pub mod consts;
|
||||
|
||||
pub use bitflags;
|
||||
|
||||
pub use fanotify::{Fanotify, Error, Response, Response as FanotifyResponse, Event};
|
75
fanotify/src/macros.rs
Normal file
75
fanotify/src/macros.rs
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copied from nix crate and modified to allow multiple patterns in a single block.
|
||||
|
||||
This simplifies flag groups definition.
|
||||
*/
|
||||
macro_rules! fa_bitflags {
|
||||
// modified: accept a list of pub struct, force cast to T
|
||||
(
|
||||
// first
|
||||
$(#[$outer:meta])*
|
||||
pub struct $BitFlags:ident: ~$T:ty {
|
||||
$(
|
||||
$(#[$inner:ident $($args:tt)*])*
|
||||
$Flag:ident;
|
||||
)+
|
||||
}
|
||||
|
||||
// modified part: match rest
|
||||
$($t:tt)*
|
||||
) => {
|
||||
::bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
$(#[$outer])*
|
||||
pub struct $BitFlags: $T {
|
||||
$(
|
||||
$(#[$inner $($args)*])*
|
||||
// always cast to $T
|
||||
const $Flag = libc::$Flag as $T;
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
// modified part: recursively handle rest structs
|
||||
fa_bitflags! {
|
||||
$($t)*
|
||||
}
|
||||
};
|
||||
|
||||
// from nix: input: accept a list of pub struct
|
||||
(
|
||||
// first
|
||||
$(#[$outer:meta])*
|
||||
pub struct $BitFlags:ident: $T:ty {
|
||||
$(
|
||||
$(#[$inner:ident $($args:tt)*])*
|
||||
$Flag:ident $(as $cast:ty)*;
|
||||
)+
|
||||
}
|
||||
|
||||
// modified part: match rest
|
||||
$($t:tt)*
|
||||
) => {
|
||||
// generate bitflags struct
|
||||
::bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
$(#[$outer])*
|
||||
pub struct $BitFlags: $T {
|
||||
$(
|
||||
$(#[$inner $($args)*])*
|
||||
const $Flag = libc::$Flag $(as $cast)*;
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
// modified part: recursively handle rest structs
|
||||
fa_bitflags! {
|
||||
$($t)*
|
||||
}
|
||||
};
|
||||
|
||||
// modified part: empty block don't produce anything.
|
||||
() => {}
|
||||
}
|
105
scripts/privileged-lldb-server.sh
Executable file
105
scripts/privileged-lldb-server.sh
Executable file
@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cat > /dev/null << .vscode/launch.json
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "run with remote lldb-server",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/target/debug/fanotify-demo",
|
||||
"args": [
|
||||
"/home/guochao/fanotify-demo/data",
|
||||
],
|
||||
"env": {
|
||||
"RUST_LOG": "trace"
|
||||
},
|
||||
"initCommands": [
|
||||
"platform select remote-linux", // For example: 'remote-linux', 'remote-macosx', 'remote-android', etc.
|
||||
"platform connect connect://127.0.0.1:11213",
|
||||
// "settings set target.inherit-env false", // See note below.
|
||||
],
|
||||
"preLaunchTask": "prepare debug"
|
||||
}
|
||||
]
|
||||
}
|
||||
.vscode/launch.json
|
||||
|
||||
cat > /dev/null << .vscode/tasks.json
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run remote server",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/privileged-lldb-server.sh",
|
||||
"isBackground": true,
|
||||
"hide": true,
|
||||
"runOptions": {
|
||||
"instanceLimit": 1,
|
||||
"runOn": "folderOpen"
|
||||
},
|
||||
"problemMatcher": {
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "starting",
|
||||
"endsPattern": "server started"
|
||||
},
|
||||
"pattern": {
|
||||
"regexp": ""
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "prepare debug",
|
||||
"type": "shell",
|
||||
"command": "true",
|
||||
"dependsOn": [
|
||||
"rust: cargo build",
|
||||
"run remote server"
|
||||
],
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
.vscode/tasks.json
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HOST=${HOST:-127.0.0.1}
|
||||
PORT=${PORT:-11213}
|
||||
|
||||
if [ "$(whoami)" != "root" ]; then
|
||||
set -x
|
||||
exec sudo -E bash "$0" "$@"
|
||||
fi
|
||||
|
||||
echo starting
|
||||
TEMPDIR="$(mktemp -d)"
|
||||
pushd $TEMPDIR # lldb-server will receive binary and save to current directory
|
||||
lldb-server platform --server --listen "$HOST:$PORT" &
|
||||
PID=$!
|
||||
trap 'kill $PID; rm -rfv "$TEMPDIR"' EXIT
|
||||
echo server started
|
||||
wait
|
Loading…
x
Reference in New Issue
Block a user