Gamin the File Alteration Monitor

Gamin is a file and directory monitoring system defined to be a subset of the FAM (File Alteration Monitor) system. This is a service provided by a library which allows to detect when a file or a directory has been modified.

Overview

Gamin is a file and directory monitoring system defined to be a subset of the FAM (File Alteration Monitor) system.

The main goals of the project are:

  1. minimize the security model of FAM, the daemon runs under the user account, it is compatible with SELinux
  2. simplify the code base, dropping some of the most exotic feature of FAM
  3. provide an API and ABI compatible replacement for FAM
  4. try to fix some other issues like resource consumption

Gamin also serves as an interface to test the inotify mechanism to improve the existing dnotify monitoring interface present in the Linux kernel.

At this point Gamin is fairly tied to Linux, portability is not a primary goal at this stage but if you have portability patches they are welcome.

From an historical point of view, gamin builds from the marmot project authored by James Willcox and Corey Bowers and then heavilly modified to turn it into a minimalist FAM replacement (Francophones will appreciate the filiation from fam to marmot and gamin.)

This library is available under the terms of the GNU LIBRARY GENERAL PUBLIC LICENSE, and a copy of it should be found in the source under the COPYING file.

Using gamin

Basically it is exactly like for using the fam interface. From a programmer point of view this is the same API, and one can make use of the existing FAM documentation.

Configuration

By default gamin should work without needing any configuration, but sometimes using the kernel notification APIs doesn't work or lead to troubles (for example when trying to unmount device). By default gamin revert to using polling for all paths matching /mnt/* or /media/* on Linux. This may be overriden or extended by the one of the three config files /etc/gamin/gaminrc, $HOME/.gaminrc, /etc/gamin/mandatory_gaminrc (note that prior to 0.1.4 only $HOME/.gaminrc was used and fsset was not implemented). Here is an example of such a configuration file:

# configuration for gamin
# Can be used to override the default behaviour.
# notify filepath(s) : indicate to use kernel notification
# poll filepath(s)   : indicate to use polling instead
# fsset fsname method poll_limit : indicate what method of notification for the filesystem
#                                  kernel - use the kernel for notification
#                                  poll - use polling for notification
#                                  none - don't use any notification
#                                  
#                                  the poll_limit is the number of seconds
#                                  that must pass before a resource is polled again.
#                                  It is optional, and if it is not present the previous
#                                  value will be used or the default.
 
notify /mnt/local* /mnt/pictures* # use kernel notification on these paths
poll /temp/*                      # use poll notification on these paths
fsset nfs poll 10                 # use polling on nfs mounts and poll once every 10 seconds

The configuration file accepts only 3 types of command:

The three config files are loaded in this order:

The mandatory config file allows the system administrator to override any potentially dangerous preferences set by the user.

When checking a path to guess whether polling or kernel notification should be used, gamin checks first the user provided rules in their declaration order within the configuration file and then check the predefined rules. This way the first declaration for /mnt/local* in the example override the default one for /mnt/* .

If gamin is not told to use poll on a particular path, it will then try and decide based on the filesystem the path is located on

Caveat: separators in the config file should be spaces, not tabs.

News

0.1.10: Nov 24 2008

0.1.9: Jul 27 2007

0.1.8: Oct 31 2006

0.1.7: Oct 27 2005

0.1.6: Sep 8 2005

0.1.5: Aug 9 2005

0.1.3: Aug 2 2005

0.1.2: Jul 13 2005

0.1.1: Jun 10 2005

0.1.0: May 12 2005

0.0.26: Mar 15 2005

0.0.25: Mar 1 2005

0.0.24: Feb 18 2005

0.0.23: Feb 8 2005

0.0.22: Jan 31 2005

0.0.21: Jan 26 2005

0.0.20: Jan 6 2005

0.0.19: Dec 3 2004

0.0.18: Nov 26 2004

0.0.16: Oct 20 2004

0.0.15: Oct 16 2004

0.0.14: Oct 3 2004

0.0.14: Oct 3 2004

0.0.13: Oct 1 2004

0.0.12: Sep 30 2004

0.0.11: Sep 27 2004

0.0.10: Sep 21 2004

0.0.9: Sep 1 2004

0.0.8: Aug 26 2004

0.0.7: Aug 24 2004

0.0.6: Aug 19 2004

Downloads

You can download gamin from the GNOME project pages, either as sources, and binaries or source RPMs.

Python bindings

Starting with release 0.0.21, gamin comes with Python bindings. Use "import gamin" to load the module. It exports the main class "WatchMonitor" which handle one connection to the gam_server. Once an instance of the class has been created you can use the watch_directory and watch_file methods to register objects to monitors, and provide the callback to be called when events are generated. Like the FAM API, the python binding API is passive, i.e. one need  to  monitor the file descriptor provided with the get_fd() method to detect events, and call handle_one_event() (blocking) or handle_events() (non-blocking) to process incoming events and get the callbacks appropriately. You can also use the event_pending() method to detect if handle_one_event() would block or not.
Since a short example is worth a thousand words, here is a small session from the python shell:

>>> import gamin
>>>
>>> def callback(path, event):
...     print "Got callback: %s, %s" % (path, event)
...
>>> mon = gamin.WatchMonitor()
>>> mon.watch_directory(".", callback)
<gamin.WatchObject instance at 0xb7f7b56c>
>>> mon.event_pending()
1
>>> mon.handle_one_event()
Got callback: /u/veillard/temp, 8
1
>>> mon.handle_events()
Got callback: bar1, 8
Got callback: foo1, 8
Got callback: /u/veillard/temp, 9
3
>>> mon.stop_watch(".")
>>> del mon

The corresponding standalone code follows:

#!/usr/bin/env python

import gamin
import time

def callback(path, event):
    print "Got callback: %s, %s" % (path, event)

mon = gamin.WatchMonitor()
mon.watch_directory(".", callback)
time.sleep(1)
ret = mon.event_pending()
if ret > 0:
    ret = mon.handle_one_event()
    ret = mon.handle_events()
mon.stop_watch(".")
del mon

Note the addition of the sleep timeout, it is needed because due to the round trip between the client and the gam_server, events may not be immediately available after the monitor creation to the client.

Developers informations

The CVS base is in the GNOME project CVS base at cvs.gnome.org, the module name is gamin. See the Gnome CVS Tools page for more information on using CVS.

We expect bug reports to be entered in GNOME bugzilla or in Red Hat bugzilla.

Contacts

The best way to contact the developers is to use the mailing-list gamin-list@gnome.org.

Before reporting a bug please double-check:

Then the best way is to log the bug in one of the bugzilla or join the list and post there if you think that some of gamin design or code should be changed. If you can provide a testgam scenario reproducing the problem this would really help getting it debugged and fixed, see examples in tests/scenario/ .

FAQ

This will be created as user feedback is provided.

Debugging Gamin

Debugging support in gamin:

Both the client and server side, if compiled with debug support accept an environment variable GAM_DEBUG which is set will make them report debugging informations to stdout.

Usually for debugging you also want to use a dedicated server process so setting the GAM_CLIENT_ID environment allows to ensure this. Usually one also want to keep control over the server lifetime and not have it exit automatically after 30 seconds without connection, there is a command line flag --notimeout to gam_server for this.

A typical example of a debugging session using 2 shells would be:

shell1: export GAM_DEBUG=
shell1: gam_server --notimeout test

to run the server in debug mode using the ID "test"

shell2: export GAM_DEBUG=
shell2: export GAM_CLIENT_ID=test
shell2: gamin_client

to run the client in a verbose session. It is perfectly possible to also run the client under a debugger, for the server it works too except the dnotify kernel interface uses a signal SIG33 which is trapped by gdb. To avoid this use the handle gdb instruction:

(gdb) handle SIG33 nostop
Signal        Stop      Print   Pass to program Description
SIG33         No        Yes     Yes             Real-time event 33
(gdb)

even better add it to your $HOME/.gdbinit .

Debugging a running program:

Both the gam_server and client of the gamin library can get switched dynamically to a debug mode by sending them a signal SIGUSR2. In that case the program or library switches to verbose debugging and outputs the traces to a new file /tmp/gamin_debug_XXXXXX . Sending the signal again to the application or the server should switch off debugging.

Debugging and testing client:

A debugging client program called testgam is also available in the tests subdirectory. It allow to use the interface and monitor the flow of event received. Here is an example of a session:

paphio:~/gamin/tests -> export GAMIN_DEBUG_SERVER="../server/gam_server"
paphio:~/gamin/tests -> ./testgam -
> connect test
connected to test
>

The environment variable can be used to specify the path to the server to run, then testgam is launched in interactive mode (argument - ) and the program is asked to connect to the server for session test (the server will be started on-demand by the library if needed).

> mkdir temp
mkdir temp
> mkfile temp/foo
mkfile temp/foo
> mondir temp
mondir temp 0
>
1: /u/veillard/gamin/tests/temp Exists: NULL
1: foo Exists: NULL
1: /u/veillard/gamin/tests/temp EndExist: NULL
> mkfile temp/bar
mkfile temp/bar
>
1: bar Created: NULL
> rmfile temp/foo
rmfile temp/foo
>
1: foo Deleted: NULL
>

In this example a new directory is created with a file in it and then monitored, then the directory content is modified. The testgam program also poll and report events coming from the server as they arrive.

Security

While gamin still use a server to provide the service (ideally if the kernel had a proper interface a library only implementation should be doable and possibly better), it tries to avoid security hazard associated to contacting an external server process:

Here is the process used to acquire and create the sockets:

If there is abstract socket support:

Use the filename "\0/tmp/fam-$USER-$GAM_CLIENT_ID". They are not mapped on the filesystem, no attack is possible that way. The client and the server checks on the first '\0' byte received from the socket that the other side is running under the same UID.

If there is no abstract socket support:

On the server side:

 start:
  try to create /tmp/fam-$USER using mkdir('/tmp/fam-$USER', 007)
  if error:
      make a stat() on it
      if doesn't exist:
          return failure to create
      if user is not getuid() or mode is not 007 or type is not dir:
          try to unlink()
          if error:
              exit with error.
          if success:
              goto start:
                                                                                
  do the socket()/bind() on /tmp/fam-$USER/fam-$GAM_CLIENT_ID

On the client side:

  make a stat on /tmp/fam-$USER
  if doesn't exist:
      return failure to create should start the server
  if user is not getuid() or mode is not 007 or type is not dir:
      try to unlink()
      if error:
          exit with error.
      if success:
          return failure should start the server
  make a stat on /tmp/fam-$USER/fam-$GAM_CLIENT_ID
  if doesn't exist:
      return failure to create should start the server
  if user is not getuid() or type is not socket:
      try to unlink()
      if error:
          exit with error.
      if success:
          return failure should start the server
                                                                                
  do the socket()/connect() on /tmp/fam-$USER/fam-$GAM_CLIENT_ID

The client and the server checks on the first '\0' byte received that the other side is of the same UID.

Internals

gamin uses a client server model, this is in a large measure justified by the inappropriate dnotify kernel API way to signal modification events to an application but also to share kernel signal when multiple application monitors the same resource. It also allow to fine tune and filters event flow in the daemon, potentially minimizing resource consumption in applications.

One server and two client connected

Internally the gam_server maintain various data structures:

the data structures present in the server

Differences from FAM

Differences

gamin should be binary and source code compatible with FAM 2.6.8. However there are some differences and at least one significant extension.

The differences are in term of implementation:

Extension(s)

We tried to limit changes in gamin but a number of features were deemed more important than sticking to the exact same set than FAM:

FAM when monitoring a directory, immediately send a set of events listing the files found in that directory, this ends up with an EndExists events. However when monitoring hierarchy it's usually far more efficient and simple to do the scanning on the client side and ignore those directory listing events from the FAM server (the only drawback is a potential mismatch of the directory content between the FAM server and the client). In such a case, all those events are not only superfluous but they are also dangerous since they can lead to a congested pipe to the client which is just scanning directories and not listening to FAM. To that intent we added in gamin 0.0.23 a new API disabling the Exists/EndExists sequences when watching directories for a given FAMConnection:

int FAMNoExists(FAMConnection *fc)

and with the Python bindings:

WatchMonitor.no_exists()

This feature is also used when the client reconnect to the server after a connection loss or if the server died.

Calling it changes the protocol as described below, directory monitoring from that call will only get mutation events and not the initial lists:

The NoExists behaviour change on callbacks