Monitor Linux File System Events with Inotify
By Eli M. Dow
2005-05-31
Using inotify in a simple application
To illustrate the use of inotify, I'll show how to construct a sample program that monitors an arbitrary directory (or simply a single file) for file system events. I'll begin at a high level to show how easy inotify makes file system monitoring. Main method This simple example shows how easy it can be to set up a watch on an arbitrary directory. We'll look at the major helper routines a bit later on. Get the sample code used in these illustrations in the Download section of this article. Listing 1. Setting up a watch on a directory
/* This program will take as argument a directory name and monitor it,
printing event notifications to the console.
*/
int main (int argc, char **argv)
{
/* This is the file descriptor for the inotify device */
int inotify_fd;
/* First we open the inotify dev entry */
inotify_fd = open_inotify_dev();
if (inotify_fd < 0)
{
return 0;
}
/* We will need a place to enqueue inotify events,
this is needed because if you do not read events
fast enough, you will miss them.
*/
queue_t q;
q = queue_create (128);
/* Watch the directory passed in as argument
Read on for why you might want to alter this for
more efficient inotify use in your app.
*/
watch_dir (inotify_fd, argv[1], ALL_MASK);
process_inotify_events (q, inotify_fd);
/* Finish up by destroying the queue, closing the fd,
and returning a proper code
*/
queue_destroy (q);
close_inotify_dev (inotify_fd);
return 0;
}
|
Important helper methods The following are the most important helper routines common to every inotify-based application: - The opening of the inotify device for reading
- The queuing of events that are read from that device
- The actual per-event handler that allows your application to do something useful with the event notification
I won't go into the details of queuing events, since several strategies can be used. The sample code provided shows one such method; more advanced multi-threaded approaches can and have been implemented elsewhere. In those implementations, a reader thread simply performs a select() on the inotify device and then copies events to some thread-shared storage (or something like Glib's asynchronous message queues) where a handler thread acts to process them later. Listing 2. Opening the inotify device
/* This simply opens the inotify node in dev (read only) */
int open_inotify_dev ()
{
int fd;
fd = open("/dev/inotify", O_RDONLY);
if (fd < 0)
{
perror ("open(\"/dev/inotify\", O_RDONLY) = ");
}
return fd;
}
|
This should look familiar to anyone who has done any programming with files on Linux systems. Listing 3. The actual event-handling routine
/* This method does the dirty work of determining what happened,
then allows us to act appropriately
*/
void handle_event (struct inotify_event *event)
{
/* If the event was associated with a filename, we will store it here */
char * cur_event_filename = NULL;
/* This is the watch descriptor the event occurred on */
int cur_event_wd = event->wd;
if (event->len)
{
cur_event_filename = event->filename;
}
printf("FILENAME=%s\n", cur_event_filename);
printf("\n");
/* Perform event dependent handler routines */
/* The mask is the magic that tells us what file operation occurred */
switch (event->mask)
{
/* File was accessed */
case IN_ACCESS:
printf("ACCESS EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was modified */
case IN_MODIFY:
printf("MODIFY EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File changed attributes */
case IN_ATTRIB:
printf("ATTRIB EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was closed */
case IN_CLOSE:
printf("CLOSE EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was opened */
case IN_OPEN:
printf("OPEN EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was moved from X */
case IN_MOVED_FROM:
printf("MOVE_FROM EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was moved to X */
case IN_MOVED_TO:
printf("MOVE_TO EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* Subdir was deleted */
case IN_DELETE_SUBDIR:
printf("DELETE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was deleted */
case IN_DELETE_FILE:
printf("DELETE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* Subdir was created */
case IN_CREATE_SUBDIR:
printf("CREATE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* File was created */
case IN_CREATE_FILE:
printf("CREATE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* Watched entry was deleted */
case IN_DELETE_SELF:
printf("DELETE_SELF EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* Backing FS was unmounted */
case IN_UNMOUNT:
printf("UNMOUNT EVENT OCCURRED: File \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
/* Too many FS events were received without reading them
some event notifications were potentially lost. */
case IN_Q_OVERFLOW:
printf("Warning: AN OVERFLOW EVENT OCCURRED: \n");
break;
case IN_IGNORED:
printf("IGNORED EVENT OCCURRED: \n");
break;
/* Some unknown message received */
default:
printf ("UNKNOWN EVENT OCCURRED for file \"%s\" on WD #%i\n",
cur_event_filename, cur_event_wd);
break;
}
}
|
Within each case statement you are free to execute any method you have implemented that suits your needs. With respect to performance monitoring, you can determine which files are read most frequently and the duration they were open. This kind of monitoring is handy, because under some circumstances, if a file is read repeatedly by an application in a small period of time, it may provide a performance enhancement to cache the file in memory rather than going back to the disk. It's easy to come up with other examples of event-specific handlers that perform interesting actions. As an example, if you were implementing a metadata storage index for your underlying file system, you could look for file-creation events and trigger a metadata mining operation on that file a short time later. In the context of security, if a file was written in a directory that no one should be writing to, you could trigger some form of system alert. A whole range of interesting opportunities present themselves. It is important to note that inotify supports a lot of very fine-grained events -- for example, CLOSE versus CLOSE_WRITE. Many of the events listed in the code in this article are probably not something you wish to see each time your code is run. In fact, whenever possible, you can and should request just the subset of events that are useful for your application. The code provided with this article shows many of the events by using the full mask (as performed near line 51 of the main method in our downloadable sample code [see Resources], or line 29 in Listing 1, above) strictly for testing purposes. Application programmers will in general want to be much more selective, and you will need a more specific mask to suit your needs. This will then allow you to remove uninteresting items from the catch statement in the handle_event() method shown earlier.
Tutorial Pages:
»
This next-gen, dnotify replacement meets file system event-monitoring needs in the 2.6 kernel
»
Why inotify?
»
Installing inotify
» Using inotify in a simple application
»
Conclusion
»
Resources
First published by IBM DeveloperWorks
|

|
|