๐ Select Language | ้ๆฉ่ฏญ่จ
Features
- Real-time Monitoring: Captures 14 fanotify events (default: 8 core change events,
--all-eventsfor all 14) - Process Attribution: Tracks PID, command name, and user for every file change โ even short-lived processes like
touch,rm,mv - Recursive Monitoring: Watch entire directory trees with automatic tracking of newly created subdirectories
- Complete Deletion Capture: Captures every file deleted during
rm -rfvia persistent directory handle cache - High Performance: Rust + Tokio, <5MB memory footprint, zero-copy FID event parsing, binary-search log querying
- Flexible Capture Filtering: Filter at capture time by event type, size, path pattern, and process name โ all in-process, nanosecond-fast, no fork.
- Live Updates: Add/remove paths while daemon runs โ no restart needed.
Quick Start
Prerequisites
- OS: Linux 5.9+ (requires fanotify FID mode)
- Tested Filesystems: ext4, XFS, btrfs
- Build: Rust toolchain (
cargo)
# Verify kernel version
# Install Rust if needed
|
Installation
# Build from source
# Or install from crates.io
Fanotify requires root privileges for the daemon:
A Complete Walkthrough
Monitor a web project directory, see what gets logged, then use standard Unix tools to filter and clean.
# Terminal 1: start the daemon (sudo for fanotify)
&
# Terminal 1 (or another): add paths to monitor
# Monitor /var/www/myapp recursively, only MODIFY + CREATE events,
# exclude editor temp files, only capture nginx and vim processes
# List what's being monitored
# โ /var/www/myapp | types=MODIFY,CREATE | recursive | min_size=- | exclude-path=*.swp | exclude-cmd=- | only-cmd=nginx,vim | events=filtered
Now trigger some real file changes:
# Terminal 2: simulate real usage
Look at what fsmon captured:
# The raw log โ one JSONL line per event
# โ {"time":"2026-05-07T10:00:01+00:00","event_type":"MODIFY","path":"/var/www/myapp/index.html","pid":1234,"cmd":"nginx","user":"www-data","file_size":21,"monitored_path":"/var/www/myapp"}
# โ {"time":"2026-05-07T10:00:03+00:00","event_type":"DELETE","path":"/var/www/myapp/index.html","pid":5678,"cmd":"rm","user":"deploy","file_size":0,"monitored_path":"/var/www/myapp"}
# โ {"time":"2026-05-07T10:00:05+00:00","event_type":"CREATE","path":"/var/www/myapp/.config.json.swp","pid":9012,"cmd":"vim","user":"dev","file_size":4096,"monitored_path":"/var/www/myapp"}
Notice: vim's .swp was captured but won't be logged โ the --exclude "*.swp" filter drops it before writing. That means it never touches disk.
Query with pipe
Now use standard tools, not fsmon options:
# What did nginx do in the last hour?
|
# What files were deleted?
|
# Who made the biggest changes?
|
# Real-time tail with filter (watch for deployments)
|
No built-in --pid, --cmd, --user, --sort flags needed โ jq does it all.
Clean with safety
# Preview what would be deleted (config default: keep 30 days)
# Actually clean with custom retention
# Or just use Unix tools directly on the files
# Delete events older than 2026-04-01:
|
# Trim to last 500 lines per log file
for; do
&&
done
# Stop the daemon
File Locations
| Purpose | Path | Format |
|---|---|---|
| Infrastructure config | ~/.config/fsmon/config.toml |
TOML (generated via fsmon generate) |
| Path database | ~/.local/share/fsmon/managed.jsonl |
JSONL (one entry per line) |
| Event logs | ~/.local/state/fsmon/*_log.jsonl |
JSONL (one event per line) |
| Unix socket | /tmp/fsmon-<UID>.sock |
TOML over stream |
Both the store path and log directory are configurable in ~/.config/fsmon/config.toml
(see [managed].file and [logging].dir).
The daemon runs as root (via sudo) but resolves your original user's home directory
via SUDO_UID + getpwuid_r, so it writes to /home/<you>/... not /root/....
Note for vfat/exfat/NFS users: The daemon tries to chown log files back to your user. Filesystems without standard Unix ownership (vfat, exfat, NFS with no_root_squash off) don't support this. Logs remain owned by root. If
fsmon cleanfails as a normal user, runsudo fsmon cleanor use the Unix tools directly on the.jsonlfiles.
Auto-start on Boot (Optional)
fsmon does not install a systemd service. To start automatically on login:
&
Capture Filtering
All capture filters run inside the daemon process (nanosecond-fast, no fork). They reduce write I/O โ events that don't match never touch disk.
| Flag | Type | Cost | Reason |
|---|---|---|---|
--types |
kernel mask | zero | fanotify only delivers matching events |
--recursive |
kernel scope | zero | watch subdirectories |
--exclude |
path regex | ~ยตs | reduce write I/O |
--min-size |
u64 compare | ~ns | reduce write I/O |
--exclude-cmd |
cmd regex | ~ยตs | reduce write I/O (new) |
--only-cmd |
cmd regex | ~ยตs | reduce write I/O (new) |
--all-events |
kernel mask | zero | enable all 14 events |
Query & Clean
Query only keeps performance-critical options. All other filtering is done by piping JSONL to standard Unix tools.
fsmon query โ scan all log files, output JSONL
fsmon query --path /tmp โ only read /tmp's log file
fsmon query --since 1h โ binary search + output
Clean uses safety net defaults from config.toml, overridable via CLI:
# Priority: CLI arg > config.toml > code default (30)
Configuration
Auto-generated on first daemon start or via fsmon generate.
# fsmon configuration file
#
# Infrastructure paths for fsmon. Monitored paths are managed separately
# via 'fsmon add' / 'fsmon remove' and persisted in [managed].file.
# All paths support ~ expansion. <UID> is replaced with the numeric UID at runtime.
[]
# Path to the auto-managed monitored paths database.
= "~/.local/share/fsmon/managed.jsonl"
[]
# Directory containing per-path log files (named by path hash).
= "~/.local/state/fsmon"
# Safety nets: keep at most 30 days, max 1GB per log file.
= 30
= "1GB"
[]
# Unix socket path for daemon-CLI live communication.
= "/tmp/fsmon-<UID>.sock"
Event Types
Default captures 8 core events. Use --all-events for all 14.
Default (8): CLOSE_WRITE, ATTRIB, CREATE, DELETE, DELETE_SELF, MOVED_FROM, MOVED_TO, MOVE_SELF
Additional (6, via --all-events): ACCESS, MODIFY, OPEN, OPEN_EXEC, CLOSE_NOWRITE, FS_ERROR
Architecture
Linux Kernel (fanotify)
โ FID events pushed to queue
โ tokio reads events asynchronously
โ fid_parser resolves paths (two-pass + dir cache)
โ Monitor filters (types, size, path pattern, cmd pattern)
โ JSONL โ per-path log files (*_log.jsonl)
User pipe:
cat/ tail *.jsonl โ jq โ your custom logic
Source Tree
src/
โโโ bin/fsmon.rs CLI: daemon, add, remove, managed, query, clean, generate
โโโ lib.rs FileEvent, EventType, clean engine, temp file safety
โโโ config.rs Infrastructure config, SUDO_UID home resolution
โโโ managed.rs Managed paths database (JSONL)
โโโ monitor.rs Fanotify loop, socket handler, all capture filters
โโโ fid_parser.rs Low-level FID event parsing, two-pass path recovery
โโโ dir_cache.rs Directory handle cache for rm -rf recovery
โโโ proc_cache.rs Netlink proc connector (short-lived process attribution)
โโโ query.rs Binary-search log query, JSONL output
โโโ socket.rs Unix socket protocol (TOML), error classification
โโโ utils.rs Size/time parsing, uid lookup, pathโlog name hash
โโโ help.rs Help text for all commands