File system event monitoring is a necessity for many types of programs ranging from file managers to security tools, but dnotify -- the standard in earlier kernels -- had limitations that left us hoping for something better. With that in mind, meet inotify, a more modern file system event-monitoring alternative.
There are many reasons to use inotify instead of dnotify. The first is that dnotify requires opening one file descriptor for each directory that you intend to watch for changes. This can become quite costly when you are monitoring several directories at once, since it is possible to reach a per-process file descriptor limit.
Additionally, the file descriptor pins the directory, disallowing the backing device to be unmounted, which causes problems in scenarios involving removable media. When using inotify, if you are watching a file on a file system that is unmounted, the watch is automatically removed and you receive an unmount event.
The second reason dnotify is inferior to inotify is a bit more complex. It is important to note that straightforward file system-monitoring granularity using the dnotify infrastructure exists only at the directory level. For more fine-grained monitoring with dnotify, application programmers are forced to keep a cache of stat structures relating to each directory being monitored. This user space cache of stat structures is needed to determine exactly what change in the directory occurred when a notification signal is received. When notifications are obtained, a list of stat structures is generated and compared with the last known state. Clearly, this technique is sub-optimal.
An additional advantage of inotify is that it uses a file descriptor as the basic interface to let application developers use select and poll to watch the device. This allows for efficient multiplexed I/O and integration with Glib's mainloop. In contrast, dnotify uses signals with which programmers often have more difficulty and that they find to be less than elegant.
Inotify solves these issues by providing a more elegant API that uses minimal file descriptors and ensures finer granularity of monitoring. Communication with inotify is provided through a device node. For these reasons it should be your clear choice when monitoring files on Linux 2.6 platforms.
The first step in installing inotify is to determine whether the Linux kernel you are using supports it. The simplest way to check your distribution is to look for the existence of a /dev/inotify device. If this device is present, you may move on to the section Using inotify in a simple application.
At the time this article is being written, inotify is included in Andrew Morton's Linux 2.6-mm tree, and several Linux distributions are providing inotify-enabled kernels (including Gentoo and Ubuntu) or have supplemental kernel packages with support (for example, Fedora and SuSE). Since Andrew can remove inotify support from his tree if he sees fit and because inotify releases are still frequent at this stage in development, patching from scratch is highly recommended.
If the device is missing, you may need to patch your kernel and create the device.
Patching your kernel for inotify
You can obtain inotify patches from the Linux Kernel Archives (see the Resources section for a link).
You should apply the patch that has the highest version number for your specific kernel. Each distribution handles kernel installation a bit differently, but the following is a generic guideline to follow: Note: Obtain your distribution's 2.6 Linux kernel source or, if appropriate, the latest stable release from the Linux Kernel Archives.
Begin by going into your kernel source directory:
bash:~$ cd /usr/src
Since you installed the kernel source earlier, you now need to unpack it:
bash:~$ sudo tar jxvf linux-source-2.6.8.1.tar.bz2
Now, make your symlink to the new source tree:
bash:~$ sudo ln -sf linux-source-2.6.8.1 linux
Change your current directory to the kernel source directory you just made:
bash:~$ cd linux
Copy the inotify patch:
bash:~$ sudo cp ~/inotify* /usr/src
Patch the kernel:
bash:~$ sudo patch -p1 < ../inotify*.patch
Build your kernel:
bash:~$ sudo make menuconfig
Configure your kernel as you normally would, making sure to enable the inotify functionality. Add this new kernel to your bootloader as necessary, remembering to maintain your old kernel image and bootloader options. This step varies across bootloaders (see the Resources section for more information about your specific bootloader). Reboot your machine and select your new inotify enabled kernel. Test your new kernel to make sure it functions properly before continuing with this process.
Next, you need to ensure the /dev/inotify device gets created. The following steps will walk you through the process. Important note: The minor number can change, so you must be vigilant about ensuring that it is up to date! If your Linux installation supports udev functionality, it will be kept up to date automatically.
After rebooting into your new kernel, you must obtain the minor number:
bash:~$ dmesg | grep ^inotify
An example of what is returned follows:
inotify device minor=63
Since inotify is a misc device, the major is 10. Create the device node as the root user by executing the following command:
bash:~$ mknod /dev/inotify c 10 63
Note: Replace the "63" with your appropriate minor number as necessary.
Optionally, you should set the permissions as you like. A sample permission set is shown below:
bash:~$ chown root:root /dev/inotify
bash:~$ chmod 666 /dev/inotify
You are now ready to use the inotify device for file system monitoring.
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.
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;
}
|
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.
When applied to such areas as performance monitoring, debugging, and automation, inotify is a powerful, highly granular mechanism for monitoring Linux file systems. Using the code provided in this article, you are ready to start writing applications that can respond to or record file system events in real time with a minimal performance overhead.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code used in this article | inotify-sample.tar | 30 KB | HTTP |
Information about download methods
- Get the latest Inotify patches provided by Robert Love.
- Obtain current Linux kernel source trees from the Linux Kernel Archives, the canonical location for Linux kernel source code.
-
Event Management Best Practices (IBM Redbook, June 2004) presents a deep and broad understanding about event management with a focus on best practices, examining event filtering, duplicate detection, correlation, notification, escalation, synchronization, trouble-ticket integration, maintenance modes, and automation.
- "Use autonomic computing for problem determination" (developerWorks, June 2004) describes how an autonomic system can be used to monitor events and diagnose an error condition in an IT system and provide corrective actions.
- "Business service Grid, Part 3: Setting up rules" (developerWorks, April 2003) shows how a policy file in a grid system can be configured for service level definitions, service selection, security, error recovery, event monitoring, and service mapping.
- Find more resources for Linux developers in the developerWorks Linux zone.
- Get involved in the developerWorks community by participating in developerWorks blogs.
- Browse for books on these and other technical topics.
- Order the SEK for Linux, a two-DVD set containing the latest IBM trial software for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
- Innovate your next Linux development project with IBM trial software, available for download directly from developerWorks.
Eli Dow is a Software Engineer in the IBM Linux Test and Integration Center in Poughkeepsie, NY. He holds a B.S. degree in Computer Science and Psychology and a Masters of Computer Science from Clarkson University. His interests include the GNOME desktop, human computer interaction, and Linux systems programming. You can contact Eli at emdow@us.ibm.com.





