GIO file monitoring overview

Posted on May 15, 2011

Last two weeks I have spent in trying to update my NetBSD 5.0.2 to current . It was an interesting quest and I still could not complete it yet :)

The CVS source snapshot has failed to build, the binary one haven’t worked properly (I had some problems with X). So I have stopped on v5.1 – it is not so outdated for a developer as v5.0.2, but it works. Though I start Emacs with LD_LIBRARY_PATH=/usr/pkg/lib (it will not launch otherwise) and sometimes MPlayer crashes the entire system, I think it is quite usable.

But this post is not about it. Here I will try to summarize how the GIO file monitoring works and how it uses inotify.

GFileMonitor for an end-user

GFileMonitor is an monitoring entity in Glib. Let’s take a look on its public interface (the official documentation here and here ):

Synopsis

enum           GFileMonitorEvent;
               GFileMonitor;

GFileMonitor*  g_file_monitor            (GFile *file,
                                          GFileMonitorFlags flags,
                                          GCancellable *cancellable,
                                          GError **error);

Signals

  "changed"                              : Run Last

To start monitoring on a specific file or directory, the programmer should call g_file_monitor() with the appropriate arguments. The returned object is the monitor itself, it will emit the "changed" signal on every event occured. The signal/slot system is common to Glib and it is no surprice that it is used here too.

The slot should have the following prototype:

void           user_function            (GFileMonitor     *monitor,
                                         GFile            *file,
                                         GFile            *other_file,
                                         GFileMonitorEvent event_type,
                                         gpointer          user_data)       : Run Last

The event_type argument will show what kind of event has happened:

typedef enum {
  G_FILE_MONITOR_EVENT_CHANGED,
  G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
  G_FILE_MONITOR_EVENT_DELETED,
  G_FILE_MONITOR_EVENT_CREATED,
  G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
  G_FILE_MONITOR_EVENT_PRE_UNMOUNT,
  G_FILE_MONITOR_EVENT_UNMOUNTED,
  G_FILE_MONITOR_EVENT_MOVED
} GFileMonitorEvent;

Thats all. Clear and simple :)

inotify for an end-user

Now let’s take a look inotify – the subsystem that is used by GFileMonitor on Linux. inotify is implemented in the kernel, the userspace interface is:

int            inotify_init             (void)
int            inotify_add_watch        (int fd,
                                         const char *pathname,
                                         uint32_t mask);

(see this LWN article for more details).

Again, the interface is fairly simple. The inotify_init() function initializes the inotify subsystem and returns a file descriptor. inotify_add_watch() specifies a file and events to monitor. The file descriptor then can be used to monitor events with select() , poll() , etc. The application will receive notifications from this file descriptor in the following form:

struct inotify_event {
    int wd;          /* Watch descriptor */
    uint32_t mask;   /* Mask of events */
    uint32_t cookie; /* Unique cookie associating related
                        events (for rename (2)) */
    uint32_t len;    /* Size of 'name' field */
    char name[];     /* Optional null-terminated name */
}; 

The following event types are available:

  1. IN_ACCESS – File was read from;
  2. IN_MODIFY – File was written to;
  3. IN_ATTRIB – File’s metadata (inode or xattr) was changed;
  4. IN_CLOSE_WRITE – File was closed (and was open for writing);
  5. IN_CLOSE_NOWRITE – File was closed (and was not open for writing);
  6. IN_OPEN – File was opened;
  7. IN_MOVED_FROM – File was moved away from watch;
  8. IN_MOVED_TO – File was moved to watch;
  9. IN_DELETE – File was deleted;
  10. IN_DELETE_SELF – The watch itself was deleted.

And again, clear and simple.

The glue

And now finally let’s take a look under the hood – on how GFileMonitor uses inotify . When g_file_monitor_file() is invoked, the following call chain occurs:

g_file_monitor()
  g_file_monitor_file()
    iface->monitor_file()
      g_local_file_monitor_file()
        _g_local_file_monitor_new()
          get_default_local_file_monitor()
          g_object_new()

Depending on the type of a file, the toplevel function g_file_monitor() calls g_file_monitor_file() for files and g_file_monitor_directory() for directories. I have shown a call stack for case of file.

g_file_monitor_file() takes the observed file’s GFile object interface, iface , and calls its monitor_file member function by pointer. For local files, which are represented with GLocalFile class, this interface member function points to g_local_file_monitor_file() . This function, in turn, calls _g_local_file_monitor_new() .

_g_local_file_monitor_new() is the most interesting one. First of all, it obtains a type of created monitor object with get_default_local_file_monitor() and then creates this instance using the obtained type with g_object_new() . Here we are facing with classes and metaclasses like in Smalltalk, Common Lisp and its modern pop successors.

get_default_local_file_monitor() returns the type of the monitor to be created. On its first invocation, the function searches for a suitable plugin across the available GIO extensions. GIO supports several types of extensions, and the function uses G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME to filter out only file monitoring ones. Then each matched exension is asked for its GLocalFileMonitorClass subclass object. Each such object has an is_supported() member function, and if this method will return true , the class will be picked up as the default class for file monitoring objects. This type will be returned on all subsequent calls to get_default_local_file_monitor() .

So, _g_local_file_monitor_new() function acts much like as a “virtual constructor”.

Thus, GIO does not refer to its inotify backend directly.

The rest is trivial: in the gio/inotify directory at Glib’s source tree we can find GInotifyFileMonitor and GInotifyDirectoryMonitor classes, which operate with inotify directly.

Considering that inotify provides a file descriptor for monitoring, the implementation of inotify backend can be summarized quite simple:

  1. When the extension is initialized, invoke inotify_init() and install GIO watches on the obtained file descriptor;
  2. When a GInotify{File,Directory}Monitor is created, invoke inotify_add_watch() with the appropriate parameters (obtained from g_file_monitor());
  3. In the inotify channel watch callback function, read a struct inotify_event from file descriptor, decode it to GIO format (GFileMonitorEvent event code, etc) and emit the "changed" signal.

That is it!