- Published on
Inotify Linux
You must have seen programs where one program is editing a file and it reflects in another program recognizes the file has been edited. Some examples would be your text editors, tail command, nodemon etc.
But how does it know when a file is modified? Does it keep comparing the contents that it has locally and on disk in a loop? Does it poll it every X seconds and updates it?
The general problem is, how to know immediately when something has changed in the system. The first thing that comes to our mind is polling. In polling, we try to check if there is an update in a forever-running while loop. We will immediately get notified when something changes. What if we want to monitor a folder or bunch of files for a change? Doing the check ourselves, sometimes wrongly, will severely impact performance. Here is where inotify comes in and makes it easier to watch filesystem changes.
inotify
inotify is a Linux kernel API where we can get notified when something changes in the filesystem. These changes are notified via inotify events.
You might want to read about inodes before proceeding further.
When a file is updated, its corresponding inode gets updated. The size of the file, pointers to the data block, access time all gets updated because the access to the file happens via inodes. Whenever this happens, Linux kernel notifies other programs using events saying that something has changed. So the program that monitors a file or folder looks something like this
# psudo code
watcher = watch("/tmp"); // Watch tmp dir
while (true) {
event = read_event(watcher); // Checks if there is any event in the queue. If there is not any, it doesn't read anything and continues.
if (event) {
// do something with the event
}
}
Case study - How does tail command work
The tail command in Linux follows a given file and prints the changes whenever a text is appended to the file being followed. An old version of tail command used polling to see if there were changes made to the file and print the changes. Later, it was enhanced to use inotify.
tail command can be used to tail and follow multiple files. tail -f <first_file> <second_file> ..
. It runs in a while loop in which, it checks for each file if there are any inotify event. If there is an event, it checks for the mtime (called last modified time in the stat
command) and checks if it was modified. In case it identifies that the file is modified, it does current_size - previous_size
and prints out the changes. The current_size is taken again from the stat
command and previous_size is stored locally. The source code is hosted at https://github.com/coreutils/coreutils/blob/master/src/tail.c#L1122.
Example program
A sample program with inotify API taken from https://linuxhint.com/inotify_api_c_language/ to watch /tmp
dir. Run the program in one terminal and try playing with files in tmp folder in another.
This is how other programs make use of inotify API to detect changes and act on them. Text editors, filebeat, nodemon make use of this API to function efficiently.
#include<stdio.h>
#include<sys/inotify.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<fcntl.h>
#define MAX_EVENTS 1024
#define LEN_NAME 16
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME ))
int fd, wd;
void sig_handler(int sig) {
inotify_rm_watch( fd, wd );
close( fd );
exit( 0 );
}
int main() {
char *path_to_be_watched = "/tmp";
signal(SIGINT,sig_handler);
fd = inotify_init();
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) exit(2);
wd = inotify_add_watch(fd,path_to_be_watched,IN_MODIFY | IN_CREATE | IN_DELETE);
if(wd == -1) {
printf("Could not watch : %s\n", path_to_be_watched);
} else {
printf("Watching : %s\n", path_to_be_watched);
}
while(true) {
int i=0,length;
char buffer[BUF_LEN];
// There could be multiple events. This length is the total length read from the buffer.
length = read(fd,buffer,BUF_LEN);
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( "The directory %s was created.\n", event->name );
}
else {
printf( "The file %s was created.\n", event->name );
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was deleted.\n", event->name );
}
else {
printf( "The file %s was deleted.\n", event->name );
}
}
else if ( event->mask & IN_MODIFY ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was modified.\n", event->name );
}
else {
printf( "The file %s was modified.\n", event->name );
}
}
}
i += EVENT_SIZE + event->len;
}
}
}