Linux ディレクトリ通知機能 ========================== Stephen Rothwell 翻訳団体: JF プロジェクト < http://www.linux.or.jp/JF/ > 翻訳者: 川崎 貴彦 < takahiko@hakubi.co.jp > 翻訳日: 2003 年 4 月 13 日 ディレクトリ通知機能の目的は、ディレクトリ自体、もしくはその内部の ファイルに変更がかかったときに、ユーザアプリケーションにそのことを 通知することにあります。基本的なメカニズムは、ユーザアプリケーション から 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 + ) のいずれか 一つを使用することをお勧めします。そうすれば、通知をキューイングする ことができます。特に、DN_MULTISHOT を指定する場合はこれが肝心です。 実装の見込み (機能もバグも :-)) ------------ ファイルに対してローカルマシンからのアクセスがあったときは、全て通知される はずです。ファイルの実体が実際にはリモートサーバ上に存在するという場合でも 同様です。これは、ローカルマシン上で動作するユーザモードのデーモンやカーネル モードの NFS サーバを経由して、リモートからアクセスがあった場合にも同様に 通知されることを意味しています。 ファイルシステムのコードに対する変更を最小限に抑えるため、ハードリンクに 関する問題は無視しています。ですので、ファイル x が二つのディレクトリ (a と b) に存在しているとき、"a/x" という名前のファイルに対して変更が かかった場合、ディレクトリ "a" 上で通知を待っているプログラムに対しては 通知がおこなわれますが、ディレクトリ "b" 上で通知を待っているプログラム に対しては通知はおこなわれません。 また、ファイルが削除 (unlink) されても、そのファイルが最後に存在して いた (link されていた) ディレクトリで通知が発生します。 例 -- #define _GNU_SOURCE /* 記号定数の定義を得るために必要 */ #include /* glibc 2.2 では、ここに必要な値が定義されている */ #include #include #include 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, &act, NULL); fd = open(".", O_RDONLY); fcntl(fd, F_SETSIG, SIGRTMIN); fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT); /* "." 内のファイルが修正されるか、または新しい ファイルが作成されれば、通知を受け取る。 */ while (1) { pause(); printf("ファイル記述子 %d 上でイベントが発生しました\n", event_fd); } } === 校正協力者 (メール到着順) ============================== JF プロジェクト < http://www.linux.or.jp> と Linux Kernel Hack Japan < http://lkh.linux.or.jp/ > の方々: 野本 浩一 さん < hng@ps.ksky.ne.jp > 花高 信哉 さん < hanataka@abyss.rim.or.jp > 塚本 明 さん < akira-t@mb.kcom.ne.jp > 大久保 克彦 さん < ohkubo-k@suri.co.jp > 高橋 浩和 さん < taka@valinux.co.jp > 草野 貴之 さん < AE5T-KSN@asahi-net.or.jp > 佐野 武俊 さん < kgh12351@nifty.ne.jp > 高橋 聡 さん < hisai@din.or.jp > 芳賀 靖史 さん < yasufumi.haga@nifty.com > 武井 伸光 さん < VEC05526@nifty.ne.jp > 山下 義之 さん < dica@eurus.dti.ne.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/ > メー リングリストに質問させていただいたところ、疑問が解決しました。下記は、 その際に得られた情報をまとめたものです。 最後の文の要点は、次のとおりです。 -------------------------------------------------- プロセスAがファイルXをオープン後、プロセスBが ファイルXを削除してしまったとしても、プロセスA から見ると、プロセスA自身がファイルXをクローズ するまでは、ファイルXは存在しているように見える。 プロセスBがファイルXを削除したあとからプロセス AがファイルXをクローズするまでの間に、プロセス AがファイルXに対して何らかの DN_* イベントを 発生させる処理をおこなった場合、そのイベントは 通知される。 -------------------------------------------------- 次の作業をおこなえば、この動作を確認することができます。 (1) 上記のプログラムを DN_ACCESS イベントだけを監視するように修正して コンパイルし、watcher というプログラム名をつける。 (2) ファイルをオープン、リード、クローズするだけの下記のプログラムを 新規作成してコンパイルし、glancer というプログラム名をつける。 --------------------------------------------------------- #include #include #include #include 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 は存在しない。