Linux Kernel Inotify Subsystem
Gabriel M.
Linux Systems Engineer | IT Infrastructure | Security | Virtualization | Automation | AI | C and Shell Scripting
When dealing with Unix-style filesystems there must be a data structure that is capable of describing an object from that filesystem, such as a file or a directory.
Linux is no exception, so here comes the Inode.
The data it represents includes access time, modification time, file owner, permissions and much more.
Directories are a special type of file. They consist in lists of names, or labels, assigned to inodes in the filesystem. In addition, they also need means of representing themselves and how to get to their parent level. Here comes the "./" and the "../", respectively.
What if you need a way of keeping track of all these possible metadata operations on files and directories?
Well, if that is the case, then you are thinking about the Kernel's Inotify subsystem. It acts as an extension to the filesystem, which provides information on changes to that filesystem. It is worth mentioning that the current inotify is a replacement for the dnotify, as of kernel version 2.6.13 (29-08-2005).
An example of the importance of the inotify subsystem relies on the simple fact that your file explorer (Dolphin, Nautilus, Thunar, Xfe, etc.) will simply update and redraw its view based on file/directory changes. Like the "update" on the total directory space, file modification time, etc.
To see inotify in action, here is a simple program that prints to the screen any modification you make to the provided file as the command line argument.
/* * Test kernel's inotify subsystem */ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/inotify.h> #ifndef MAXIMUM_LENGHT #define MAXIMUM_LENGHT 255 #endif #ifndef BUFFER #define BUFFER (2048) #endif static void printEvent(struct inotify_event *); int main(int argc, char *argv[]) { int inotify_fd; int watcher; int watched; char buffer[BUFFER]; ssize_t num_read; char *ptr; struct inotify_event *event; if (argc < 2 || strcmp(argv[1], "--help") == 0 ) fprintf(stdout, "Usage: %s file|dir\n", argv[0]); // ask for our inotify instance inotify_fd = inotify_init(); // tell the kernel what file(s) are of interest (from cmd line) for (watched = 1; watched < argc; watched++) { /* Watch all events */ watcher = inotify_add_watch(inotify_fd, argv[watched], IN_ALL_EVENTS); if (watcher == -1) { fprintf(stderr,"error:inotify_add_watch"); exit(EXIT_FAILURE); } fprintf(stdout,"%s watched by %d\n", argv[watched], watcher); } // read events forever for (;;) { num_read = read(inotify_fd, buffer, BUFFER); if (num_read == 0) { fprintf(stderr,"read() from inotify fd returned 0!"); exit(EXIT_FAILURE); } if (num_read == -1) { fprintf(stderr, "read error!"); exit(EXIT_FAILURE); } fprintf(stdout,"Read %ld bytes from inotify descriptor\n", (long) num_read); /* Process all of the events in buffer returned by read() */ for (ptr = buffer; ptr < buffer + num_read; ) { event = (struct inotify_event *) ptr; printEvent(event); /* go to next event */ ptr += sizeof(struct inotify_event) + event->len; } } exit(EXIT_SUCCESS); } static void printEvent(struct inotify_event *e) { // Do we have any "saved" information? if (e->cookie > 0) fprintf(stdout, "cookie = 4%d; ", e->cookie); fprintf(stdout, "Inotify Bit Mask = "); if (e->mask & IN_OPEN) fprintf(stdout, "IN_OPEN"); if (e->mask & IN_ISDIR) fprintf(stdout, "IN_ISDIR"); if (e->mask & IN_ACCESS) fprintf(stdout, "IN_ACCESS"); if (e->mask & IN_ATTRIB) fprintf(stdout, "IN_ATTRIB"); if (e->mask & IN_DELETE) fprintf(stdout, "IN_DELETE"); if (e->mask & IN_CREATE) fprintf(stdout, "IN_CREATE"); if (e->mask & IN_MODIFY) fprintf(stdout, "IN_MODIFY"); if (e->mask & IN_IGNORED) fprintf(stdout, "IN_IGNORED"); if (e->mask & IN_UNMOUNT) fprintf(stdout, "IN_UNMOUNT"); if (e->mask & IN_MOVED_TO) fprintf(stdout, "IN_MOVED_TO"); if (e->mask & IN_MOVE_SELF) fprintf(stdout, "IN_MOVE_SELF"); if (e->mask & IN_Q_OVERFLOW) fprintf(stdout, "IN_Q_OVERFLOW"); if (e->mask & IN_MOVED_FROM) fprintf(stdout, "IN_MOVED_FROM"); if (e->mask & IN_DELETE_SELF) fprintf(stdout, "IN_DELETE_SELF"); if (e->mask & IN_CLOSE_WRITE) fprintf(stdout, "IN_CLOSE_WRITE"); if (e->mask & IN_CLOSE_NOWRITE) fprintf(stdout, "IN_CLOSE_NOWRITE"); fprintf(stdout, "\n"); /* Is there a name? */ if ( e->len > 0) fprintf(stdout, " name = %s\n", e->name);
}
To run this program, you just need to compile it
gcc -o inotify_test inotify_example.c
And fire it up against any file you want...
./inotify_test inotify_example.c
If you simply save that file being watched, you can see the program printing information regarding changes.
inotify_example.c watched by 1 Read 16 bytes from inotify descriptor Inotify Bit Mask = IN_OPEN Read 16 bytes from inotify descriptor Inotify Bit Mask = IN_ACCESS Read 16 bytes from inotify descriptor Inotify Bit Mask = IN_CLOSE_NOWRITE Read 32 bytes from inotify descriptor Inotify Bit Mask = IN_MODIFY Inotify Bit Mask = IN_OPEN Read 16 bytes from inotify descriptor Inotify Bit Mask = IN_MODIFY Read 16 bytes from inotify descriptor Inotify Bit Mask = IN_CLOSE_WRITE