When you call read() on an inotify file descriptor, the kernel fills your buffer with one or more inotify_event structures. Here is the structure definition:
struct inotify_event {
int wd; /* Watch descriptor โ which path caused this event */
uint32_t mask; /* What happened โ bitmask of event bits */
uint32_t cookie; /* Links related events (used for renames) */
uint32_t len; /* Length of the 'name' field, including null bytes */
char name[]; /* Optional: name of the file that changed (flexible array) */
};
| int wd 4 bytes |
uint32_t mask 4 bytes |
uint32_t cookie 4 bytes |
uint32_t len 4 bytes |
char name[] 0 or more bytes (variable) |
Let’s understand each field:
| Field | What It Tells You |
|---|---|
| wd | The watch descriptor that caused this event. Use this to look up which path is being monitored (your program must keep a mapping of wd โ path). |
| mask | Bitmask of what happened. Check with bitwise AND: if (event->mask & IN_CREATE). |
| cookie | Non-zero only for rename events. The IN_MOVED_FROM and IN_MOVED_TO events of the same rename have identical cookie values. Zero for all other events. |
| len | Number of bytes allocated for the name field, including the null terminator and any padding. May be 0 if the event is for the watched object itself (not a file inside it). |
| name | The filename (not the full path) of the file that triggered the event, when the event is for a file inside a watched directory. Empty (len=0) when the event is for the watched path itself. |
A single read() call can return multiple events in one shot. They are packed back-to-back in your buffer. Each event is variable-size because of the name field.
| Event 1: wd=1 | mask=IN_CREATE | cookie=0 | len=8 | name=”foo.txt\0″ |
| Event 2: wd=1 | mask=IN_MODIFY | cookie=0 | len=8 | name=”foo.txt\0″ |
| Event 3: wd=2 | mask=IN_DELETE | cookie=0 | len=12 | name=”oldfile.txt\0″ |
To advance from one event to the next inside the buffer:
/* Move pointer forward by fixed header size + variable name size */
p += sizeof(struct inotify_event) + event->len;
event->len to move past the name field. Forgetting this causes misaligned reads and crashes.The minimum safe buffer size to hold at least one event is:
#include <limits.h> /* for NAME_MAX */
/* NAME_MAX = 255 on Linux (max filename length)
+1 for the null terminator
+ fixed header size */
size_t min_buf = sizeof(struct inotify_event) + NAME_MAX + 1;
/* = 16 + 255 + 1 = 272 bytes minimum */
In practice, use a much larger buffer (4096 or 8192 bytes) so that one read() call returns many events at once, which is more efficient.
read() returns -1 with errno = EINVAL. This changed in kernel 2.6.21 โ older kernels returned 0 instead, which was confusing.You can query how many bytes are currently available without reading them:
#include <sys/ioctl.h>
int bytesAvail;
ioctl(fd, FIONREAD, &bytesAvail);
printf("%d bytes of events are ready to read\n", bytesAvail);
Ordering guarantee: Events are always delivered in the order they happened. So for a rename, you are guaranteed to read IN_MOVED_FROM before IN_MOVED_TO.
Coalescing: If the same event happens twice in a row on the same file (same wd, mask, cookie, and name), the kernel merges them into one. For example, if a file is modified 100 times in quick succession, you might receive fewer than 100 IN_MODIFY events. This saves kernel memory but means you cannot use inotify to count how many times something happened.
Because the inotify instance is a regular file descriptor, you can use it with select(), poll(), or epoll. This lets you watch for inotify events and other fd events (like socket data) in the same event loop.
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <unistd.h>
#define BUF_SIZE 4096
int main(void)
{
int fd, wd;
char buf[BUF_SIZE];
fd_set readfds;
struct timeval timeout;
struct inotify_event *event;
char *p;
fd = inotify_init1(IN_CLOEXEC);
if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); }
wd = inotify_add_watch(fd, "/tmp/testdir", IN_ALL_EVENTS);
if (wd == -1) { perror("inotify_add_watch"); close(fd); exit(EXIT_FAILURE); }
printf("Watching /tmp/testdir. Times out after 5 seconds of no activity.\n");
for (;;) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/* Timeout: 5 seconds */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int ready = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (ready == 0) {
printf("No events in 5 seconds. Still watching...\n");
continue;
}
if (ready == -1) { perror("select"); break; }
if (FD_ISSET(fd, &readfds)) {
ssize_t numRead = read(fd, buf, BUF_SIZE);
for (p = buf; p < buf + numRead; ) {
event = (struct inotify_event *) p;
printf("Event mask: 0x%x", event->mask);
if (event->len > 0)
printf(" file: %s", event->name);
printf("\n");
p += sizeof(struct inotify_event) + event->len;
}
}
}
close(fd);
return 0;
}
This is a complete program that watches any path given on the command line and prints every event in human-readable form. Compile once, use for debugging any inotify project.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <limits.h>
#include <unistd.h>
/* Minimum buffer guaranteed to hold one event */
#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
/* Print a human-readable description of a single event */
static void display_event(struct inotify_event *i)
{
printf(" wd=%-3d ", i->wd);
if (i->cookie > 0)
printf("cookie=%-5d ", i->cookie);
printf("mask=");
if (i->mask & IN_ACCESS) printf("IN_ACCESS ");
if (i->mask & IN_ATTRIB) printf("IN_ATTRIB ");
if (i->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE ");
if (i->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE ");
if (i->mask & IN_CREATE) printf("IN_CREATE ");
if (i->mask & IN_DELETE) printf("IN_DELETE ");
if (i->mask & IN_DELETE_SELF) printf("IN_DELETE_SELF ");
if (i->mask & IN_IGNORED) printf("IN_IGNORED ");
if (i->mask & IN_ISDIR) printf("IN_ISDIR ");
if (i->mask & IN_MODIFY) printf("IN_MODIFY ");
if (i->mask & IN_MOVE_SELF) printf("IN_MOVE_SELF ");
if (i->mask & IN_MOVED_FROM) printf("IN_MOVED_FROM ");
if (i->mask & IN_MOVED_TO) printf("IN_MOVED_TO ");
if (i->mask & IN_OPEN) printf("IN_OPEN ");
if (i->mask & IN_Q_OVERFLOW) printf("IN_Q_OVERFLOW ");
if (i->mask & IN_UNMOUNT) printf("IN_UNMOUNT ");
printf("\n");
if (i->len > 0)
printf(" name=%s\n", i->name);
}
int main(int argc, char *argv[])
{
int inotifyFd, wd, j;
char buf[BUF_LEN] __attribute__((aligned(8)));
ssize_t numRead;
char *p;
struct inotify_event *event;
if (argc < 2) {
fprintf(stderr, "Usage: %s path1 [path2 ...]\n", argv[0]);
exit(EXIT_FAILURE);
}
inotifyFd = inotify_init1(IN_CLOEXEC);
if (inotifyFd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); }
for (j = 1; j < argc; j++) {
wd = inotify_add_watch(inotifyFd, argv[j], IN_ALL_EVENTS);
if (wd == -1) { perror("inotify_add_watch"); exit(EXIT_FAILURE); }
printf("Watching %s (wd=%d)\n", argv[j], wd);
}
for (;;) {
numRead = read(inotifyFd, buf, BUF_LEN);
if (numRead == 0) { fprintf(stderr, "read() returned 0\n"); break; }
if (numRead == -1) { perror("read"); break; }
printf("\nRead %ld bytes, containing events:\n", (long)numRead);
for (p = buf; p < buf + numRead; ) {
event = (struct inotify_event *) p;
display_event(event);
p += sizeof(struct inotify_event) + event->len;
}
}
close(inotifyFd);
return 0;
}
# Compile
gcc -o inotify_demo inotify_demo.c
# Watch two directories
mkdir -p /tmp/dir1 /tmp/dir2
./inotify_demo /tmp/dir1 /tmp/dir2 &
# Generate events:
touch /tmp/dir1/test.txt # IN_CREATE, IN_OPEN, IN_ATTRIB, IN_CLOSE_WRITE
echo hi > /tmp/dir1/test.txt # IN_MODIFY, IN_CLOSE_WRITE
mv /tmp/dir1/test.txt /tmp/dir2/ # IN_MOVED_FROM (dir1) + IN_MOVED_TO (dir2)
rm /tmp/dir2/test.txt # IN_DELETE
Use IN_NONBLOCK so your program can do other work while waiting for events.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/inotify.h>
#include <unistd.h>
#define BUF_SIZE 4096
int main(void)
{
int fd, wd;
char buf[BUF_SIZE];
ssize_t numRead;
struct inotify_event *event;
char *p;
/* IN_NONBLOCK: read() won't block if no events are available */
fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); }
wd = inotify_add_watch(fd, "/tmp/testdir", IN_CREATE | IN_MODIFY);
if (wd == -1) { perror("inotify_add_watch"); close(fd); exit(EXIT_FAILURE); }
printf("Non-blocking watch on /tmp/testdir\n");
for (int i = 0; i < 10; i++) { /* poll 10 times */
numRead = read(fd, buf, BUF_SIZE);
if (numRead == -1) {
if (errno == EAGAIN) {
/* No events right now โ go do other work */
printf("No events yet. Doing other work...\n");
sleep(1);
continue;
}
perror("read");
break;
}
for (p = buf; p < buf + numRead; ) {
event = (struct inotify_event *) p;
if (event->len > 0)
printf("Event: %s\n", event->name);
p += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
return 0;
}
Learn how the kernel limits inotify usage and how to configure those limits.
