Provided by: manpages-ja_0.5.0.0.20210215+dfsg-1_all
名前
fanotify - ファイルシステムイベントを監視する
説明
fanotify API はファイルシステムイベントの通知と横取り機能 (interception) を提供する。 ユー スケースとしては、ウイルススキャンや階層型ストレージの管理などがある。 現在のところ、限定 的なイベントのみがサポートされている。 特に、作成 (create)、削除 (delete)、移動 (move) イ ベントがサポートされていない (これらのイベントを通知する API の詳細については inotify(7) を参照)。 inotify(7) API と比較して追加されている機能としては、 マウントされたファイルシステムの全オ ブジェクトを監視する機能、 アクセス許可の判定を行う機能、 他のアプリケーションによるアクセ スの前にファイルを読み出したり変更したりする機能がある。 この API では以下のシステムコールを使用する: fanotify_init(2), fanotify_mark(2), read(2), write(2), close(2)。 fanotify_init(), fanotify_mark() と通知グループ fanotify_init(2) システムコールは fanotify 通知グループを作成、初期化し、 この通知グループ を参照するファイルディスクリプターを返す。 fanotify 通知グループはカーネル内部のオブジェクトで、 イベントが作成されるファイル、 ディ レクトリ、 マウントポイントのリストを保持する。 fanotify 通知グループの各エントリーには 2 つのビットマスクがある。 mark マスクと ignore マ スクである。 mark マスクはどのファイル操作についてイベントを作成するかを定義する。 ignore マスクはどの操作についてイベントを作成しないかを定義する。 これらの 2 種類のマスクがあるこ とで、 マウントポイントやディレクトリに対してイベントの受信を mark しておきつつ、 同時にそ のマウントポイントやディレクトリ配下の特定のオブジェクトに対するイベントを無視する、 と いったことができる。 fanotify_mark(2) システムコールは、ファイル、ディレクトリ、マウントを通知グループに追加 し、 どのイベントを報告 (もしくは無視) するかを指定する。 また、このようなエントリーの削 除、変更も行う。 ignore マスクの考えられる使用方法はファイルキャッシュに対してである。 ファイルキャッシュに 関して興味のあるイベントは、ファイルの変更とファイルのクローズである。 それゆえ、 キャッ シュされたディレクトリやマウントポイントは、 これらのイベントを受信するようにマークされ る。 ファイルが変更されたという最初のイベントを受信した後は、 対応するキャッシュエントリー は無効化される。 そのファイルがクローズされるまでは、 このファイルに対する変更イベントは興 味のない情報となる。 したがって、 変更イベントを ignore マスクに追加することができる。 ク ローズイベントを受信すると、 変更イベントを ignore イベントから削除し、 ファイルキャッシュ エントリーを更新することができる。 fanotify 通知グループのエントリーは、 ファイルやディレクトリでは inode 番号経由で参照さ れ、 マウントではマウント ID 経由で参照される。 ファイルやディレクトリの名前が変更された り、移動されたりした場合も、 関連するエントリーはそのまま残る。 ファイルやディレクトリが削 除されたり、マウントがアンマウントされたりした場合には、 対応するエントリーは削除される。 イベントキュー 通知グループにより監視されているファイルシステムオブジェクトでイベントが発生すると、 fanotify システムはイベントを生成し、 そのイベントはキューにまとめられる。 これらのイベン トは、 fanotify_init(2) が返した fanotify ファイルディスクリプターから (read(2) などを使っ て) 読み出すことができる。 2 種類のイベントが生成される。 notification (通知) イベントと permission (アクセス許可) イ ベントである。 通知イベントは単なる情報通知であり、 イベントで渡されたファイルディスクリプ ターをクローズする場合 (下記参照) を除き、 受信したアプリケーションでアクションを取る必要 はない。 アクセス許可イベントは、 受信したアプリケーションがファイルアクセスの許可を承認す るかを判定する必要がある。 この場合、 受信者はアクセスが許可されたか否かを決定する応答を書 き込まなければならない。 イベントは、 読み出されると、 fanotify グループのイベントキューから削除される。 読み出され たアクセス許可イベントは、 fanotify ファイルディスクリプターにアクセス許可の判定が書き込ま れるか、 fanotify ファイルディスクリプターがクローズされるまで、 fanotify グループの内部の リストに保持される。 fanotify イベントの読み出し fanotify_init(2) が返したファイルディスクリプターに対する read(2) を呼び出しは、 (fanotify_init(2) の呼び出しでフラグ FAN_NONBLOCK を指定しなかった場合) ファイルイベントが 起こるか、呼び出しがシグナルによって割り込まれる (signal(7) 参照) まで停止する。 read(2) が成功すると、読み出しバッファーには以下の構造体が 1 つ以上格納される。 struct fanotify_event_metadata { __u32 event_len; __u8 vers; __u8 reserved; __u16 metadata_len; __aligned_u64 mask; __s32 fd; __s32 pid; }; 性能上の理由から、複数のイベントを一度の read(2) で取得できるように大きめのバッファーサイ ズ (例えば 4096 バイト) を使用することを推奨する。 read(2) の返り値はバッファーに格納されたバイト数である。 エラーの場合は -1 が返される (た だし、バグも参照)。 fanotify_event_metadata 構造体のフィールドは以下のとおりである。 event_len これは、 このイベントのデータ長であり、バッファー内の次のイベントへのオフセットであ る。 現在の実装では、 event_len の値は常に FAN_EVENT_METADATA_LEN である。 しかしな がら、 API は将来可変長の構造体を返すことができるように設計されている。 vers このフィールドには構造体のバージョン番号が入る。 実行時に返された構造体がコンパイル 時の構造体と一致しているかを検査するには、 この値を FANOTIFY_METADATA_VERSION を比 較すること。 一致しない場合、 アプリケーションはその fanotify ファイルディスクリプ ターを使用するのを諦めるべきである。 reserved このフィールドは使用されない。 metadata_len この構造体の長さである。 このフィールドは、 イベント種別単位のオプションヘッダーの 実装を扱うために導入された。 現在の実装ではこのようなオプションヘッダーは存在しな い。 mask イベントを示すビットマスクである (下記参照) fd これはアクセスされたオブジェクトに対するオープンされたファイルディスクリプターであ る。 または、キューのオーバーフローが発生した場合には FAN_NOFD が入る。 ファイル ディスクリプターは監視対象のファイルやディレクトリの内容にアクセスするのに使用でき る。 読み出したアプリケーションは責任を持ってこのファイルディスクリプターをクローズ しなければならない。 fanotify_init(2) を呼び出す際、 呼び出し元はこのファイルディスクリプターに対応する オープンファイル記述にセットされた様々なファイル状態フラグを (event_f_flags 引き数 を使って) 指定することができる。 さらに、 (カーネル内部の) FMODE_NONOTIFY ファイル 状態フラグがオープンファイル記述にセットされる。 このフラグは fanotify イベントの生 成を抑制する。 したがって、 fanotify イベントの受信者がこのファイルディスクリプター を使って通知されたファイルやディレクトリにアクセスした際に、 これ以上イベントが作成 されなくなる。 pid これはイベントが発生する原因となったプロセス ID である。 fanotify イベントを監視し ているプログラムは、 この PID を getpid(2) が返す PID と比較することで、 イベントが 監視しているプログラム自身から発生したかどうか、 別のプロセスによるファイルアクセス により発生したか、を判定できる。 mask のビットマスクは、1 つのファイルシステムオブジェクトに対してどのイベントが発生したか を示す。 監視対象のファイルシステムオブジェクトに複数のイベントが発生した場合は、 このマス クに複数のビットがセットされることがある。 特に、 同じファイルシステムオブジェクトに対する 連続するイベントが同じプロセスから生成された場合には、 一つのイベントにまとめられることが ある。 例外として、 2 つのアクセス許可イベントが一つのキューエントリーにまとめられることは 決してない。 mask でセットされている可能性のあるビットは以下のとおりである。 FAN_ACCESS ファイルやディレクトリがアクセスされた (読み出しが行われた) (ただし、「バグ」の節も 参照)。 FAN_OPEN ファイルやディレクトリがオープンされた。 FAN_MODIFY ファイルやディレクトリが変更された。 FAN_CLOSE_WRITE 書き込み用 (O_WRONLY か O_RDWR) にオープンされたファイルがクローズされた。 FAN_CLOSE_NOWRITE 読み出し用 (O_RDONLY) にオープンされたファイルがクローズされた。 FAN_Q_OVERFLOW イベントキューが 16384 エントリーの上限を超過した。 この上限は fanotify_init(2) 呼 び出し時に FAN_UNLIMITED_QUEUE フラグを指定することで上書きできる。 FAN_ACCESS_PERM アプリケーションが例えば read(2) や readdir(2) などを使ってファイルやディレクトリを 読み出そうとした。 このイベントを読み出したプログラムは、 そのファイルシステムオブ ジェクトへのアクセス許可を承認するかを判定し (下記で説明するとおり) 応答を書き込ま なければならない。 FAN_OPEN_PERM アプリケーションがファイルやディレクトリをオープンしようとした。 このイベントを読み 出したプログラムは、 そのファイルシステムオブジェクトのオープンを承認するかを判定し (下記で説明するとおり) 応答を書き込まなければならない。 クローズイベントを確認するために以下のビットマスクを使うことができる。 FAN_CLOSE ファイルがクローズされた。 以下の同義語である。 FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE fanotify ファイルディスクリプターからの read(2) が返した fanotify イベントメタデータを含む バッファーに対して繰り返しを行うため、 以下のマクロが提供されている。 FAN_EVENT_OK(meta, len) このマクロは、 バッファー meta の残りの長さ len を、 メタデータ構造体の長さとバッ ファーの最初のメタデータ構造体の event_len フィールドと比較して検査する。 FAN_EVENT_NEXT(meta, len) このマクロは、 meta が指すメタデータ構造体の event_len フィールドで示された長さを 使って、 meta の次のメタデータ構造体のアドレスを計算する。 len はバッファーに現在 残っているメタデータのバイト数である。 このマクロは meta の次のメタデータ構造体への ポインターを返し、 スキップされたメタデータ構造体のバイト数だけ len を減算する (つ まり、 len から meta->event_len を引き算する)。 また、 以下のマクロも用意されている。 FAN_EVENT_METADATA_LEN このマクロは fanotify_event_metadata 構造体の (バイト単位の) サイズを返す。 返され る値はイベントメタデータの最小値である (現在のところ、これが唯一のサイズである)。 fanotify ファイルディスクリプターのイベントを監視する fanotify イベントが発生すると、 epoll(7), poll(2), select(2) に fanotify ファイルディスク リプターが渡された場合には、そのファイルディスクリプターが読み出し可能であると通知される。 アクセス許可イベントの取り扱い アクセス許可イベントでは、 アプリケーションは以下の形式の構造体を fanotify ファイルディス クリプターに write(2) しなければならない。 struct fanotify_response { __s32 fd; __u32 response; }; この構造体のフィールドは以下のとおりである。 fd このフィールドは fanotify_event_metadata 構造体で返されたファイルディスクリプターで ある。 response このフィールドはアクセス許可を承認するかどうかを示す。 値は、このファイル操作を許可 する FAN_ALLOW か、 このファイル操作を拒否する FAN_DENY のいずれかでなければならな い。 アクセスを拒否した場合、 アクセスを要求したアプリケーションは EPERM エラーを受け取ることに なる。 fanotify ファイルディスクリプターのクローズ fanotify 通知グループを参照するすべてのファイルディスクリプターがクローズされると、 fanotify グループは解放され、 カーネルが再利用できるようにそのリソースは解放される。 close(2) の際に、 処理中であったアクセス許可イベントには許可が設定される。 /proc/[pid]/fdinfo ファイル /proc/[pid]/fdinfo/[fd] には、 プロセス pid のファイルディスクリプター fd の fanotify マークに関する情報が格納される。 詳細はカーネルのソースファイル Documentation/filesystems/proc.txt を参照。
エラー
通常の read(2) のエラーに加え、 fanotify ファイルディスクリプターから読み出しを行った際に 以下のエラーが発生することがある。 EINVAL バッファーがイベントを保持するには小さすぎる。 EMFILE オープンしたファイル数のプロセス毎の上限に達した。 getrlimit(2) の RLIMIT_NOFILE の 説明を参照。 ENFILE オープンされたファイル数のシステム全体の上限に達した。 proc(5) の /proc/sys/fs/file-max を参照。 ETXTBSY fanotify_init(2) の呼び出し時に O_RDWR か O_WRONLY が event_f_flags 引き数に指定さ れており、 現在実行中の監視対象のファイルに対してイベントが発生した際に、 このエ ラーが read(2) から返される。 通常の write(2) のエラーに加え、 fanotify ファイルディスクリプターに書き込みを行った際に以 下のエラーが発生することがある。 EINVAL fanotify アクセス許可がカーネルの設定で有効になっていない。 応答構造体の response 値が無効である。 ENOENT 応答構造体のファイルディスクリプター fd が無効である。 このエラーはアクセス許可イベ ントに対する応答がすでに書き込まれている際に発生する。
バージョン
fanotify API は Linux カーネルのバージョン 2.6.36 で導入され、 バージョン 2.6.37 で有効に された。 fdinfo のサポートはバージョン 3.8 で追加された。
準拠
fanotify API は Linux 独自のものである。
注意
fanotify API が利用できるのは、 カーネルが CONFIG_FANOTIFY 設定オプションを有効にして作成 されている場合だけである。 また、 fanotify アクセス許可の処理が利用できるのは CONFIG_FANOTIFY_ACCESS_PERMISSIONS 設定オプションが有効になっている場合だけである。 制限と警告 fanotify が報告するのはユーザー空間プログラムがファイルシステム API 経由で行ったイベントだ けである。 その結果、 fanotify ではネットワークファイルシステム上で発生したリモートイベン トは捕捉できない。 inotify API は mmap(2), msync(2), munmap(2) により起こったファイルのアクセスと変更を報告し ない。 ディレクトリのイベントは、ディレクトリ自身がオープン、読み出し、クローズされた場合にしか作 成されない。 マークされたディレクトリでの子要素の追加、削除、変更では、監視対象のディレク トリ自身へのイベントは作成されない。 fanotify のディレクトリの監視は再帰的ではない。 ディレクトリ内のサブディレクトリを監視する には、 追加で監視用のマークを作成しなければならない。 (ただし、 fanotify API では、サブ ディレクトリが監視対象としてマークされているディレクトリに作成された際に検出する手段は提供 されていない点に注意すること。) マウントの監視を使うことで、 ディレクトリツリー全体を監視 することができる。 ベントキューはオーバーフローすることがある。 この場合、 イベントは失われる。
バグ
Linux 3.17 時点では、 以下のバグが存在する。 * Linux では、ファイルシステムオブジェクトは複数のパスでアクセス可能である。 例えば、 ファイルシステムの一部は mount(8) の --bind オプションを使って再マウントされることがあ る。 マークされたマウントの監視者は、 同じマウントを使ったファイルオブジェクトについて のみイベント通知を受ける。 それ以外のイベントは通知されない。 * fallocate(2) の呼び出しでは fanotify イベントが作成されない。 * イベントが生成された際に、 そのファイルのファイルディスクリプターを渡す前に、 イベント を受信するプロセスのユーザー ID がそのファイルに対する読み出し/書き込み許可があるかの 確認は行われない。 非特権ユーザーによって実行されたプログラムに CAP_SYS_ADMIN ケーパビ リティーがセットされている場合には、 このことはセキュリティーリスクとなる。 * read(2) の呼び出しが fanotify キューから複数のイベントを処理している際に、 エラーが発生 した場合、 返り値はエラーが発生する前までにユーザー空間バッファーに正常にコピーされたイ ベントの合計長となる。 返り値は -1 にならず、 errno もセットされない。 したがって、 読 み出しを行うアプリケーションではエラーを検出する方法はない。
例
以下のプログラムは fanotify API の使用法を示すものである。 コマンドライン引き数で渡された マウントポイントを監視し、 種別が FAN_PERM_OPEN と FAN_CLOSE_WRITE のイベントを待つ。 アク セス許可イベントが発生には、 FAN_ALLOW 応答を返す。 以下の出力例はファイル /home/user/temp/notes を編集した際に記録されたものである。 ファイル をオープンする前に FAN_OPEN_PERM イベントが発生している。 ファイルをクローズした後に FAN_CLOSE_WRITE イベントが発生している。 エンターキーをユーザーが押すと、 このプログラムの 実行は終了する。 出力例 # ./fanotify_example /home Press enter key to terminate. Listening for events. FAN_OPEN_PERM: File /home/user/temp/notes FAN_CLOSE_WRITE: File /home/user/temp/notes Listening for events stopped. プログラムソース #define _GNU_SOURCE /* O_LARGEFILE の定義を得るために必要 */ #include <errno.h> #include <fcntl.h> #include <limits.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys/fanotify.h> #include <unistd.h> /* ファイルディスクリプター 'fd' から読み出しできる全 fanotify イベントを読み出す */ static void handle_events(int fd) { const struct fanotify_event_metadata *metadata; struct fanotify_event_metadata buf[200]; ssize_t len; char path[PATH_MAX]; ssize_t path_len; char procfd_path[PATH_MAX]; struct fanotify_response response; /* fanotify ファイルディスクリプターからイベントが読み出せる間はループする */ for(;;) { /* イベントを読み出す */ len = read(fd, (void *) &buf, sizeof(buf)); if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* 読み出せるデータの最後に達しているかチェックする */ if (len <= 0) break; /* バッファーの最初のイベントを参照する */ metadata = buf; /* バッファー内の全イベントを処理する */ while (FAN_EVENT_OK(metadata, len)) { /* 実行時とコンパイル時の構造体が一致するか確認する */ if (metadata->vers != FANOTIFY_METADATA_VERSION) { fprintf(stderr, "Mismatch of fanotify metadata version.\n"); exit(EXIT_FAILURE); } /* metadata->fd には、キューのオーバーフローを示す FAN_NOFD か、 ファイルディスクリプター (負でない整数) のいずれかが入っている。 ここではキューのオーバーフローは無視している。 */ if (metadata->fd >= 0) { /* オープン許可イベントを処理する */ if (metadata->mask & FAN_OPEN_PERM) { printf("FAN_OPEN_PERM: "); /* ファイルのオープンを許可する */ response.fd = metadata->fd; response.response = FAN_ALLOW; write(fd, &response, sizeof(struct fanotify_response)); } /* 書き込み可能ファイルのクローズイベントを処理する */ if (metadata->mask & FAN_CLOSE_WRITE) printf("FAN_CLOSE_WRITE: "); /* アクセスされたファイルのパス名を取得し表示する */ snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", metadata->fd); path_len = readlink(procfd_path, path, sizeof(path) - 1); if (path_len == -1) { perror("readlink"); exit(EXIT_FAILURE); } path[path_len] = '\0'; printf("File %s\n", path); /* イベントのファイルディスクリプターをクローズする */ close(metadata->fd); } /* 次のイベントに進む */ metadata = FAN_EVENT_NEXT(metadata, len); } } } int main(int argc, char *argv[]) { char buf; int fd, poll_num; nfds_t nfds; struct pollfd fds[2]; /* マウントポイントが指定されたか確認する */ if (argc != 2) { fprintf(stderr, "Usage: %s MOUNT\n", argv[0]); exit(EXIT_FAILURE); } printf("Press enter key to terminate.\n"); /* fanotify API にアクセスするためのファイルディスクリプターを作成する */ fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK, O_RDONLY | O_LARGEFILE); if (fd == -1) { perror("fanotify_init"); exit(EXIT_FAILURE); } /* 指定されたマウントに対して以下を監視するようにマークを付ける: - ファイルのオープン前のアクセス許可イベント - 書き込み可能なファイルディスクリプターのクローズ後の 通知イベント */ if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_OPEN_PERM | FAN_CLOSE_WRITE, AT_FDCWD, argv[1]) == -1) { perror("fanotify_mark"); exit(EXIT_FAILURE); } /* ポーリングの準備 */ nfds = 2; /* コンソールの入力 */ fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; /* fanotify の入力 */ fds[1].fd = fd; fds[1].events = POLLIN; /* イベントの発生を待つループ */ printf("Listening for events.\n"); while (1) { poll_num = poll(fds, nfds, -1); if (poll_num == -1) { if (errno == EINTR) /* シグナルに割り込まれた場合 */ continue; /* poll() を再開する */ perror("poll"); /* 予期しないエラー */ exit(EXIT_FAILURE); } if (poll_num > 0) { if (fds[0].revents & POLLIN) { /* コンソールからの入力がある場合: 空の標準入力であれば終了 */ while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n') continue; break; } if (fds[1].revents & POLLIN) { /* fanotify イベントがある場合 */ handle_events(fd); } } } printf("Listening for events stopped.\n"); exit(EXIT_SUCCESS); }
関連項目
fanotify_init(2), fanotify_mark(2), inotify(7)
この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクト の説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。