Watching file system events with inotify 08-28-2017, 03:53 PM
#1
If you're trying to write event-driven code that fires on certain file system events (e.g. file access, modification, creation, deletion) for projects like auto-build systems, event logs, or weird shit like this (which I should finish eventually), inotify is the tool for the job. If you're running any derivative of Unix (e.g. Linux or OSX) the inotify library is almost definitely on your system given it's part of glibc, so there's virtually no need to check for it.
To start, we need to include some headers and define some constants.
These headers provide all the functionality we need for the program.
Next we need to create our buffer and initialize inotify, checking for errors returned by the function.
After initializing, we can start watching directories using the inotify_add_watch() function. In our case, we'll make a function and call it using the first argument passed to our program (it's argv[1], as argv[0] is the program name). The mask in the third argument represents some of many event flags offered by the library, all of which you can find here.
As well, we need to call some cleanup functions at the end of main(), before we return.
Next, we can finally start getting event reports. We need to create an infinite loop to constantly check, two variables to hold the current size of the buffer and the current event index in the given iteration, and a final variable to hold the event itself.
After checking errors at the start of the loop, we can go through the available events, check their masks, and take whatever action we see fit from there. I choose to make this a separate function to keep things organized, but you can cram it into main if you'd like (but that doesn't mean you should
).
This serves as a working example (see this gist for the code), but we can go one better by recursing subdirectories (thread coming soon).
To start, we need to include some headers and define some constants.
Code:
// io & file functions
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
// system limits (e.g. file name length)
#include <limits.h>
// type aliases & inotify
#include <sys/types.h>
#include <sys/inotify.h>
// maximum number of concurrent events
#define MAX_EVENTS 256
// size of one inotify event
#define E_SIZE (sizeof (struct inotify_event))
// event buffer size; (size of event + max path length) * event max
#define BUF_LENGTH MAX_EVENTS*(E_SIZE+PATH_MAX)
Next we need to create our buffer and initialize inotify, checking for errors returned by the function.
Code:
int main(int argc,char **argv){
/* allocate memory for the size of inotify_event + the
maximum path length, MAX_EVENTS times */
char buf[BUF_LENGTH];
// file/watch descriptors
int fd,wd;
// initialize inotify, check for and reporting errors
if((fd=inotify_init())<0)
perror("Couldn't initialize inotify");
}
After initializing, we can start watching directories using the inotify_add_watch() function. In our case, we'll make a function and call it using the first argument passed to our program (it's argv[1], as argv[0] is the program name). The mask in the third argument represents some of many event flags offered by the library, all of which you can find here.
As well, we need to call some cleanup functions at the end of main(), before we return.
Code:
int add_watch_dir(int fd,char *dirname,uint32_t mask){
int wd;
/* add directory watch to the inotify file descriptor and
check for errors */
if((wd=inotify_add_watch(fd,dirname,mask))==-1)
perror("Failed to watch directory");
else
printf("Watching directory %s\n",dirname);
return wd;
}
int main(int argc,char **argv){
...
// check that a directory argument was supplied
if(argc<2){
fprintf(stderr,"No watch directory supplied\n");
return 1;
}
// call our new function, listening for create, modify, and delete events
wd=add_watch_dir(fd,argv[1],IN_CREATE|IN_MODIFY|IN_DELETE);
// rest of the program goes here
inotify_rm_watch(fd,wd);
close(fd);
return 0;
}
Next, we can finally start getting event reports. We need to create an infinite loop to constantly check, two variables to hold the current size of the buffer and the current event index in the given iteration, and a final variable to hold the event itself.
After checking errors at the start of the loop, we can go through the available events, check their masks, and take whatever action we see fit from there. I choose to make this a separate function to keep things organized, but you can cram it into main if you'd like (but that doesn't mean you should

Code:
// macro for reporting file or directory
#define target_ftype(e) (e->mask&IN_ISDIR?"directory":"file")
void handle_event(int fd,char *buf){
size_t idx=0,length=read(fd,buf,BUF_LENGTH); // buffer index and read length
struct inotify_event *event; // pointer to event
// report read() errors
if(length<0) perror("read");
// while the index is under the length read from fd
while(idx<length){
event=(struct inotify_event*) &buf[idx]; // get event at current index
if(!event->len) continue; // skip if the event is zero-length
if(event->mask&IN_CREATE) // check for create flag in mask
printf("%s %s created\n",target_ftype(event),event->name);
else if(event->mask&IN_MODIFY) // check for modify flag
printf("%s %s modified\n",target_ftype(event),event->name);
else if(event->mask&IN_DELETE) // check for delete flag
printf("%s %s deleted\n",target_ftype(event),event->name);
// add event size to idx
idx+=E_SIZE+event->len;
}
}
int main(int argc,char **argv){
...
while(1) handle_event(fd,buf);
inotify_rm_watch(fd,wd);
close(fd);
return 0;
}
This serves as a working example (see this gist for the code), but we can go one better by recursing subdirectories (thread coming soon).
(This post was last modified: 08-28-2017, 04:32 PM by Inori.)
It's often the outcasts, the iconoclasts ... those who have the least to lose because they
don't have much in the first place, who feel the new currents and ride them the farthest.
don't have much in the first place, who feel the new currents and ride them the farthest.