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.
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:
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.
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.
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:
/etc/gamin/gaminrc
~/.gaminrc
/etc/gamin/mandatory_gaminrc
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.
You can download gamin from the GNOME project pages, either as sources, and binaries or source RPMs.
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.
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.
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/ .
This will be created as user feedback is provided.
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 .
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.
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.
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:
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.
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.
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.
Internally the gam_server maintain various data structures:
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:
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: