C Programming

How to Use inotify API in C Language

Inotify is a Linux API used for file system events monitoring.

This article will show you how Inotify is used for tracking the creation, deletion, or modification of files and directories of the Linux file system.

To monitor a specific file or directory using Inotify, follow these steps:

  1. Create an inotify instance using the inotify_init()
  2. Add the full path of the directory or the file to monitor and the events to watch using the function inotify_add_watch(). In the same function, we specify which events (ON CREATE, ON ACCESS, ON MODIFY etc.), changes to the files, or changes to the directory must be monitored.
  3. Wait for events to occur and read the buffer, which contains one or more events that occurred, using the read() or select()
  4. Process the event which has occurred, then return to step 3 to wait for more events, and repeat.
  5. Remove the watch descriptor using the inotify_rm_watch()
  6. Close the inotify instance.

Now, we will see the functions that are used for Inotify API.

Header file: sys/inotify.h

inotify_init() function :

Syntax: int inotify_init (void)

Arguments: No arguments.

Return Values: On success, the function returns a new file descriptor, for failure the function returns -1.

inotify_add_watch() function:

Syntax: int inotify_add_watch ( int fd, const char *pathname, uint32_t mask )

Arguments:

This function takes three arguments.

The 1st argument (fd) is a file descriptor which refer to the inotify instance (return value of inotify_init() function) .

The 2nd argument is path of the directory or file which is being monitored.

The 3rd argument is a bitmask. The bitmask represents the events which are being watched. We can watch one or more events using bitwise-OR.

Return Values: On success, the function returns a watch descriptor, for failure the function returns -1.

inotify_rm_watch() function:

Syntax: int inotify_rm_watch ( int fd, int32_t wd )

Arguments:

This function takes two arguments.

The 1st argument (fd) is a file descriptor which refer to the inotify instance (return value of inotify_init() function) .

The 2nd argument (wd) is a watch descriptor (return value of inotify_add_watch()  function) .

Return Values:  On success, the function returns 0, for failure the function returns -1.

We use read() function(declared in unistd.h header file) to read the buffer, which is stored the information of the events occurred in the form of the inotify_event structure. The inotify_event structure is declared in sys/inotify.h header file:

struct inotify_event {
int32t   wd;
uint32_t  mask;
uint32_t  cookie;
uint32_t  len;
char name[];
}

The inotify_event structure represents a file system event returned by the inotify system and contains the following members:

  • wd: Watch descriptor (return value of inotify_add_watch() function)
  • mask: A bit mask that includes all the event types
  • cookie: Unique number that identifies events
  • len: Number of bytes in the name field
  • name: Name of the file or directory in which the event occurred

Below is a working example, using the Inotify API:

Inotify.c file:

#include<stdio.h>
#include<sys/inotify.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<fcntl.h> // library for fcntl function
 
#define MAX_EVENTS 1024  /* Maximum number of events to process*/
#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 fd,wd;
 
void sig_handler(int sig){
 
       /* Step 5. Remove the watch descriptor and close the inotify instance*/
       inotify_rm_watch( fd, wd );
       close( fd );
       exit( 0 );
 
}
 
 
int main(int argc, char **argv){
 
 
       char *path_to_be_watched;
       signal(SIGINT,sig_handler);
 
       path_to_be_watched = argv[1];
 
       /* Step 1. Initialize inotify */
       fd = inotify_init();
 
 
       if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)  // error checking for fcntl
       exit(2);
 
       /* Step 2. Add Watch */
       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(1){
 
              int i=0,length;
              char buffer[BUF_LEN];
 
              /* Step 3. Read buffer*/
              length = read(fd,buffer,BUF_LEN);
 
              /* Step 4. Process the events which has occurred */
              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;
          }
    }
}

Output:

To execute the program and see the output, we must first open two terminals. One terminal is used to run the program Inotify.c. In the second terminal, we go to the path that is being watched by the Inotify.c. If we create any directory or file, modify any file, or delete any directory or file, we will see these on the first terminal.

In the Inotify.c example, the unistd.h header file is used for the read() and close() function, the stdlib.h header file is used for the exit() function, the signal.h header file is used for the signal() function and the SIG_INT macro (See signal handling for details), and the fcntl.h header file is used for the fcntl() function.

We declare fd (inotify instance) and wd (watch descriptor) as global variables so that these variables are accessible from all functions.

The fcntl() function is used so that when we read using the fd descriptor, the thread will not be blocked.

Next, we add a watch using the inotify_add_watch() function. Here, we pass fd, the path of the directory that will be watched, and the mask. You can pass the mask of the events you want to monitor using bitwise-OR.

Now, read the buffer. Information about one or more events is stored in the buffer. You can process all events one by one using the loop. You may check the event->mask to know which type of events have happened.

We use an infinite while loop to continuously check when events occurred. If no events have happened, the read() function returns with a  0. The return value of the read() function is stored in the length variable. When the value of the length variable is greater than zero, one or more events have occurred.

We use the SIG_INT signal (press Ctrl+C) to exit from the process. When you press Ctrl+C, the sig_handler() function is called (See signal handling for details). This function removes the watch descriptor, closes the inotify instance fd, and exits the program.

Conclusion

You can use Inotify API in your own applications for monitoring, debugging, automation, and more, in your own way. Here, we have seen the execution flow of Inotify API.

About the author

Bamdeb Ghosh

Bamdeb Ghosh is having hands-on experience in Wireless networking domain.He's an expert in Wireshark capture analysis on Wireless or Wired Networking along with knowledge of Android, Bluetooth, Linux commands and python. Follow his site: wifisharks.com