この API では以下のシステムコールが使用される。
注意深くプログラミングすることで、 アプリケーションは inotify を使ってファイルシステムオブジェクトの集合の状態を効率的に監視しキャッシュしておくことができる。 しかしながら、ロバストなアプリケーションでは、監視ロジックのバグや以下に説明があるような種類の競合条件によりファイルシステムの状態とキャッシュが一致しない状態になることがあるという事実も考慮に入れておくべきである。 おそらく何らかの一貫性のチェックを行い、不一致が検出された場合にはキャッシュを再構築するのが懸命だろう。
read(2) が成功すると、以下の構造体を 1 つ以上含むバッファーが返される:
struct inotify_event {
int wd; /* 監視対象ディスクリプター */
uint32_t mask; /* イベントを示すマスク */
uint32_t cookie; /* 関連するイベント群を関連づける
一意なクッキー (rename(2) 用) */
uint32_t len; /* 'name' フィールドのサイズ */
char name[]; /* ヌルで終端された任意の名前 */
};
wd はイベント発生の監視対象を指定する。 これは、前もって行われた inotify_add_watch(2) 呼び出しで返された監視対象ディスクリプターのうちの 1 つである。
mask には発生したイベント (下記参照) を記述するためのビットが含まれる。
cookie は関連するイベントを関連づけるための一意な整数である。 現在のところ、この値は rename イベントに対してのみ使われており、 結果のペアである IN_MOVED_FROM と IN_MOVED_TO イベントを アプリケーションで関連づけることができる。 他のイベント種別の場合には、 cookie は 0 に設定する。
name フィールドは監視しているディレクトリ内のファイルに対して イベントが返される場合のためにだけ存在する。 監視するディレクトリ内のファイル名を表す。 このファイル名はヌルで終端され、 その後の読み込みで適切なアドレス境界に調整するために、 さらにヌルバイト ('\0') が含まれる場合もある。
len フィールドはヌルバイトを含む name の全てのバイト数を表す。 よって、 inotify_event 構造体のサイズは sizeof(struct inotify_event)+len である。
read(2) に渡されたバッファーが小さすぎて次のイベントに関する情報を返せ ない場合の動作はカーネルのバージョンにより異なる。 2.6.21 より前のカー ネルでは、 read(2) は 0 を返す。 2.6.21 以降のカーネルでは、 read(2) はエラー EINVAL で失敗する。 バッファーサイズとして
sizeof(struct inotify_event) + NAME_MAX + 1
を指定すれば、少なくとも 1 イベントで読み出しを行うには十分である。
Inotify monitoring is inode-based: when monitoring a file (but not when monitoring the directory containing a file), an event can be generated for activity on any link to the file (in the same or a different directory).
ディレクトリを監視する場合:
Note: when monitoring a directory, events are not generated for the files inside the directory when the events are performed via a pathname (i.e., a link) that lies outside the monitored directory.
監視対象のディレクトリ内のオブジェクトに対してイベントが発生した場合、 inotify_event 構造体で返される name フィールドは、ディレクトリ内のファイル名を表す。
IN_ALL_EVENTS マクロは上記のイベント全てのマスクとして定義される。 このマクロは inotify_add_watch(2) を呼び出すときの mask 引数として使える。
以下の 2 つの便利なマクロが定義されている。
その他にも以下のビットを inotify_add_watch(2) を呼ぶときの mask に指定できる:
以下のビットが read(2) で返される mask フィールドに設定される:
アプリケーションがディレクトリ dir1 と dir2、およびファイル dir1/myfile を監視しているとする。 以下に生成されるイベントの例を示す。
dir1/xx と dir2/yy は同じファイルを参照するリンクで (他のリンクはないものとする)、 アプリケーションは dir1, dir2, dir1/xx, dir2/yy を監視しているものとする。 以下に示す順序で下記の呼び出しを実行すると、以下のイベントが生成される。
アプリケーションがディレクトリ dir と (空の) ディレクトリ dir/subdir を監視しているものとする。 以下に生成されるイベントの例を示す。
Linux 2.6.25 以降では、シグナル駆動 (signal-driven) I/O の通知が inotify ファイルディスクリプターについて利用可能である。 fcntl(2) に書かれている (O_ASYNC フラグを設定するための) F_SETFL, F_SETOWN, F_SETSIG の議論を参照のこと。 シグナルハンドラーに渡される siginfo_t 構造体は、以下のフィールドが設定される (siginfo_t は sigaction(2) で説明されている)。 si_fd には inotify ファイルディスクリプター番号が、 si_signo にはシグナル番号が、 si_code には POLL_IN が、 si_band には POLLIN が設定される。
inotify ファイルディスクリプターに対して 連続して生成される出力 inotify イベントが同一の場合 (wd, mask, cookie, name が等しい場合)、 前のイベントがまだ読み込まれていなければ、 連続するイベントが 1 つのイベントにまとめられる (ただし「バグ」の節も参照のこと)。 これによりイベントキューに必要なカーネルメモリー量が減るが、 これはまたアプリケーションがファイルイベント数を信頼性を持って数えるのに inotify を使用できないということでもある。
inotify ファイルディスクリプターの読み込みで返されるイベントは、 順序付けられたキューになる。 従って、たとえば、あるディレクトリの名前を別の名前に変更した場合、 inotify ファイルディスクリプターについての正しい順番で イベントが生成されることが保証される。
The set of watch descriptors that is being monitored via an inotify file descriptor can be viewed via the entry for the inotify file descriptor in the process's /proc/[pid]/fdinfo directory. See proc(5) for further details. The FIONREAD ioctl(2) returns the number of bytes available to read from an inotify file descriptor.
inotify は、ファイルシステム API 経由でユーザー空間プログラムがきっかけとなったイベントだけを報告する。 結果として、 inotify はネットワークファイルシステムで発生したリモートのイベントを捉えることはできない (このようなイベントを捉えるにはアプリケーションはファイルシステムをポーリングする必要がある)。 さらに、 /proc, /sys, /dev/pts といったいくつかの疑似ファイルシステムは inotify で監視することができない。
inotify API は mmap(2), msync(2), munmap(2) により起こったファイルのアクセスと変更を報告しない。
inotify API では影響が受けるファイルをファイル名で特定する。 しかしながら、アプリケーションが inotify イベントを処理する時点では、 そのファイル名がすでに削除されたり変更されたりしている可能性がある。
inotify API では監視対象ディスクリプターを通してイベントが区別される。 (必要であれば) 監視対象ディスクリプターとパス名のマッピングをキャッシュしておくのはアプリケーションの役目である。 ディレクトリの名前変更の場合、キャッシュしている複数のパス名に影響がある点に注意すること。
inotify によるディレクトリの監視は再帰的に行われない: あるディレクトリ以下の サブディレクトリを監視する場合、 監視対象を追加で作成しなければならない。 大きなディレクトリツリーの場合には、この作業にかなり時間がかかることがある。
ディレクトリツリー全体を監視していて、 そのツリー内に新しいサブディレクトリが作成されるか、 既存のディレクトリが名前が変更されそのツリー内に移動した場合、 新しいサブディレクトリに対する watch を作成するまでに、 新しいファイル (やサブディレクトリ) がそのサブディレクトリ内にすでに作成されている場合がある点に注意すること。 したがって、watch を追加した直後にサブディレクトリの内容をスキャンしたいと思う場合もあるだろう (必要ならそのサブディレクトリ内のサブディレクトリに対する watch も再帰的に追加することもあるだろう)。
イベントキューはオーバーフローする場合があることに注意すること。 この場合、イベントは失なわれる。 ロバスト性が求められるアプリケーションでは、 イベントが失なわれる可能性も含めて適切に処理を行うべきである。 例えば、アプリケーション内のキャッシュの一部分または全てを再構築する必要があるかもしれない。 (単純だが、おそらくコストがかかる方法は、 inotify ファイルディスクリプターをクローズし、 キャッシュを空にし、 新しい inotify ファイルディスクリプターを作成し、 監視しているオブジェクトの監視対象ディスクリプターとキャッシュエントリーの再作成を行う方法である。)
If a filesystem is mounted on top of a monitored directory, no event is generated, and no events are generated for objects immediately under the new mount point. If the filesystem is subsequently unmounted, events will subsequently be generated for the directory and the objects it contains.
これらの 2 つのイベントは、 inotify ファイルディスクリプターから読み出しを行った場合に、通常はイベントストリーム内で連続している。 しかしながら、連続していることは保証されていない。 複数のプロセスが監視対象オブジェクトでイベントを発生させた場合、 (めったに起こらないことだが) イベント IN_MOVED_FROM と IN_MOVED_TO の間に任意の数の他のイベントがはさまる可能性がある。 さらに、対となるイベントがアトミックにキューに挿入されることも保証されていない。 IN_MOVED_FROM が現れたが IN_MOVED_TO は現れていないという短い期間がありえるということだ。
したがって、 rename(2) により生成された IN_MOVED_FROM と IN_MOVED_TO のイベントの組の対応を取るのは本質的に難しいことである (監視対象のディレクトリの外へオブジェクトの rename が行われた場合には IN_MOVED_TO イベントは存在しさえしないことを忘れてはならない)。 (イベントは常に連続しているとの仮定を置くといった) 発見的な方法を使うと、ほとんどの場合でイベントの組をうまく見つけることができるが、 いくつかの場合に見逃すことが避けられず、 アプリケーションが IN_MOVED_FROM と IN_MOVED_TO イベントが無関係だとみなしてしまう可能性がある。 結果的に、監視対象ディスクリプターが破棄され再作成された場合、これらの監視対象ディスクリプターは、処理待ちイベントの監視対象ディスクリプターと一貫性のないものになってしまう (inotify ファイルディスクリプターの再作成とキャッシュの再構成はこの状況に対処するのに有用な方法なのだが)。
また、アプリケーションは、 IN_MOVED_FROM イベントが今行った read(2) の呼び出しで返されたバッファーのちょうど一番最後のイベントで、 IN_MOVED_TO イベントは次の read(2) を行わないと取得できない可能性も考慮に入れる必要がある。 2 つ目の read(2) は (短い) タイムアウトで行うべきである。 これは、 IN_MOVED_FROM-IN_MOVED_TO のイベントペアのキューへの挿入はアトミックではなく、 また IN_MOVED_TO イベントが全く発生しない可能性もあるという事実を考慮に入れておく必要があるからである。
2.6.16 以前のカーネルでは IN_ONESHOT mask フラグが働かない。
元々は設計/実装時の意図通り、 イベントが一つ発生し watch が削除された際に IN_ONESHOT フラグでは IN_IGNORED イベントが発生しなかった。 しかし、 別の変更での意図していなかった影響により、 Linux 2.6.36 以降では、 この場合に IN_IGNORED イベントが生成される。
カーネル 2.6.25 より前では、 連続する同一のイベントを一つにまとめることを意図したコード (古い方のイベントがまだ読み込まれていない場合に、 最新の 2 つのイベントを一つにまとめられる可能性がある) が、 最新のイベントが「最も古い」読み込まれていないイベントとまとめられるか をチェックするようになっていた。
inotify_rm_watch(2) の呼び出しにより監視対象ディスクリプターが削除された場合 (なお、監視対象ファイルの削除や監視対象ファイルが含まれるファイルシステムのアンマウントによっても監視対象ディスクリプターは削除される)、 この監視対象ディスクリプター関連の処理待ちの未読み出しイベントは、 読み出し可能なままとなる。 監視対象ディスクリプターは inotify_add_watch(2) によって後で割り当てられるため、 カーネルは利用可能な監視対象ディスクリプターの範囲 (0 から INT_MAX) から昇順にサイクリックに割り当てを行う。未使用の監視対象ディスクリプターを割り当てる際、 その監視対象ディスクリプター番号に inotify キューで処理待ちの未読み出しイベントがあるかの確認は行われない。 したがって、監視対象ディスクリプターが再割り当てされた際に、 その監視対象ディスクリプターの一つ前の使用時に発生した処理待ちの未読み出しイベントが存在するということが起こりうる。 その結果、アプリケーションはこれらのイベントを読み出す可能性があり、 これらのイベントが新しく再利用された監視対象ディスクリプターに関連付けられたファイルに属するものかを解釈する必要が出て来る。 実際のところ、このバグを踏む可能性は極めて低い。 それは、このバグを踏むためには、アプリケーションが INT_MAX 個の監視対象ディスクリプターが一周させて、 キューに未読み出しイベントが残っている監視対象ディスクリプターを解放し、 その監視対象ディスクリプターを再利用する必要があるからである。 この理由と、実世界のアプリケーションで発生したというバグ報告がないことから、 Linux 3.15 時点では、この計算上は起こりうるバグを取り除くためのカーネルの変更は行われていない。
以下は、 ファイル /home/user/temp/foo を編集し、 ディレクトリ /tmp の一覧表示を行った場合の出力である。 対象のファイルとディレクトリがオープンされる前に、イベント IN_OPEN が発生している。 対象ファイルがクローズされた後にイベント IN_CLOSE_WRITE が発生している。 対象ディレクトリがクローズされた後にイベント IN_CLOSE_NOWRITE が発生している。 ユーザーが ENTER キーを押すると、プログラムの実行は終了する。
/* Read all available inotify events from the file descriptor 'fd'.
wd is the table of watch descriptors for the directories in argv.
argc is the length of wd and argv.
argv is the list of watched directories.
Entry 0 of wd and argv is unused. */
static void
handle_events(int fd, int *wd, int argc, char* argv[])
{
/* Some systems cannot read integer variables if they are not
properly aligned. On other systems, incorrect alignment may
decrease performance. Hence, the buffer used for reading from
the inotify file descriptor should have the same alignment as
struct inotify_event. */
char buf[4096]
__attribute__ ((aligned(__alignof__(struct inotify_event))));
const struct inotify_event *event;
ssize_t len;
/* Loop while events can be read from inotify file descriptor. */
for (;;) {
/* Read some events. */
len = read(fd, buf, sizeof(buf));
if (len == -1 && errno != EAGAIN) {
perror("read");
exit(EXIT_FAILURE);
}
/* If the nonblocking read() found no events to read, then
it returns -1 with errno set to EAGAIN. In that case,
we exit the loop. */
if (len <= 0)
break;
/* バッファー内の全イベントを処理する */
for (char *ptr = buf; ptr < buf + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *) ptr;
/* Print event type */
if (event->mask & IN_OPEN)
printf("IN_OPEN: ");
if (event->mask & IN_CLOSE_NOWRITE)
printf("IN_CLOSE_NOWRITE: ");
if (event->mask & IN_CLOSE_WRITE)
printf("IN_CLOSE_WRITE: ");
/* Print the name of the watched directory */
for (int i = 1; i < argc; ++i) {
if (wd[i] == event->wd) {
printf("%s/", argv[i]);
break;
}
}
/* Print the name of the file */
if (event->len)
printf("%s", event->name);
/* Print type of filesystem object */
if (event->mask & IN_ISDIR)
printf(" [directory]\n");
else
printf(" [file]\n");
}
}
}
int
main(int argc, char* argv[])
{
char buf;
int fd, i, poll_num;
int *wd;
nfds_t nfds;
struct pollfd fds[2];
if (argc < 2) {
printf("Usage: %s PATH [PATH ...]\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("Press ENTER key to terminate.\n");
/* Create the file descriptor for accessing the inotify API */
fd = inotify_init1(IN_NONBLOCK);
if (fd == -1) {
perror("inotify_init1");
exit(EXIT_FAILURE);
}
/* Allocate memory for watch descriptors */
wd = calloc(argc, sizeof(int));
if (wd == NULL) {
perror("calloc");
exit(EXIT_FAILURE);
}
/* Mark directories for events
- file was opened
- file was closed */
for (i = 1; i < argc; i++) {
wd[i] = inotify_add_watch(fd, argv[i],
IN_OPEN | IN_CLOSE);
if (wd[i] == -1) {
fprintf(stderr, "Cannot watch '%s': %s\n",
argv[i], strerror(errno));
exit(EXIT_FAILURE);
}
}
/* ポーリングの準備 */
nfds = 2;
/* コンソールの入力 */
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
/* Inotify input */
fds[1].fd = fd;
fds[1].events = POLLIN;
/* Wait for events and/or terminal input */
printf("Listening for events.\n");
while (1) {
poll_num = poll(fds, nfds, -1);
if (poll_num == -1) {
if (errno == EINTR)
continue;
perror("poll");
exit(EXIT_FAILURE);
}
if (poll_num > 0) {
if (fds[0].revents & POLLIN) {
/* Console input is available. Empty stdin and quit */
while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
continue;
break;
}
if (fds[1].revents & POLLIN) {
/* Inotify events are available */
handle_events(fd, wd, argc, argv);
}
}
}
printf("Listening for events stopped.\n");
/* Close inotify file descriptor */
close(fd);
free(wd);
exit(EXIT_SUCCESS);
}
Linux カーネルソース内の Documentation/filesystems/inotify.txt