Getting Started with Inotify

12
24434
Getting started...

Getting started...

Inotify is a Linux kernel feature that provides an event-driven framework for applications to monitor changes in the filesystem. This article demonstrates how to use the Inotify API to register for interesting events, and do something in response to those events.

The changes that you can track with Inotify could be the creation, deletion, or modification of directories and files. To use the Inotify API, you just need to include the header sys/inotify.h in your C program.

Watches

A watch is at the core of Inotify. You use a watch to specify the directory or file you are interested in monitoring for events, and the events that you are interested in. For example, the watch function inotify_add_watch(fd, root, IN_CREATE | IN_MODIFY | IN_DELETE) specifies that you want to monitor the directory specified in the string root, and you want to monitor all file and directory creation, modification and deletion events, as specified by the watch mask, IN_CREATE | IN_MODIFY | IN_DELETE in this directory. The valid watch masks that can be specified are found in linux/inotify.h.

The return value of the above function is an integer that uniquely identifies this watch, and on error, a value of -1 is returned. fd is the file descriptor that specifies the current Inotify instance. A watch is uniquely identified by the path name it is watching, so if you want to specify a different watch for the same path, you will have to call inotify_add_watch( ) with the same path, but with the new combination of events that you want to watch.

A simple notification program

The following C program shows a simple demo Inotify application, which watches a directory for file and directory creation, modification and deletion events:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <limits.h>

#define MAX_EVENTS 1024 /*Max. number of events to process at one go*/
#define LEN_NAME 16 /*Assuming that the length of the filename won't exceed 16 bytes*/
#define EVENT_SIZE  ( sizeof (struct inotify_event) ) /*size of one event*/
#define BUF_LEN     ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME )) /*buffer to store the data of events*/

int main( int argc, char **argv ) 
{
  int length, i = 0, wd;
  int fd;
  char buffer[BUF_LEN];

  /* Initialize Inotify*/
  fd = inotify_init();
  if ( fd < 0 ) {
    perror( "Couldn't initialize inotify");
  }

  /* add watch to starting directory */
  wd = inotify_add_watch(fd, argv[1], IN_CREATE | IN_MODIFY | IN_DELETE); 

  if (wd == -1)
    {
      printf("Couldn't add watch to %s\n",argv[1]);
    }
  else
    {
      printf("Watching:: %s\n",argv[1]);
    }

  /* do it forever*/
  while(1)
    {
      i = 0;
      length = read( fd, buffer, BUF_LEN );  

      if ( length < 0 ) {
        perror( "read" );
      }  

      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 with WD %d\n", event->name, event->wd );       
          }
          
          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 with WD %d\n", event->name, event->wd );       
          }
          
          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 with WD %d\n", event->name, event->wd );       
          }  


          i += EVENT_SIZE + event->len;
        }
      }
    }

  /* Clean up*/
  inotify_rm_watch( fd, wd );
  close( fd );
  
  return 0;
}

Shown below is the sample output of compiling and running the above program:

$ gcc listing1.c
$ ./a,.out /home/gene/
Watching:: /home/gene/ 
.
.
The file .xsession-errors was modified with WD 1 
The file .xsession-errors was modified with WD 1 
The file .xsession-errors was modified with WD 1 
The directory foobar was Created.

The first step that you need to perform in your application is the initialisation of the Inotify instance using inotify_init( ), which returns a file descriptor that will be used to add watches and read event data in the rest of the application.

Event data is returned as a structure variable of type struct inotify_event (defined in linux/inotify.h), which is read into a buffer using the standard read() system call.

Finally, the watch is removed, and the Inotify instance is closed using the inotify_rm_watch() and close() function calls respectively.

Watching subdirectories

Unfortunately, there is no direct method to specify that you want all subdirectories under a particular directory to also be monitored. Hence, to watch subdirectories under a specified directory, you have to traverse each subdirectory and add a watch for each of them.

The following C program does the above, traversing only the subdirectories immediately under the specified directory, using functions from dirent.h, and adds a watch for each. Subsequently, instead of printing events directly to stdout, it logs it into a file, inotify_logger.log.

/* Using Inotify to monitor the sub-dirs under the specifiied dir*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <dirent.h>
#include <limits.h>

#define MAX_LEN 1024 /*Path length for a directory*/
#define MAX_EVENTS 1024 /*Max. number of events to process at one go*/
#define LEN_NAME 16 /*Assuming that the length of the filename won't exceed 16 bytes*/
#define EVENT_SIZE  ( sizeof (struct inotify_event) ) /*size of one event*/
#define BUF_LEN     ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME )) /*buffer to store the data of events*/


/* Log file*/
FILE *fp_log;
 

/* Add inotify watches to directories immediately under root
 * in addition to itself */

void add_watches(int fd, char *root)
{
  int wd;
  char *abs_dir;
  struct dirent *entry;
  DIR *dp;

  dp = opendir(root);
  if (dp == NULL)
    {
      perror("Error opening the starting directory");
      exit(0);
    }

  /* add watch to starting directory */
  wd = inotify_add_watch(fd, root, IN_CREATE | IN_MODIFY | IN_DELETE); 
  if (wd == -1)
    {
      fprintf(fp_log,"Couldn't add watch to %s\n",root);
    }
  else
    {
      printf("Watching:: %s\n",root);
    }

  /* Add watches to the Level 1 sub-dirs*/
  abs_dir = (char *)malloc(MAX_LEN);
  while((entry = readdir(dp)))
    { 
      /* if its a directory, add a watch*/
      if (entry->d_type == DT_DIR)
        {
          strcpy(abs_dir,root);
          strcat(abs_dir,entry->d_name);
          
          wd = inotify_add_watch(fd, abs_dir, IN_CREATE | IN_MODIFY | IN_DELETE); 
          if (wd == -1)
              printf("Couldn't add watch to the directory %s\n",abs_dir);
          else
            printf("Watching:: %s\n",abs_dir);
        }
    }
  
  closedir(dp);
  free(abs_dir);
}

/* Main routine*/
int main( int argc, char **argv ) 
{
  int length, i = 0;
  int fd;
  char buffer[BUF_LEN], root[MAX_LEN];


  /*Check for supplied path to monitor*/
  switch(argc)
    {
    case 1: printf("No directory specified. Will monitor the entire filesystem...\n\n");
      strcpy(root,"/");
      break;
      
    case 2: strcpy(root,argv[1]);
      if(root[strlen(root)-1]!='/')
        strcat(root,"/");
      puts(root);

      break;
      
    default: printf("Ignoring all other arguments after the first\n");
    }
  

  /* Set up logger*/
  fp_log = fopen("inotify_logger.log","a");
  if (fp_log == NULL)
    {
      printf("Error opening logger. All output will be redirected to the stdout\n");
      fp_log = stdout;
    }

  fd = inotify_init();
  if ( fd < 0 ) {
    perror( "Couldn't initialize inotify");
  }

  /* Read the sub-directories at one level under argv[1] 
   * and monitor them for access */
  add_watches(fd,root);
  
  /* do it forever*/
  while(1)
    {
      i = 0;
      length = read( fd, buffer, BUF_LEN );  

      if ( length < 0 ) {
        perror( "read" );
      }  

      /* Read the events*/
      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)
              fprintf(fp_log,"%d DIR::%s CREATED\n", event->wd,event->name );       
            else
              fprintf(fp_log, "%d FILE::%s CREATED\n", event->wd, event->name);       
          }
          
          if ( event->mask & IN_MODIFY) {
            if (event->mask & IN_ISDIR)
              fprintf(fp_log,"%d DIR::%s MODIFIED\n", event->wd,event->name );       
            else
              fprintf(fp_log,"%d FILE::%s MODIFIED\n", event->wd,event->name );       

          }
          
          if ( event->mask & IN_DELETE) {
            if (event->mask & IN_ISDIR)
              fprintf(fp_log,"%d DIR::%s DELETED\n", event->wd,event->name );       
            else
              fprintf(fp_log,"%d FILE::%s DELETED\n", event->wd,event->name );       
          }  

          i += EVENT_SIZE + event->len;
        }
      }
    }
  /* Clean up*/
  ( void ) close( fd );
  
  return 0;
}

Auto-compiling files

Now that you have a basic idea of how you can use Inotify in applications that may need to monitor the filesystem, let us do something fun, and possibly, quite useful. Using Inotify, let us monitor a directory, and whenever a C source file is created or modified in it, let’s invoke gcc to compile the file automatically, and place the binaries in a separate bin/ directory under this directory. The source code of auto_compile.c follows:

/* auto_compile.c*/

/* Automatic compilation of C code using Inotify
 * Assumptions:
 * argv[1]: where the source codes are stored
 * bin/ sub-directory: in which the binaries will be stored
 * 
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <limits.h>

#define MAX_EVENTS 1024 /*Max. number of events to process at one go*/
#define LEN_NAME 16 /*Assuming that the length of the filename won't exceed 16 bytes*/
#define EVENT_SIZE  ( sizeof (struct inotify_event) ) /*size of one event*/
#define BUF_LEN     ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME )) /*buffer to store the data of events*/

int main( int argc, char **argv ) 
{
  int length, i = 0, wd;
  int fd;
  char buffer[BUF_LEN],cur_dir[BUF_LEN];

  char command[100],copy_cmd[100];
  strcpy(command,"gcc -o ");

  /* Initialize Inotify*/
  fd = inotify_init();
  if ( fd < 0 ) {
    perror( "Couldn't initialize inotify");
  }

  /* add watch to starting directory */
  wd = inotify_add_watch(fd, argv[1], IN_CREATE | IN_MODIFY); 

  if (wd == -1)
    {
      printf("Couldn't add watch to %s\n",argv[1]);
    }
  else
    {
      printf("Watching:: %s\n",argv[1]);
    }

  /* do it forever*/
  while(1)
    {
      i = 0;
      length = read( fd, buffer, BUF_LEN );  

      if ( length < 0 ) {
        perror( "read" );
      }  

      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( "Compiling::  %s \n", event->name);       

                /*save the current dir*/
                getcwd(cur_dir,BUF_LEN);
                chdir(argv[1]);

                /* form string for gcc*/
                strcat(command,"bin/"); 
                strcat(command,event->name);
                strcat(command,".out ");
                strcat(command,event->name);

                /* execute gcc*/
                system(command);

                /*change back to dir*/
                chdir(cur_dir);
                strcpy(command,"gcc -o ");
              }
                
          }
          
          if ( event->mask & IN_MODIFY) {
            if (event->mask & IN_ISDIR)
              printf( "The directory %s was modified.\n", event->name );       
            else
              {
                printf( "Compiling:: %s \n", event->name);       

                /*save the current dir*/
                getcwd(cur_dir,BUF_LEN);
                chdir(argv[1]);

                /* form string for gcc*/
                strcat(command,"bin/");
                strcat(command,event->name);
                strcat(command,".out ");
                strcat(command,event->name);

                /* execute gcc*/
                system(command);

                /*change back to dir*/
                chdir(cur_dir);
                strcpy(command,"gcc -o ");
              }

          }       
          i += EVENT_SIZE + event->len;
        }
      }
    }

  /* Clean up*/
  inotify_rm_watch( fd, wd );
  close( fd );
  
  return 0;
}

Given below is the output of compiling and running the above C program:

$ gcc auto_compile.c 
$ ./a.out /home/gene/codes/ 
Watching:: /home/gene/codes/ 
Compiling:: hello1.c 
Compiling:: hello2.c

Of course, you could easily modify the above code to compile/execute programs in other languages.

inotify-tools

inotify-tools is a C library, as well as a set of command-line programs providing a user-friendly interface for Inotify. One useful tool is inotifywatch, which can be used to watch a directory, and is thus a more feature-rich version of our second exercise (the second program).

dnotify, inotify and fanotify

Inotify succeeded dnotify, and fanotify is slowly gathering momentum, having been introduced in kernel 2.6.36. See this LWN article for a description of it.

If you navigate to the kernel sources subdirectory under fs/notify, you can see implementations of the above three coexisting in a possible state of harmony.

In this article, we have used a hands-on approach to study a very useful feature of the Linux kernel, and all in user-space. Though not a particularly new concept (in use since 2005), now you know how you can write applications that perform actions in response to filesystem events. If you program in a language other than C, there are APIs available for C++, Java, Python, Ruby, and even a node.js module! Here are a few suggestions for building upon the code given in this article:

  • Extend auto_compile.c to be implemented as a file-manager extension, such as a Nautilus extension, so that whenever a new source file is created or modified in a designated directory, it automatically compiles it.
  • Code using Inotify will be most useful when implemented as a daemon process, such that it can run in the background without any user intervention required. Hence, the while(1) loop can be better implemented as a “wake-on” feature, such that at other times, it stays in a sleeping mode.
Resources

12 COMMENTS

  1. Sorry But I want your help

    http://t.in.com/ej3X

    Go To Above Link & Like The Video PLzzzzz
    I Want More Likes For This
    Above That Video You Will Find Like Button Of Facebook Click On That & Share This & Tell Your Friends To Like It
    SO PLZ GO TO ABOVE LINK WATCH VIDEO & LIKE IT

    • What do you mean? You could monitor the file for *all* possible events and then see which was the most recent one..

      What are you trying to do?

  2. Hello,

    When compiling your code above, I get:

    fileMonitor.cpp: In function ‘int main(int, char**)’:
    fileMonitor.cpp:41:42: error: ‘read’ was not declared in this scope
    fileMonitor.cpp:79:13: error: ‘close’ was not declared in this scope

    While compiling with:

    g++ -o fileMonitor fileMonitor.cpp

    Can you help me out?
    Thanks

    • fileMonitor.cpp:41:42: error: ‘read’ was not declared in this scope

      fileMonitor.cpp:79:13: error: ‘close’ was not declared in this scope

      this is an error about declaretions. you only must to add at the begin of this code the correct library
      #include

      check on linux terminal or google man read or man close

  3. Hi, I am using first code of this page.
    compiled as
    #gcc test.c -test
    #./test /root/test_notify_events/mic/
    And from another terminal running
    #echo “1” > /root/test_notify_events/mic/event
    Every time I am getting log twice:
    The file event was modified with WD 1
    The file event was modified with WD 1

    I am confused, why this is called twice on single change in file

    • Same here, this is little puzzling and potentially problematic when you want to perform some particular action each time there is a change, it seems as if the code would perform those actions twice…

LEAVE A REPLY

Please enter your comment!
Please enter your name here