ファイル/ディレクトリの変更監視用システムコール。昔は dnotify というシステムコールがあったがその改良版として 2.6.13 から導入されたらしい。

ノウハウ

  • (xubuntu9.10環境だけかもしれないが)監視対象にファイルを指定すると IN_MODIFY の通知イベントが二つくる。謎。
    • 監視対象にディレクトリを指定する分にはちゃんと一つずつ通知されるので、通常はディレクトリ単位で指定するのが良さそうに思える。
  • 以下3点を実装することで監視できる。
    • inotify初期化
    • 監視対象追加
    • 監視開始(必要であれば繰り返す)
  • inotifyのディスクリプタは通常のファイルディスクリプタとして扱えるので、poll(2)系のシステムコールを使ってI/Oイベントとして実装できる。

サンプルコード

LINUXシステムプログラミング のまんまですが、以下のような感じで /tmp 配下のファイルに変更があり次第システムログに出力します。

#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <syslog.h>

#define BUF_LEN 256
char buf[BUF_LEN] __attribute__((aligned(4)));


int main(int argc, char *argv[])
{
        int fd, wd;
        ssize_t len, i = 0, count = 0;

        openlog("notifier", LOG_CONS, LOG_DAEMON);

        // 監視セットアップ
        fd = inotify_init();
        if (fd < 0) {
                syslog(LOG_ERR, "failed inotify_init()");
                exit(EXIT_FAILURE);
        }
        wd = inotify_add_watch(fd, "/tmp", IN_ALL_EVENTS);
        if (wd < 0) {
                syslog(LOG_ERR, "failed inotify_add_watch()");
                exit(EXIT_FAILURE);
        }

        // 監視開始
        while (1) {
                len = read(fd, buf, BUF_LEN);
                while (i < len) {
                        struct inotify_event *event = (struct inotify_event *)&buf[i];
                        syslog(LOG_INFO,
                               "[%d]wd=%d mask=%08x cookie=%d len=%d dir=%s\n",
                               count++,
                               event->wd,
                               event->mask,
                               event->cookie,
                               event->len,
                               (event->mask & IN_ISDIR) ? "yes" : "no");
                        if (event->len)
                                syslog(LOG_INFO, "name=%s\n", event->name);
                        i += sizeof(struct inotify_event) + event->len;
                }
        }

        return 0;
}

以下は ruby 実装版。 rb-inotify (0.7.0) を使っています。

require "rubygems"
require "rb-inotify"

notifier = INotify::Notifier.new

notifier.watch("/tmp", :all_events) do
  puts "/tmp was modified!"
end


notifier.run