đ 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 Filtering: Filter by time, size, process, user, event type, and exclude patterns (wildcards)
- Multiple Formats: Human-readable, JSON, and CSV output
- TOML Configuration: Persistent config at
~/.fsmon/config.toml,~/.config/fsmon/config.toml, or/etc/fsmon/config.toml(priority order) - Log Management: Time-based and size-based log rotation with dry-run preview
- Systemd Service: Install as systemd service with configurable security hardening
Why fsmon
Ever needed to answer "Who modified this file?" on a Linux server? That's exactly what fsmon is for.
Traditional file monitoring tools give you events without context â fsmon bridges that gap by attributing every file change to its responsible process. Whether it's a rogue script, an automated deployment, or a misconfigured service, you'll know exactly what happened, when, and who (or what) caused it.
Quick Start
Prerequisites
- OS: Linux 5.9+ (requires fanotify FID mode)
- Tested Filesystems: ext4, XFS, btrfs (Note: Linux 6.18+ recommended for full recursive operation support of btrfs)
- Build: Rust toolchain (
cargo)
# Verify kernel version
# Install Rust if needed
|
Installation
# Build from source
# Or install from crates.io
Important: Fanotify requires root privileges
# Method 1: Copy to /usr/local/bin (recommended)
# Method 2: Use full path directly
Basic Usage
# Monitor a directory
# Monitor with recursive watching
# Exclude patterns
# Install as systemd service for long-term auditing
# Query historical events
# Clean old logs (dry-run preview)
# Check service status
Examples
Investigate Configuration Changes
# Monitor /etc for modifications
# In another terminal, make a change
|
# Query the results
Track Large File Creation
# Watch for files larger than 50MB
# Trigger
Audit Deletion Operations
# Capture complete recursive deletion
# Trigger
# Output shows every file deleted (even in subdirectories)
)
)
Filter with Combined Criteria
# Query nginx operations in last hour, sorted by file size
# Monitor only CREATE and DELETE events, exclude temp files
Command Reference
Configuration
fsmon supports TOML configuration files. Search priority (first found wins):
~/.fsmon/config.tomlâ legacy backward-compatible path~/.config/fsmon/config.tomlâ XDG standard path (generated byfsmon generate)/etc/fsmon/config.tomlâ system-wide configuration
Default config (fsmon generate):
[]
# Directories to watch for filesystem events
= []
# Minimum file size to report (supports KB, MB, GB suffixes, e.g. "100MB", "1GB")
# min_size = "100MB"
# Comma-separated event types to filter (ACCESS, MODIFY, CREATE, DELETE, ...)
# types = "MODIFY,CREATE"
# Glob patterns to exclude from monitoring
# exclude = "*.tmp"
# Report all 14 event types regardless of the 'types' filter
= false
# Path to the event log file
# output = "/var/log/fsmon.log"
# Log output format: "human", "json", or "csv"
= "human"
# Watch subdirectories recursively
= false
# Fanotify read buffer size in bytes
= 32768
[]
# Event log file to query
# log_file = "/var/log/fsmon.log"
# Start time: relative ("1h", "30m", "7d") or absolute ("2024-05-01 10:00")
# since = "1h"
# End time: same format as since
# until = "2h"
# Filter by process IDs (comma-separated)
# pid = "1234,5678"
# Filter by process name (wildcard support: nginx*, python)
# cmd = "nginx"
# Filter by usernames (comma-separated)
# user = "root,admin"
# Filter by event types (comma-separated)
# types = "MODIFY,CREATE"
# Minimum size change to include
# min_size = "100MB"
# Output format: "human", "json", or "csv"
= "human"
# Sort results: "time", "size", or "pid"
= "time"
[]
# Event log file to clean
# log_file = "/var/log/fsmon.log"
# Number of days to retain log entries
= 30
# Maximum log file size before tail truncation (e.g. "100MB", "1GB")
# max_size = "500MB"
[]
# systemd ProtectSystem value ("yes", "no", "strict", "full")
= "strict"
# systemd ProtectHome value ("yes", "no", "read-only")
= "read-only"
# Additional read-write paths for systemd service (used when ProtectSystem is strict)
= ["/var/log"]
# systemd PrivateTmp value ("yes" or "no")
= "yes"
CLI flags override config file values.
Technical Architecture
Modules
| Module | Description |
|---|---|
main.rs |
CLI entry point with clap derive, FileEvent struct, log cleaning engine |
monitor.rs |
Core fanotify monitoring loop, scope filtering, file size tracking (LRU) |
fid_parser.rs |
Low-level FID mode event parsing, two-pass path recovery |
dir_cache.rs |
Directory handle caching via name_to_handle_at for deleted file path resolution |
proc_cache.rs |
Netlink proc connector listener â captures short-lived process info at exec() |
query.rs |
Log file querying with binary search optimization and combined filters |
config.rs |
TOML-based persistent configuration |
systemd.rs |
Systemd service lifecycle (install, uninstall, status, start, stop) |
output.rs |
Event output formatting (human, JSON, CSV) |
utils.rs |
Size/time parsing, process info helpers, UID lookup |
help.rs |
Centralized help text for all commands |
Data Flow
Linux Kernel (fanotify)
â FID events pushed to queue
â tokio::select reads events asynchronously
â fid_parser parses FID records (two-pass: resolve + cache recover)
â Monitor filters (type, size, exclude, scope)
â output formats (human/json/csv) â stdout + optional file
- fanotify (FID mode + FAN_REPORT_NAME): Kernel pushes file events with directory file handles and filenames. No polling â events delivered immediately via non-blocking read.
- Proc Connector: Background thread subscribes to netlink
PROC_EVENT_EXECnotifications, caching every process's(pid, cmd, user)at the instant it execs. This ensures short-lived processes (touch,rm,mv) are attributable even after they exit. - FID Parser + Dir Cache: Two-pass event processing: (1) resolve file handles via
open_by_handle_at, (2) use persistent directory handle cache to recover paths for events where the parent directory was already deleted. Handles multi-level nestedrm -rfscenarios. - Binary Search Query:
fsmon queryuses binary search on approximately time-sorted log files, narrowing the scan range to O(log N) seek operations. Combined withexpand_offset_backwardto catch minor out-of-order entries. - Rust + Tokio: Single-threaded async loop (
tokio::selectbetween fanotify fd and Ctrl+C signal). Background thread for proc connector. No complex concurrency â high efficiency instead.
Event Mask Strategy
fsmon uses a two-tier marking strategy:
- FAN_MARK_FILESYSTEM (preferred): Marks the entire mount point covering the target path â no race window for newly created files. Falls back if
EXDEV(btrfs subvolumes). - Inode mark fallback: Marks individual directories one by one, with recursive traversal for
--recursivemode. Dynamically marks newly created directories in real-time.
Event Types
Default captures 8 core events. Use --all-events for all 14.
Default Events (8):
| Event | Description |
|---|---|
| CLOSE_WRITE | File closed after write (best "modified" signal) |
| ATTRIB | Metadata changed (permissions, timestamps, owner) |
| CREATE | File/directory created |
| DELETE | File/directory deleted |
| DELETE_SELF | The monitored file/directory itself was deleted |
| MOVED_FROM | File moved out of monitored directory |
| MOVED_TO | File moved into monitored directory |
| MOVE_SELF | The monitored file/directory itself was moved |
Additional Events (6, via --all-events):
| Event | Description |
|---|---|
| ACCESS | File read |
| MODIFY | File content written (very noisy) |
| OPEN | File/directory opened |
| OPEN_EXEC | File opened for execution |
| CLOSE_NOWRITE | Read-only file closed |
| FS_ERROR | Filesystem error (Linux 5.16+) |