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:
# 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.txtUnderstanding the Linux inotify API Functions
`inotify_init1(int flags)`: Creates an inotify instance and returns a standard file descriptor. Passing
IN_NONBLOCKenables 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_eventstructures 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:
#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:
# 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
Comments and corrections