Thursday, September 16, 2010

Code Snippet: D_NOTIFY and inotify

D_NOTIFY is a facility in the Linux 2.4 kernel to monitor a directory for changes. It will send the monitoring application a signal when files in the directory are added, removed, or modified. It will be triggered if a new subdirectory is added, but does not trigger when files in that subdirectory are modified. D_NOTIFY sends a signal as notification. By default it will send SIGIO, though this can be changed.

In Linux 2.6 the far superior inotify interface was added. Where dnotify sends signals, inotify uses a file descriptor suitable for adding to select() or poll(). If your application will always run on 2.6, you should use inotify. If you need to support older kernels, dnotify still works in 2.6.

Code snippets using D_NOTIFY and inotify are provided below. Both examples monitor the current working directory for addition, removal, or modification of files.


D_NOTIFY

Suitable for 2.4 and 2.6 kernels.

#include <stdio.h>
#define __USE_GNU
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

volatile sig_atomic_t modified = 0;

static void handler(int signum, siginfo_t* si, void* data) {
  modified = 1;
}

#define MYSIG (SIGRTMIN+3)

int main(int argc, char** argv) {
  struct sigaction act;
  int fd;

  act.sa_sigaction = handler;
  sigemptyset(&act.sa_mask);
  act.sa_flags = SA_SIGINFO;
  sigaction(MYSIG, &act, NULL);

  fd = open(".", O_RDONLY);
  /* The default signal is SIGIO, but we use MYSIG instead */
  fcntl(fd, F_SETSIG, MYSIG);
  fcntl(fd, F_NOTIFY, DN_MODIFY | DN_CREATE | DN_DELETE | DN_MULTISHOT);

  while (1) {
    pause();

    if (modified) {
      printf("Directory modified!\n");
      modified = 0;
    }
  }
}

inotify

Suitable for 2.6 kernels, with a much nicer (non-signals based) API.

#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>

int main(int argc, char** argv) {
  int fd, watchdir, rlen;
  /* there is a variable length filename in the inotify_event, need to leave room for it. */
  char buf[sizeof(struct inotify_event) + 256];

  if ((fd = inotify_init()) < 0) {
    perror("inotify_init failed");
    exit(1);
  }

  if ((watchdir = inotify_add_watch (fd, ".",
                   IN_MODIFY | IN_CREATE | IN_DELETE)) < 0) {
    perror("inotify_add_watch failed");
    exit(2);
  }

  while ((rlen = read(fd, buf, sizeof(buf))) > 0) {
    struct inotify_event* event = (struct inotify_event*) buf;
    /* can examine event-> mask to determine what happened */
    printf("Directory modified!\n");
  }
}