How do I program for Linux's new `fanotify` file system monitoring feature?

joeforker picture joeforker · Dec 2, 2009 · Viewed 16.8k times · Source

fanotify, built on top of fsnotify, is supposed to replace inotify which replaced dnotify. Are there some good programming examples or existing utilities that use fanotify to watch for changes in a filesystem? How much detail does fanotify provide?

Answer

MvG picture MvG · Nov 15, 2012

This LWN article is often quoted as a source of documentation for fanotify. But the description there appears to be out of date. fanotify no longer works using a socket connection. Instead, there are two new libc functions wrapping syscalls, declared in sys/fanotify.h. One is called fanotify_init, the other is fanotify_mark. At the time of this writing, these syscalls are still included in the list of missing manual pages. There is, however, a mail containing drafts for these manual pages. With a combination of these man pages, a look at the headers in question, and a bit of trial and error, you should be able to get this going.

It seems that some of the functionality originally envisioned for fanotify is no longer suipported in that fashion. For example, the LWN article describes a FAN_GLOBAL_LISTENER flag which will implicitely mark the whole filesystem tree unless parts are explicitely unmarked. The current interface has no such provision, but a similar result can be achieved using the following mark:

fanotify_mark(fan,
              FAN_MARK_ADD | FAN_MARK_MOUNT,
              FAN_OPEN | FAN_EVENT_ON_CHILD,
              AT_FDCWD, "/")

Where inotify events provide the path to the accessed object as part of the event, fanotify opens a file descriptor for it. In order to turn this descriptor into a path name, the corresponding entry from the proc file system can be used, as described here.

Here is a simple example which simply prints the name of every opened file:

#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/fanotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#define CHK(expr, errcode) if((expr)==errcode) perror(#expr), exit(EXIT_FAILURE)
int main(int argc, char** argv) {
  int fan;
  char buf[4096];
  char fdpath[32];
  char path[PATH_MAX + 1];
  ssize_t buflen, linklen;
  struct fanotify_event_metadata *metadata;
  CHK(fan = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY), -1);
  CHK(fanotify_mark(fan, FAN_MARK_ADD | FAN_MARK_MOUNT,
                    FAN_OPEN | FAN_EVENT_ON_CHILD, AT_FDCWD, "/"), -1);
  for (;;) {
    CHK(buflen = read(fan, buf, sizeof(buf)), -1);
    metadata = (struct fanotify_event_metadata*)&buf;
    while(FAN_EVENT_OK(metadata, buflen)) {
      if (metadata->mask & FAN_Q_OVERFLOW) {
        printf("Queue overflow!\n");
        continue;
      }
      sprintf(fdpath, "/proc/self/fd/%d", metadata->fd);
      CHK(linklen = readlink(fdpath, path, sizeof(path) - 1), -1);
      path[linklen] = '\0';
      printf("%s opened by process %d.\n", path, (int)metadata->pid);
      close(metadata->fd);
      metadata = FAN_EVENT_NEXT(metadata, buflen);
    }
  }
}