2026-06-16
Everyone reaches for inotifywait when they want to know "what just touched this directory?" — and then hits the same wall: inotify needs you to know the path up front. Want to catch any process writing anywhere on the system? You'd have to walk every mount, register every directory, and you'd still miss anything created after you started watching.
The kernel has a second filesystem-notification API almost nobody uses: fanotify. It's mount-wide, kernel-side, and tells you the PID and filename of whatever just hit the disk. fatrace is the 200-line C wrapper that turns that API into a one-liner.
$ sudo apt install fatrace # Debian/Ubuntu
$ sudo dnf install fatrace # Fedora
$ sudo fatrace
chrome(38214): RO /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
journald(412): W /var/log/journal/abc.../system.journal
slack(91022): RWO+ /home/shaun/.config/Slack/Cache/data_0
gvfsd(2851): O /run/user/1000/gvfs
The flags after the PID are the operation: Read, Write, Open, Close, and + means open+close were merged. No paths registered. No watcher setup. Every process, every file, system-wide.
"What is making my SSD chatter when the machine is idle?"
$ sudo fatrace -t -f W -s 60 | awk '{print $1}' | sort | uniq -c | sort -rn | head
8421 firefox(31204):
1108 journald(412):
422 chrome(38214):
188 prometheus(8801):
Timestamped (-t), writes only (-f W), 60-second window (-s 60). You now know which process is writing the most without strace-ing every PID.
"Which config file did this daemon just read on reload?"
$ sudo fatrace -c & # -c restricts to the current mount
$ systemctl reload nginx
$ fg # Ctrl-C
nginx(1827): O /etc/nginx/nginx.conf
nginx(1827): RO /etc/nginx/conf.d/proxy.conf
nginx(1827): RO /etc/nginx/mime.types
You just answered a question that would otherwise mean strace -f -e trace=openat -p $(pidof nginx) with output you'd have to grep through.
"What's touching /var/lib right now?" Pipe it through grep, since fatrace doesn't filter by path (mount, yes; path, no — that's the deliberate tradeoff):
$ sudo fatrace | grep -F /var/lib/postgresql
postgres(2914): W /var/lib/postgresql/16/main/pg_wal/000000010000...
strace -f -e trace=file is a per-process tool. You have to know which PID(s) to attach to, and attaching slows the target down significantly because every syscall round-trips through ptrace. fatrace runs in the kernel, costs single-digit percent overhead, and catches every process — including ones that fork after you started, ones you don't have a name for, and short-lived ones that strace would never have time to attach to.
auditd is the "real" answer for security logging — but configuring an audit rule, parsing ausearch output, and remembering the syscall numbers takes longer than the question is worth. fatrace is for interactive debugging: you run it, you get human-readable lines on stdout, you ctrl-C. No daemon, no rules file, no log rotation. When you're done playing detective, it leaves no trace.
Limitations worth knowing: it can't see network filesystems (fanotify is local-only), it needs CAP_SYS_ADMIN (so, root), and on systems with millions of file ops per second, you'll want -f W or a grep filter so you don't drown.
fatrace turns the kernel's fanotify API into a one-line system-wide filesystem tracer.
