Skip to content

Repository files navigation

Fanotify

Safe Ruby bindings for Linux fanotify

Gem Version Downloads CI Ruby Version Platform License

Website · Features · Installation · Quick Start · Permission Events · Development


Fanotify provides Ruby 3.2+ C bindings for Linux fanotify(7). It monitors filesystem activity, reports file identity and name records, and safely handles permission decisions that can block other processes.

Warning

Permission events block the process accessing a file until userspace responds. A missing response or filesystem access from the handler can deadlock a process or the machine. Fanotify::Notifier#each_event fails open and closes event descriptors, but handlers must still follow the safety rules below.

Features

  • High-level file watching with Fanotify.watch
  • Notification and permission event groups
  • Safe allow, deny, deferred, timeout, and fail-open decisions
  • FID, filename, rename, target FID, and PIDFD records
  • Filesystem error and queue-overflow reporting
  • Linux 6.14 pre-access range events for hierarchical storage managers
  • Native extension with no runtime gem dependencies

Installation

Add the gem to your Gemfile:

gem "fanotify"

Then install it:

bundle install

The native extension is built during installation.

Requirements

  • Linux 5.1+ with CONFIG_FANOTIFY
  • Ruby 3.2+
  • A C compiler and Linux UAPI headers
  • CAP_SYS_ADMIN for mount/filesystem marks and all permission events
Feature Minimum Linux version
FID reporting 5.9
Unprivileged FID notification groups 5.13
PIDFD reports 5.15
Rename and target FID reports 5.17
Experimental pre-access events 6.14

Fanotify.supported? checks whether the syscall exists. It does not prove that the current process can create a particular group or mark.

Quick Start

Watch a directory for ordinary notifications:

require "fanotify"

Fanotify.watch("/data", events: %i[create delete modify]) do |event|
  puts "#{event.mask.inspect} #{event.name}"
end

Use Notifier when you need explicit classes, reports, or mark flags:

Fanotify::Notifier.open(class: :notif, report: %i[fid dfid_name]) do |notifier|
  notifier.mark(:add, "/data", events: %i[create delete event_on_child], only_dir: true)
  notifier.each_event { |event| puts event.name }
end

Event#fid, #dfid, #old_dfid, and #new_dfid are comparable Fanotify::FileHandle values printable as hex. Version 1 does not open them with open_by_handle_at(2).

Permission Events

Permission marks require CAP_SYS_ADMIN:

Fanotify::Notifier.open(class: :content, response_timeout: 5.0) do |notifier|
  notifier.exclude_self!
  notifier.mark(:add, "/data", events: %i[open_perm event_on_child], only_dir: true)

  notifier.each_event do |event|
    event.deny! if event.path&.end_with?(".secret")
    # No explicit decision means allow! when the block exits.
  end
end

Failure Behavior

The default behavior is fail-open:

  • An unanswered permission event is allowed after the handler block returns.
  • A handler exception allows the event before re-raising the same exception.
  • defer! registers an asynchronous decision. The watchdog allows it after five seconds by default. response_timeout: nil disables the deadline, while GC fail-open cleanup remains active.
  • pending_events exposes live deferred events.
  • close allows unanswered events and interrupts threads blocked in read_events with Fanotify::Error.
  • A failed response write closes the entire notification group, releasing every process waiting on it.

on_error: :deny changes only the handler-exception decision. It is opt-in because application errors then deny filesystem access.

Handler Safety

  • Load code, resolve configuration, and open log destinations before adding a permission mark. Do not call require, open logs, or lazily load files inside the handler.
  • Call exclude_self! to filter self-generated events already read by the notifier. It cannot rescue a handler that blocks itself while opening another marked file on the same thread.
  • Inspect event.file, which duplicates the kernel-opened descriptor and closes with the event. Do not reopen event.path for a security decision.
  • Treat event.path as informational. It comes from /proc/self/fd/N, reopening it has a TOCTOU race, and deleted paths keep the kernel's " (deleted)" suffix.
  • Keep handlers bounded. The kernel has no response timeout; the gem watchdog covers only events deferred through this notifier.

Pre-access Events

Linux 6.14 pre-access events expose the kernel-reported access range through Event#range_offset and Event#range_count. The range can be larger than the application-level read request. These events also require an HSM-capable filesystem such as ext4, XFS, or Btrfs. See examples/lazy_fetch.rb.

Platform Notes

Environment Behavior
macOS, BSD, Windows require "fanotify" succeeds and supported? is false; constructing a notifier raises Fanotify::UnsupportedError.
Docker or Podman Permission events and mount/filesystem marks normally require CAP_SYS_ADMIN; Docker Desktop runs inside a Linux VM.
GitHub-hosted runners Unit tests run normally; system tests use the included virtme-ng kernel VM.
WSL2 Availability depends on the kernel configuration and process capabilities.
Older Linux kernels Unsupported flags and combinations raise the kernel's Errno::* exception.

Examples

Development

bundle install
bundle exec rake test:unit
bundle exec rake rbs
sudo bundle exec rake test:system  # Linux with CAP_SYS_ADMIN

The system suite has hard timeouts and covers notifications, permission decisions, exception fail-open, deferred cleanup, FID names, unprivileged mode, queue overflow, and descriptor leak detection.

Run it in a kernel VM with:

tools/vm/run.sh              # Linux 6.12
KVER=6.1 tools/vm/run.sh

Regenerate Ruby constant bindings with bundle exec rake gen:constants. Values are resolved from the target system's UAPI headers when the extension is built.

Reference throughput for 10,000 distinct FAN_OPEN FID events was 266,374 events/s on Ruby 3.4.10 and Linux 6.8 arm64 in a Docker Desktop VM. Benchmark the deployment host with bundle exec ruby benchmark/events.rb; handler work and event shape dominate real throughput.

Contributing

Bug reports and pull requests are welcome at https://github.com/ydah/fanotify.

License

Released under the MIT License.

About

Ruby bindings for Linux fanotify

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages