Linux でファイルの変更を検出する(inotify/fanotify)

作成日: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 の違い
比較項目inotifyfanotify
実行権限ユーザー権限可root権限が必要
監視範囲指定ディレクトリ直下(複数指定可)マウントポイント単位(複数指定可)
監視操作creat, open, rename, 属性変更create, open
そのほか アクセスしているプロセスのIDが分かる

inotify (inode-based file event notifications)

inotify は Linux 2.6.13 からマージされたファイル報告機能で、指定したディレクトリの直下にあるファイルやディレクトリを監視できる。 監視対象のディレクトリ権限さえあれば監視が可能。 ただし指定ディレクトリから再帰的に監視することはできない。

inotify の監視の流れ以下のようになる。

  1. inotify_init を実行してファイルディスクリプタ(fd)を得る。
  2. inotify_add_watch を使って監視ディレクトリ(watching directory; wd) を登録する。監視ディレクトリは複数登録できる。
  3. ファイル変更イベントは fd を read で読むことで可能である。ファイルが更新するタイミングは fd を selectpollepoll でも監視できる。
#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 */
};

wdinotify_add_watch の戻り値がいずれかが入っている。 複数の監視ディレクトリを指定した場合に、それを区別するために使える。

mask はイベントの種類を示している。 イベントの種類は以下の通り。

inotify event mask
IN_ACCESS 0x0001File was accessed.
IN_MODIFY 0x0002File was modified.
IN_ATTRIB 0x0004Metadata changed.
IN_CLOSE_WRITE 0x0008Writtable file was closed.
IN_CLOSE_NOWRITE0x0010Unwrittable file closed.
ファイルクローズ時には IN_CLOSE_WRITE か IN_CLOSE_NOWRITE のどちらかが報告される。
IN_OPEN 0x0020File was opened.
IN_MOVED_FROM 0x0040File was moved from X.
IN_MOVED_TO 0x0080File was moved to Y.
IN_CREATE 0x0100Subfile was created.
IN_DELETE 0x0200Subfile was deleted.
IN_DELETE_SELF 0x0400Self was deleted.
IN_MOVE_SELF 0x0800Self was moved.
以下の4つは
IN_UNMOUNT 0x2000Backing fs was unmounted.
IN_Q_OVERFLOW 0x4000Event queued overflowed.
IN_IGNORED 0x8000File 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" とあるが、これは嘘なので信じては駄目。

注意事項

fanotify (fscking all notification)

fanotify は Linux 2.6.31 からマージされたファイル報告機能で、inotify と同様にファイルを監視する機能だが以下に

  1. ファイルにアクセスしているプロセスの情報が PID で取得できる。
  2. 指定ディレクトリ以下を再帰的に監視範囲にする機能はないが、マウントポイントより下の全てを監視範囲にする機能がある。
  3. ファイルへの変更が行われる前に通知を出し、変更を許可するか不許可を制御できる。
  4. ファイルの削除や属性の変更の検出できない。
  5. 実行に 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);
fanotify_initflags
下の3つの中から排他指定
FAN_CLASS_NOTIF 0x0000FS_PRIO_0
FAN_CLASS_CONTENT 0x0004FS_PRIO_1
FAN_CLASS_PRE_CONTENT0x0008FS_PRIO_2
以下は OR で繋げて指定
FAN_CLOEXEC 0x0001 
FAN_NONBLOCK 0x0002 
FAN_UNLIMITED_QUEUE 0x0010max_events の上限を無限にする(デフォルトは FANOTIFY_DEFAULT_MAX_EVENTS)
FAN_UNLIMITED_MARKS 0x0020max_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_fdfanotify_init の戻り値を指定する。 flags には以下の表のパラメータを設定する。

fanotify_markflags
以下の3つのうち1つを選んで設定する(排他条件)
FAN_MARK_ADD 0x000000001mask は必ず必要
FAN_MARK_REMOVE 0x000000002mask は必ず必要
FAN_MARK_FLUSH 0x000000080mask と dfd は 0、pathname は NULL とし、単独で用いること
以下は OR でつなげて指定する
FAN_MARK_DONT_FOLLOW 0x00000004 
FAN_MARK_ONLYDIR 0x00000008path に指定できるのはディレクトリだけとする。それ以外を指定すると 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_MODIFY0x00000040監査ファイルをk監視対象から外すために使う。FAN_MARK_MOUNT を指定するなら不要。
  • /var/log/audit/audit.log
  • /var/log/messages
  • /var/log/wtmp
  • /var/run/utmp
FAN_MARK_ADD とFAN_MARK_IGNORED_MAS と一緒に指定すること。mask は FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS を設定すること。
指定できるかどうか不明
FAN_Q_OVERFLOW 0x00004000Event queued overflowed
FAN_EVENT_ON_CHILD 0x08000000ディレクトリの下にあるファイルを監視対象にする。

mask には以下の表のパラメータを設定する。

fanotify_markmask
指定できるかどうか不明
FAN_MARK_DONT_FOLLOW 0x00000004指定できないかも
FAN_MARK_ONLYDIR 0x00000008指定できないかも
補足するイベントを指定
FAN_ACCESS 0x00000001File was accessed
FAN_MODIFY 0x00000002File was modified
FAN_CLOSE_WRITE 0x00000008Writtable file closed(*)
FAN_CLOSE_NOWRITE 0x00000010Unwrittable file closed(*)
FAN_CLOSE 0x00000018上2つの OR
FAN_OPEN 0x00000020File was opened
FAN_ALL_EVENTS 0x0000003BFAN_ACCESS | FAN_MODIFY | FAN_CLOSE | FAN_OPEN の OR
FAN_OPEN_PERM 0x00010000File open in perm check
FAN_ACCESS_PERM 0x00020000File accessed in perm check
FAN_ALL_PERM_EVENTS 0x00030000上2つの OR
指定できるかどうか不明
FAN_Q_OVERFLOW 0x00004000Event queued overflowed
FAN_ONDIR 0x40000000flags の FAN_MARK_ONLYDIR と同じ
FAN_EVENT_ON_CHILD 0x08000000interested in child events (*)

ファイル変更通知

ファイル変更通知は fanotify_fdread することで得られる。 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;
};

inotify は通知を受けたファイルをファイル名の文字列で報告したが、fanotify は通知を受けたファイルを監視プロセスの内部に open する。 これは fd で報告され、fd の値が 0 以上の場合には /proc/self/fd/fd が対象ファイルとなる。 これはシンボリックリンクなので readlink で追跡することが可能。

mask
FAN_ACCESS 0x00000001File was accessed
FAN_MODIFY 0x00000002File was modified
FAN_CLOSE_WRITE 0x00000008Writtable file closed(*)
FAN_CLOSE_NOWRITE 0x00000010Unwrittable file closed(*)
FAN_OPEN 0x00000020File was opened
FAN_OPEN_PERM 0x00010000File open in perm check
FAN_ACCESS_PERM 0x00020000File 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;
};
response に種類
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));

変更の通知を出さなかった場合どうなるかは不明。

  1. LWN fanotify: the fscking all notification system
  2. 情報処理推進機構(IPA) 情報セキュリティ:調査・研究報告書:情報セキュリティ技術動向調査(2010年下期)

コメント

コメントを書き込む

TOP    掲示板    戻る
Written by NAKAMURA Minoru, Email: nminoru atmark nminoru dot jp, Twitter:@nminoru_jp