                     Linux ディレクトリ通知機能
                     ==========================

           Stephen Rothwell <sfr@canb.auug.org.au>

ディレクトリ通知機能の目的は、ディレクトリ自体、もしくはその内部の
ファイルに変更がかかったときに、ユーザアプリケーションにそのことを
通知することにあります。基本的なメカニズムは、ユーザアプリケーション
から fcntl(2) コールを用いてディレクトリに対する通知登録をおこなって
もらい、通知自体はシグナル送信でおこなう、というものです。

アプリケーションでは、どのイベントが発生したときに通知を受け取るのかを
決めることができます。現在定義されているイベントは次のとおりです。

        DN_ACCESS       ディレクトリ内のファイルがアクセスされた (read)
        DN_MODIFY       ディレクトリ内のファイルが変更された (write,truncate)
        DN_CREATE       ディレクトリ内にファイルが作成された
        DN_DELETE       ディレクトリ内からファイルが削除された
        DN_RENAME       ディレクトリ内のファイルの名前が変更された
        DN_ATTRIB       ディレクトリ内のファイルの属性が変更された (chmod,chown)

通常は、通知を受けるたびにアプリケーション側で再度登録をしなければ
なりません。しかし、イベントマスクが DN_MULTISHOT との論理和として
与えられている場合は、あえて登録を抹消しない限り有効のままとなります
(「イベント無し」を登録することで抹消とします) 。

デフォルトでは、プロセスに対して SIGIO シグナルが送信されるだけであり、
その他の有益な情報は得られません。しかし、F_SETSIG fcntl(2) を使って
送信するシグナルをカーネルに伝えておけば、シグナルハンドラに siginfo
構造体が渡されます。この構造体のメンバ変数である si_fd には、イベントが
発生したディレクトリに対応するファイル記述子が入っています。

アプリケーションでリアルタイムシグナル (SIGRTMIN + <n>) のいずれか
一つを使用することをお勧めします。そうすれば、通知をキューイングする
ことができます。特に、DN_MULTISHOT を指定する場合はこれが肝心です。
SIGRTMIN はたいていブロックされているので、(少なくとも) SIGRTMIN + 1
を使うほうが良いということに注意してください。

実装の見込み (機能もバグも :-))
------------

ファイルに対してローカルマシンからのアクセスがあったときは、全て通知される
はずです。ファイルの実体が実際にはリモートサーバ上に存在するという場合でも
同様です。これは、ローカルマシン上で動作するユーザモードのデーモンやカーネル
モードの NFS サーバを経由して、リモートからアクセスがあった場合にも同様に
通知されることを意味しています。

ファイルシステムのコードに対する変更を最小限に抑えるため、ハードリンクに
関する問題は無視しています。ですので、ファイル x が二つのディレクトリ
(a と b) に存在しているとき、"a/x" という名前のファイルに対して変更が
かかった場合、ディレクトリ "a" 上で通知を待っているプログラムに対しては
通知がおこなわれますが、ディレクトリ "b" 上で通知を待っているプログラム
に対しては通知はおこなわれません。

また、ファイルが削除 (unlink) されても、そのファイルが最後に存在して
いた (link されていた) ディレクトリで通知が発生します。

例
--

        #define _GNU_SOURCE     /* 記号定数の定義を得るために必要 */
        #include <fcntl.h>      /* glibc 2.2 では、ここに必要な値が定義されている */
        #include <signal.h>
        #include <stdio.h>
        #include <unistd.h>

        static volatile int event_fd;

        static void handler(int sig, siginfo_t *si, void *data)
        {
                event_fd = si->si_fd;
        }

        int main(void)
        {
                struct sigaction act;
                int fd;

                act.sa_sigaction = handler;
                sigemptyset(&act.sa_mask);
                act.sa_flags = SA_SIGINFO;
                sigaction(SIGRTMIN + 1, &act, NULL);

                fd = open(".", O_RDONLY);
                fcntl(fd, F_SETSIG, SIGRTMIN + 1);
                fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT);
                /* "." 内のファイルが修正されるか、または新しい
                  ファイルが作成されれば、通知を受け取る。      */
                while (1) {
                        pause();
                        printf("ファイル記述子 %d 上でイベントが発生しました\n", event_fd);
                }
        }

------------------------------------------------------------
翻訳団体： JF プロジェクト < http://www.linux.or.jp/JF/ >
翻訳者：   川崎 貴彦 < takahiko(a)hakubi.co.jp >
翻訳日：   2004/02/08
校正者：   JF プロジェクトと Linux Kernel Hack Japan
           < http://lkh.linux.or.jp/ > の方々(メール到着順)

           野本 浩一 さん    < hng(a)ps.ksky.ne.jp >
           花高 信哉 さん    < hanataka(a)abyss.rim.or.jp >
           塚本 明 さん      < akira-t(a)mb.kcom.ne.jp >
           大久保 克彦 さん  < ohkubo-k(a)suri.co.jp >
           高橋 浩和 さん    < taka(a)valinux.co.jp >
           草野 貴之 さん    < AE5T-KSN(a)asahi-net.or.jp >
           佐野 武俊 さん    < kgh12351(a)nifty.ne.jp >
           高橋 聡 さん      < hisai(a)din.or.jp >
           芳賀 靖史 さん    < yasufumi.haga(a)nifty.com >
           武井 伸光 さん    < VEC05526(a)nifty.ne.jp >
           山下 義之 さん    < dica(a)eurus.dti.ne.jp >
           Seiji Kaneko さん < skaneko(a)a2.mbn.or.jp >

=== 以下、訳者による追加 ===================================
翻訳中、原文の最後の文 "Also, files that are unlinked, will still cause
notifications in the last directory that they were linked to." の意味する
ことが分からず、Linux Kernel Hack Japan < http://lkh.linux.or.jp/ > メー
リングリストに質問させていただいたところ、疑問が解決しました。下記は、
その際に得られた情報をまとめたものです。

最後の文の要点は、次のとおりです。

        --------------------------------------------------
        プロセスＡがファイルＸをオープン後、プロセスＢが
        ファイルＸを削除してしまったとしても、プロセスＡ
        から見ると、プロセスＡ自身がファイルＸをクローズ
        するまでは、ファイルＸは存在しているように見える。

        プロセスＢがファイルＸを削除したあとからプロセス
        ＡがファイルＸをクローズするまでの間に、プロセス
        ＡがファイルＸに対して何らかの DN_* イベントを
        発生させる処理をおこなった場合、そのイベントは
        通知される。
        --------------------------------------------------

次の作業をおこなえば、この動作を確認することができます。

(1) 上記のプログラムを DN_ACCESS イベントだけを監視するように修正して
    コンパイルし、watcher というプログラム名をつける。

(2) ファイルをオープン、リード、クローズするだけの下記のプログラムを
    新規作成してコンパイルし、glancer というプログラム名をつける。

        ---------------------------------------------------------
        #include <stdio.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>

        int main(int argc, char **argv)
        {
                int fd;
                char buf[BUFSIZ];


                /*
                 * 第一引数で指定されたファイルをリードオンリーで
                 * オープンし、何かキー入力があるまで待機する。
                 */
                if ( (fd=open(argv[1],O_RDONLY)) == -1 ) return -1;
                getchar();

                /*
                 * 何かキー入力があったら、ファイルの内容を読み出す。
                 * この read により、DN_ACCESS イベントが発生する。
                 * 上記 open と下記 read の間に他のプロセスが当該
                 * ファイルを削除しても、DN_ACCESS は通知される。
                 */
                read(fd, buf, sizeof(buf));

                close(fd);
                return 0;
        }
        ---------------------------------------------------------

(3) "mkdir dir; echo xxx > dir/xxx" でファイル dir/xxx を作成する。
    (注：単なる "touch dir/xxx" では空のファイルが作成されるだけ
     なので、glancer の read() で DN_ACCESS イベントが発生しない)

(4) dir ディレクトリに移動し、watcher を起動する。

(5) 他のターミナルで、"glancer dir/xxx" で glancer を起動する。glancer は
    dir/xxx をオープン後、キー入力待ちになる。

(6) glancer を待機状態にさせたまま、他のターミナルから "rm dir/xxx" を
    実行して dir/xxx を削除する。削除後、"ls dir" とすると、当然ながら
    dir/xxx が存在しないことがわかる。

(7) 待機状態になっている glancer に対して、何かキー入力をおこなう。
    すると、glancer は dir/xxx の内容をリードする。このリードにより
    DN_ACCESS イベントが発生する。この DN_ACCESS は、(4) で起動された
    watcher に通知される。

(8) glancer 終了後、"ls dir" としても、dir/xxx は存在しない。

