inotify
Idiomatic Rust wrapper around the Linux inotify API for watching files and directories for changes
Repository Health
Technical Analysis
inotify is a safe, idiomatic Rust wrapper around the Linux kernel’s inotify subsystem, letting applications watch files and directories for creation, modification, deletion, and other filesystem events without dealing directly with raw file descriptors or unsafe FFI calls.
The crate closely mirrors the underlying inotify API while adding Rust-native ergonomics: typed watch masks and event masks via bitflags, RAII cleanup of file descriptors and watch descriptors, and both a synchronous blocking read API and an optional async EventStream built on Tokio. It is Linux-specific by design, targeting developers who need direct, low-level access to inotify rather than a cross-platform notification abstraction.
What You Get
- An
Inotify::init()entry point that safely wrapsinotify_init1withIN_CLOEXECandIN_NONBLOCKset automatically - A
WatchesAPI for adding/removing watches on paths with typedWatchMaskflags (CREATE, MODIFY, DELETE, MOVE, ACCESS, and more) - Synchronous
read_events_blockingandread_events(non-blocking) methods for consuming raw kernel events into typedEventstructs - An optional
streamfeature (enabled by default) providingEventStream, a Tokio-backed async stream of inotify events viainto_event_stream - Typed
EventMaskflags (e.g.CREATE,DELETE,ISDIR,MOVE_SELF) so event matching is done with bitflag checks instead of raw integers - RAII-managed file descriptors (
FdGuard) and watch descriptors, so watches and the inotify instance are cleaned up automatically on drop
Common Use Cases
- Building a file-watcher or live-reload tool that reacts when source files in a directory change
- Implementing a lightweight config-reload mechanism that re-reads a config file when it’s modified
- Writing a sync/backup daemon that mirrors filesystem changes (create/delete/move) into another location
- Feeding filesystem change events into an async application via
EventStreamalongside other Tokio-based I/O
Under The Hood
Architecture — The crate is organized around a single Inotify handle (src/inotify.rs) that owns an Arc<FdGuard> wrapping the raw inotify file descriptor returned by inotify_init1. Watches are managed through a separate Watches struct (src/watches.rs) that holds a Weak reference back to the same FdGuard, so watch descriptors can be added or removed via inotify_add_watch/inotify_rm_watch without duplicating fd ownership. Reading events flows through util::read_into_buffer into src/events.rs, which parses the kernel’s packed inotify_event byte layout (fixed header plus a variable-length, NUL-padded name field) into safe Event/EventOwned structs via unsafe pointer arithmetic isolated to that one module. The optional stream.rs module layers a Tokio AsyncFd-based EventStream on top of the same blocking read primitives, so the sync and async paths share the same event-decoding logic rather than duplicating it.
Tech Stack — The crate targets Rust edition 2018 with a minimum supported Rust version of 1.70. Core runtime dependencies are inotify-sys (raw FFI bindings to the kernel syscalls), libc (for fcntl/O_NONBLOCK handling), and bitflags (for the typed WatchMask/EventMask constants). The stream feature, enabled by default, pulls in futures-util and tokio (with the net feature) to provide async event polling. Dev-dependencies (tempfile, maplit, rand, plus tokio with macros/rt-multi-thread/time) support the integration test suite. There is no build script or code generation step — the crate builds as a plain cargo build against these dependencies.
Code Quality — tests/main.rs contains a substantial integration test suite (595 lines, 16 #[test] functions) exercising watch add/remove, event masks, non-blocking reads, and the async stream path against real temporary files via tempfile. Source files are consistently documented with rustdoc comments that include compiling, runnable examples (visible throughout lib.rs, inotify.rs, and watches.rs), which also serve as doctests run by cargo test. Error handling is idiomatic: public methods return std::io::Error rather than panicking, and resource cleanup (file descriptors, watch descriptors) is handled via Drop implementations rather than requiring explicit close calls from callers. Unsafe code is present but narrowly scoped to the FFI boundary and buffer-parsing logic in events.rs and fd_guard.rs.
API Design — The public API is small and consistent: Inotify::init() to create a handle, .watches().add(path, mask) / .remove(descriptor) to manage watches, and read_events_blocking/read_events/into_event_stream to consume events, all returning io::Result. Naming maps closely to the underlying inotify concepts (WatchMask, EventMask, WatchDescriptor) which keeps the wrapper’s mental model close to the man page while still being idiomatic Rust (bitflags instead of raw ints, Result instead of errno). Getting started requires minimal boilerplate — a handle, a watch, and a read loop, as shown directly in the crate-level doctest — and the README plus two runnable examples (watch.rs, stream.rs) cover both the sync and async usage paths.