Building responsive system daemons, auto-reloading build tools, and security auditing agents requires a mechanism to detect file creation, modification, move, and deletion events instantly without wasteful CPU polling loop iterations.

The Linux kernel provides the inotify API (sys/inotify.h)—an event-driven subsystem that pushes file system notifications directly to user-space application file descriptors. In this article, we write a robust C program that monitors target directories for real-time file system mutations.

Quick Compilation and Execution Sequence

Compile the C monitor program and watch a target directory for file system changes:

Terminal Commandsbash
# 1. Compile the inotify event watcher
gcc -O2 inotify_watcher.c -o inotify_watcher
 
# 2. Run the event monitor against a target directory
./inotify_watcher /tmp/test_dir
 
# 3. Test event triggers in a secondary terminal session
touch /tmp/test_dir/sample.txt
echo "hello inotify" >> /tmp/test_dir/sample.txt
mv /tmp/test_dir/sample.txt /tmp/test_dir/renamed.txt
rm /tmp/test_dir/renamed.txt

Understanding the Linux inotify API Functions

  • `inotify_init1(int flags)`: Creates an inotify instance and returns a standard file descriptor. Passing IN_NONBLOCK enables non-blocking asynchronous reads.

  • `inotify_add_watch(int fd, const char *pathname, uint32_t mask)`: Registers a watch item on a file or directory path and returns a Watch Descriptor (wd).

  • `inotify_rm_watch(int fd, int wd)`: Unregisters an existing watch descriptor from the kernel queue.

  • `read(int fd, void *buf, size_t count)`: Reads one or more struct inotify_event structures from the inotify queue.

Complete C Event Monitor Source Code (inotify_watcher.c)

This C program initializes an inotify queue, monitors directory events (IN_CREATE, IN_MODIFY, IN_DELETE, IN_MOVED_FROM, IN_MOVED_TO), and dynamically parses variable-length inotify_event structures:

inotify_watcher.cc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
 
#define EVENT_SIZE  (sizeof(struct inotify_event))
#define BUF_LEN     (1024 * (EVENT_SIZE + 16))
 
int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <directory_to_watch>
", argv[0]);
        return 1;
    }
 
    const char *target_dir = argv[1];
 
    // Initialize inotify instance
    int fd = inotify_init();
    if (fd < 0) {
        perror("inotify_init failed");
        return 1;
    }
 
    // Add watch for file creation, modification, deletion, and renaming
    uint32_t mask = IN_CREATE | IN_MODIFY | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO;
    int wd = inotify_add_watch(fd, target_dir, mask);
    if (wd < 0) {
        perror("inotify_add_watch failed");
        close(fd);
        return 1;
    }
 
    printf("Monitoring file system events in directory: '%s'
", target_dir);
 
    char buffer[BUF_LEN];
 
    while (1) {
        int length = read(fd, buffer, BUF_LEN);
        if (length < 0) {
            perror("read error");
            break;
        }
 
        int i = 0;
        while (i < length) {
            struct inotify_event *event = (struct inotify_event *) &buffer[i];
 
            if (event->len) {
                if (event->mask & IN_CREATE) {
                    if (event->mask & IN_ISDIR)
                        printf("Directory created: '%s'
", event->name);
                    else
                        printf("File created: '%s'
", event->name);
                } else if (event->mask & IN_MODIFY) {
                    printf("File modified: '%s'
", event->name);
                } else if (event->mask & IN_DELETE) {
                    if (event->mask & IN_ISDIR)
                        printf("Directory deleted: '%s'
", event->name);
                    else
                        printf("File deleted: '%s'
", event->name);
                } else if (event->mask & IN_MOVED_FROM) {
                    printf("File renamed/moved out: '%s'
", event->name);
                } else if (event->mask & IN_MOVED_TO) {
                    printf("File renamed/moved in: '%s'
", event->name);
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }
 
    // Cleanup watch descriptor and file descriptor
    inotify_rm_watch(fd, wd);
    close(fd);
    return 0;
}

Kernel Resource Tuning and Limits

If your application monitors large folder trees, you may encounter ENOSPC errors. Inspect and tune system-wide inotify limits under sysctl:

Terminal Commandsbash
# Inspect maximum watch limit per user
cat /proc/sys/fs/inotify/max_user_watches
 
# Increase watch limit for large projects (e.g. 524288 watches)
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p