Linux file system events with C, Python and Ruby

Some applications (like file managers, monitoring tools, etc) need to know about events in the file system, for example when a file was created, opened or deleted.

With Linux, you can use the inotify mechanism to react to those events (with kernel 2.6.13 or above). In this article, I show you examples for it’s usage with C, Python and Ruby. In an upcoming post, we use Java 7 to monitor file events.

The C version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <sys/inotify.h>
#include <stdlib.h>
#include <limits.h>
 
 
// hard coded directory and file to watch. don't do this in production code
#define DIR_TO_WATCH "/tmp/notify-dir"
#define FILE_TO_WATCH "/tmp/notify-dir/notify-file.txt"
 
#define EVENT_SIZE (sizeof (struct inotify_event))
 
// define large enough buffer
#define EVENT_BUFFER_LENGTH (1024 * EVENT_SIZE + NAME_MAX + 1)
 
void print_event(struct inotify_event *event) {
 
    if (event->mask & IN_CREATE)
        printf("file created in directory\n");
    if (event->mask & IN_DELETE)
        printf("file deleted in directory\n");
    if (event->mask & IN_ACCESS)
        printf("file accessed\n");
    if (event->mask & IN_CLOSE)
        printf("file closed after reading or writing \n");
    if (event->mask & IN_OPEN)
        printf("file opened\n");
 
    if (event->len)
        printf("name: %s\n", event->name);
 
}
 
int main(int argc, char** argv) {
 
    int notify_fd;
    int watch_fd;
    long input_len;
    char *ptr;
    char buffer[EVENT_BUFFER_LENGTH];
    struct inotify_event *event;
 
    notify_fd = inotify_init();
    if (notify_fd < 0) {
        perror("cannot init inotify");
        exit(EXIT_FAILURE);
    }
 
    watch_fd = inotify_add_watch(notify_fd, DIR_TO_WATCH, IN_CREATE | IN_DELETE);
    if (watch_fd < 0) {
        perror("cannot add directory");
        exit(EXIT_FAILURE);
    }
    watch_fd = inotify_add_watch(notify_fd, FILE_TO_WATCH, IN_ACCESS | IN_CLOSE | IN_OPEN);
    if (watch_fd < 0) {
        perror("cannot add file");
        exit(EXIT_FAILURE);
    }
 
    while (1) {
        input_len = read(notify_fd, buffer, EVENT_BUFFER_LENGTH);
        if (input_len <= 0) {
            perror("error reading from inotify fd");
            exit(EXIT_FAILURE);
        }
 
        ptr = buffer;
        while (ptr < buffer + input_len) {
            event = (struct inotify_event *) ptr;
            print_event(event);
            ptr += sizeof (struct inotify_event) +event->len;
        }
    }
 
    exit(EXIT_SUCCESS);
}

This code is relatively straightforward. You start the mechanism mit inotify_init(), add watches with inotify_add_watch, read the events from the inotify file descriptor and call the print_event function to print some information on stdout. Of course, depending on your software, you will do something completely different for each event.
The meaning of the events should be clear from the names of the constants. IN_CLOSE is used for closing a file after reading it or writing to it. You can also use two different events for those types of closing (IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, see Python example below).
The inotify mechanism supports many more events than used in this example. See the man page for inotify for details.

If you use C++, you may want to have a look at this:
inotify C++ interface

Ruby Version

1
2
3
4
5
6
7
8
9
10
11
12
require "rb-inotify"
 
DIR_TO_WATCH = "/tmp/notify-dir"
 
notifier = INotify::Notifier.new
 
notifier.watch(DIR_TO_WATCH, :create, :delete) do |event|
  puts "Create event for: #{event.name}" if event.flags.include?(:create)
  puts "Delete event for: #{event.name}" if event.flags.include?(:delete)
end
 
notifier.run

This uses the rb-inotify Ruby library. In this example, Ruby 1.9 was used.
To keep the example short, the Ruby version only watches events for a given directory (when a file is created or deleted). That should be enough to show you how the library works.
If you watch only one event, you don’t need the if behind the puts. I added this because I watch for several events but wanted different output for each event.
In order to watch for file events like in the C version, do the same thing for a file and use different events.
More information about the Ruby rb-inotify library can be found here:
http://rdoc.info/projects/nex3/rb-inotify

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pyinotify
 
DIR_TO_WATCH="/tmp/notify-dir"
FILE_TO_WATCH="/tmp/notify-dir/notify-file.txt"
 
wm = pyinotify.WatchManager()
 
dir_events = pyinotify.IN_DELETE | pyinotify.IN_CREATE
file_events = pyinotify.IN_OPEN | pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CLOSE_NOWRITE
 
class EventHandler(pyinotify.ProcessEvent):
    def process_IN_DELETE(self, event):
        print("File %s was deleted" % event.pathname) #python 3 style print function
    def process_IN_CREATE(self, event):
        print("File %s was created" % event.pathname)
    def process_IN_OPEN(self, event):
        print("File %s was opened" % event.pathname)
    def process_IN_CLOSE_WRITE(self, event):
        print("File %s was closed after writing" % event.pathname)
    def process_IN_CLOSE_NOWRITE(self, event):
        print("File %s was closed after reading" % event.pathname)
 
event_handler = EventHandler()
notifier = pyinotify.Notifier(wm, event_handler)
 
wm.add_watch(DIR_TO_WATCH, dir_events)
wm.add_watch(FILE_TO_WATCH, file_events)
 
notifier.loop()

The Python example used Python 3 (version 3.2 in my machine) as you can see by the way print is used (as a function).
In the Python example I used different handlers for IN_CLOSE_WRITE (used after a file was closed after writing something to it) and IN_CLOSE_NOWRITE (used after a file was closed after just reading the content).
You could write only one callback method process_IN_CLOSE to handle both events, but I wanted different output messages. And sometimes it is better to write a little more code to make it cleaer.

The pynotify module is available for both Python 2 and Python 3 and is very easy to use. More information can be found here:
https://github.com/seb-m/pyinotify/wiki

Conclusion

As you can see, listening to different file system events on Linux is not difficult using C, C++, Python or Ruby. The inotify mechanism is also available for other languages (for example, see here for a Haskell Version).

I prefer to use the Ruby or Python version over the C version as the source code is considerably shorter and easier to understand (as it is often the case with shorter code).
Of course it depends on your project. If you use C for your project, you have to go with the C version. If you just need a short script, for example for monitoring, I recommend going with the Ruby or Python solution (or a Perl implementation)

These examples only show some basic functionality of the inotify mechanism. For example, the Python version has different notifiers, for example a ThreadedNotifier. For more details, check the man pages and the documentation listed above. I hope this example serves as a starting point for your own programs.

Those modules and libraries are specific to Linux and won’t work on other operating systems. If you are a Java developer, you can use inotify-java, but this won’t be platform independent (which is often a goal for Java software).
In an upcoming posting, I will show you how to use the latest features of Java 7 to monitor file system events.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章