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:
IN_ACCESS
– File was read from;IN_MODIFY
– File was written to;IN_ATTRIB
– File’s metadata (inode or xattr) was changed;IN_CLOSE_WRITE
– File was closed (and was open for writing);IN_CLOSE_NOWRITE
– File was closed (and was not open for writing);IN_OPEN
– File was opened;IN_MOVED_FROM
– File was moved away from watch;IN_MOVED_TO
– File was moved to watch;IN_DELETE
– File was deleted;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:
- When the extension is initialized, invoke
inotify_init()
and install GIO watches on the obtained file descriptor; - When a
GInotify{File,Directory}Monitor
is created, invokeinotify_add_watch()
with the appropriate parameters (obtained fromg_file_monitor()
); - 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!