作成日:2012.04.29
更新履歴
(2012.04.29) 2007年1月17日の日記から作成。
はじめに
MacOS や Windows にはディレクトリやファイルを監視して、それらが変更を受けたときにイベントや通知(notification)をあげる機構が備わっている。 MacOS の Active Folder や Windows の FindFirstChangeNotification である。
POSIX にはこれを実現する標準的な方法はないが、Linux には dnotify、inotify、fanotify という監視機構が存在する。 このうち dnotify は歴史的に inotify に摩り替わったようなので inotify と fanotify について解説する。
2つの機能の特徴をまとめておく。
比較項目 | inotify | fanotify |
---|---|---|
実行権限 | ユーザー権限可 | root権限が必要 |
監視範囲 | 指定ディレクトリ直下(複数指定可) | マウントポイント単位(複数指定可) |
監視操作 | creat, open, rename, 属性変更 | create, open |
そのほか | アクセスしているプロセスのIDが分かる |
inotify (inode-based file event notifications)
inotify は Linux 2.6.13 からマージされたファイル報告機能で、指定したディレクトリの直下にあるファイルやディレクトリを監視できる。 監視対象のディレクトリ権限さえあれば監視が可能。 ただし指定ディレクトリから再帰的に監視することはできない。
inotify の監視の流れ以下のようになる。
inotify_init
を実行してファイルディスクリプタ(fd)を得る。inotify_add_watch
を使って監視ディレクトリ(watching directory; wd) を登録する。監視ディレクトリは複数登録できる。- ファイル変更イベントは fd を
read
で読むことで可能である。ファイルが更新するタイミングは fd をselect
、poll
、epoll
でも監視できる。
#include <stdlio.h>
#include <stdlib.h>
#include <sys/inotify.h>
int fd = inotify_init();
if (fd == -1) {
perror("inotify_init"); exit(EXIT_FAILURE);
}
int wd = inotify_add_watch(fd, "/home/nminoru/",
IN_ALL_EVENTS /* 全てのイベントを監視 */);
if (wd == -1) {
perror("inotify_add_watch"); exit(1);
}
/* 監視したいディレクトリの数だけ inotify_add_watch を実行 */
/* イベントの取得の監視 */
int ret = read (fd, buffer, buffer_size);
/* イベントの解析 */
詳細はサンプルの inotify.c を参照のこと。
read
で読み込めるイベントデータは以下の構造体が続いている。
複数の監視ディレクトリを指定した場合、それらは混ざって報告される。
struct inotify_event { int wd; /* Watch descriptor */ uint32_t mask; /* Mask of events */ uint32_t cookie; /* Unique cookie associating related events (for rename(2)) */ uint32_t len; /* Size of name field */ char name[]; /* Optional null-terminated name */ };
wd は inotify_add_watch
の戻り値がいずれかが入っている。
複数の監視ディレクトリを指定した場合に、それを区別するために使える。
mask はイベントの種類を示している。 イベントの種類は以下の通り。
IN_ACCESS | 0x0001 | File was accessed. |
IN_MODIFY | 0x0002 | File was modified. |
IN_ATTRIB | 0x0004 | Metadata changed. |
IN_CLOSE_WRITE | 0x0008 | Writtable file was closed. |
IN_CLOSE_NOWRITE | 0x0010 | Unwrittable file closed. ファイルクローズ時には IN_CLOSE_WRITE か IN_CLOSE_NOWRITE のどちらかが報告される。 |
IN_OPEN | 0x0020 | File was opened. |
IN_MOVED_FROM | 0x0040 | File was moved from X. |
IN_MOVED_TO | 0x0080 | File was moved to Y. |
IN_CREATE | 0x0100 | Subfile was created. |
IN_DELETE | 0x0200 | Subfile was deleted. |
IN_DELETE_SELF | 0x0400 | Self was deleted. |
IN_MOVE_SELF | 0x0800 | Self was moved. |
以下の4つは | ||
IN_UNMOUNT | 0x2000 | Backing fs was unmounted. |
IN_Q_OVERFLOW | 0x4000 | Event queued overflowed. |
IN_IGNORED | 0x8000 | File was ignored. |
IN_MOVED_FROM と IN_MOVED_TO は移動先と移動元の関連付けに cookie フィールドを用いる。
inotify_event
構造体の name には、変更を受けたファイルのファイル名が NULL 終端の文字列で入っている。
inotify_event
構造体のデータは可変長になっている。
ある inotify_event
の次のデータは sizeof(struct inotify_event) + inotify_p->len
の位置からはじまる。
len は "Size of name field" とあるが、これは嘘なので信じては駄目。
注意事項
read
に小さなバッファサイズを指定するとエラーとなる。最低でも数イベント分のバッファを用意すること。- ファイル移動が IN_MOVED_FROM と IN_MOVED_TO で監視できるのは、監視ディレクトリ間の移動だけである。監視非対象のディレクトリから移動したり、監視非対象のディレクトリに移動した場合、IN_MOVED_FROM と IN_MOVED_TO が片側だけ発生する。
fanotify (fscking all notification)
fanotify は Linux 2.6.31 からマージされたファイル報告機能で、inotify と同様にファイルを監視する機能だが以下に
- ファイルにアクセスしているプロセスの情報が PID で取得できる。
- 指定ディレクトリ以下を再帰的に監視範囲にする機能はないが、マウントポイントより下の全てを監視範囲にする機能がある。
- ファイルへの変更が行われる前に通知を出し、変更を許可するか不許可を制御できる。
- ファイルの削除や属性の変更の検出できない。
- 実行に root 権限が必要。
サンプル
以下からサンプルをダウンロードできる。
git clone git://git.infradead.org/users/eparis/fanotify-example.git
システムコール
fanotify は fanotify_init と fanotify_mark の 2 つのシステムコールを使う。 ただし現状は GLIBC 対応がないため、システムコールを直接呼び出さないといけない。
fanotify_init
fanotify_init
は使用時に 1 度だけ呼び出す。
成功すると正値を返す。
これはファイルディスクリプタで read
を実行するとファイル変更情報が得られる。
場合によっては write
も実行する。
失敗時には -1 を返し、errno にエラー番号が返される。
int fanotify_init(unsigned int flags, unsigned int event_f_flags);
下の3つの中から排他指定 | ||
---|---|---|
FAN_CLASS_NOTIF | 0x0000 | FS_PRIO_0 |
FAN_CLASS_CONTENT | 0x0004 | FS_PRIO_1 |
FAN_CLASS_PRE_CONTENT | 0x0008 | FS_PRIO_2 |
以下は OR で繋げて指定 | ||
FAN_CLOEXEC | 0x0001 | |
FAN_NONBLOCK | 0x0002 | |
FAN_UNLIMITED_QUEUE | 0x0010 | max_events の上限を無限にする(デフォルトは FANOTIFY_DEFAULT_MAX_EVENTS) |
FAN_UNLIMITED_MARKS | 0x0020 | max_marks の上限を無限にする(デフォルトは FANOTIFY_DEFAULT_MAX_MARKS) |
FAN_CLASS_NOTIF と FAN_CLASS_CONTENT と FAN_CLASS_PRE_CONTENT は排他指定をする(らしい)。 FAN_ALL_PERM_EVENTS を指定する場合には FAN_CLASS_CONTENT を、それ以外は FAN_CLASS_NOTIF を指定する。
event_f_flags はよく分からないのですが O_RDONLY | O_LARGEFILE を指定するようだ。
fanotify_mark
fanotify の各種操作を行うシステムコールである。 このシステムコールで監視ポイントの追加、削除、フラッシュ(全削除)を行う。
int fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask, int dfd, const char *pathname);
fanotify_fd は fanotify_init
の戻り値を指定する。
flags には以下の表のパラメータを設定する。
以下の3つのうち1つを選んで設定する(排他条件) | ||
---|---|---|
FAN_MARK_ADD | 0x000000001 | mask は必ず必要 |
FAN_MARK_REMOVE | 0x000000002 | mask は必ず必要 |
FAN_MARK_FLUSH | 0x000000080 | mask と dfd は 0、pathname は NULL とし、単独で用いること |
以下は OR でつなげて指定する | ||
FAN_MARK_DONT_FOLLOW | 0x00000004 | |
FAN_MARK_ONLYDIR | 0x00000008 | path に指定できるのはディレクトリだけとする。それ以外を指定すると ENOTDIR のエラーを返す。 |
FAN_MARK_MOUNT | 0x00000010 | ディレクトリ単位ではなくマウントポイント単位で監視する |
FAN_MARK_IGNORED_MASK | 0x00000020 | 監視ポイント下の特定のファイル・ディレクトリを監視対象から外す。FAN_MARK_ADD と一緒に指定すること。mask は FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS を設定すること。 |
FAN_MARK_IGNORED_SURV_MODIFY | 0x00000040 | 監査ファイルをk監視対象から外すために使う。FAN_MARK_MOUNT を指定するなら不要。
|
指定できるかどうか不明 | ||
FAN_Q_OVERFLOW | 0x00004000 | Event queued overflowed |
FAN_EVENT_ON_CHILD | 0x08000000 | ディレクトリの下にあるファイルを監視対象にする。 |
mask には以下の表のパラメータを設定する。
指定できるかどうか不明 | ||
---|---|---|
FAN_MARK_DONT_FOLLOW | 0x00000004 | 指定できないかも |
FAN_MARK_ONLYDIR | 0x00000008 | 指定できないかも |
補足するイベントを指定 | ||
FAN_ACCESS | 0x00000001 | File was accessed |
FAN_MODIFY | 0x00000002 | File was modified |
FAN_CLOSE_WRITE | 0x00000008 | Writtable file closed(*) |
FAN_CLOSE_NOWRITE | 0x00000010 | Unwrittable file closed(*) |
FAN_CLOSE | 0x00000018 | 上2つの OR |
FAN_OPEN | 0x00000020 | File was opened |
FAN_ALL_EVENTS | 0x0000003B | FAN_ACCESS | FAN_MODIFY | FAN_CLOSE | FAN_OPEN の OR |
FAN_OPEN_PERM | 0x00010000 | File open in perm check |
FAN_ACCESS_PERM | 0x00020000 | File accessed in perm check |
FAN_ALL_PERM_EVENTS | 0x00030000 | 上2つの OR |
指定できるかどうか不明 | ||
FAN_Q_OVERFLOW | 0x00004000 | Event queued overflowed |
FAN_ONDIR | 0x40000000 | flags の FAN_MARK_ONLYDIR と同じ |
FAN_EVENT_ON_CHILD | 0x08000000 | interested in child events (*) |
ファイル変更通知
ファイル変更通知は fanotify_fd を read
することで得られる。
read
のバッファに得られる情報は fanotify_event_metadata
構造体の配列である。
inotify と違い fanotify の通知データは実質固定長である。
struct fanotify_event_metadata { __u32 event_len; __u8 vers; __u8 reserved; __u16 metadata_len; __aligned_u64 mask; __s32 fd; __s32 pid; };
- pid はファイルにアクセスしたプロセスの PIDが記録される。
- fd は 0 以上の非負の数の場合、アクセスしたファイルを示す。
- mask はファイル更新の種類を示す。
inotify は通知を受けたファイルをファイル名の文字列で報告したが、fanotify は通知を受けたファイルを監視プロセスの内部に open
する。
これは fd で報告され、fd の値が 0 以上の場合には /proc/self/fd/fd
が対象ファイルとなる。
これはシンボリックリンクなので readlink
で追跡することが可能。
FAN_ACCESS | 0x00000001 | File was accessed |
FAN_MODIFY | 0x00000002 | File was modified |
FAN_CLOSE_WRITE | 0x00000008 | Writtable file closed(*) |
FAN_CLOSE_NOWRITE | 0x00000010 | Unwrittable file closed(*) |
FAN_OPEN | 0x00000020 | File was opened |
FAN_OPEN_PERM | 0x00010000 | File open in perm check |
FAN_ACCESS_PERM | 0x00020000 | File accessed in perm check |
fanotify_event_metadata
構造体のデータを使い終わったら、fd によって開かれているファイルディスクリプタは close
する必要がある。
変更の許可
fanotify_event_metadata
構造体の mask が FAN_OPEN_PERM または FAN_ACCESS_PERM だった場合、変更を許可・不許可を指示することができる。
というか指示しなければならない。
struct fanotify_response { __s32 fd; __u32 response; };
- fd は
fanotify_event_metadata
構造体の fd をそのまま指定する。 - response は FAN_ALLOW か FAN_DENY のどちらかを指定する。
FAN_ALLOW | 0x01 | 変更を許可する |
FAN_DENY | 0x02 | 変更を許可しない |
以下のように書く。
struct fanotify_event_metadata *metadata; struct fanotify_response response_struct; response_struct.fd = metadata->fd; response_struct.response = FAN_ALLOW; int ret; ret = write(fanotify_fd, &response_struct, sizeof(response_struct));
変更の通知を出さなかった場合どうなるかは不明。
リンク
- LWN fanotify: the fscking all notification system
- 情報処理推進機構(IPA) 情報セキュリティ:調査・研究報告書:情報セキュリティ技術動向調査(2010年下期)