inotify(7) API と比較して追加されている機能としては、 マウントされたファイルシステムの全オブジェクトを監視する機能、 アクセス許可の判定を行う機能、 他のアプリケーションによるアクセスの前にファイルを読み出したり変更したりする機能がある。
この API では以下のシステムコールを使用する: fanotify_init(2), fanotify_mark(2), read(2), write(2), close(2)。
fanotify 通知グループはカーネル内部のオブジェクトで、 イベントが作成されるファイル、ディレクトリ、ファイルシステム、マウントポイントのリストを保持する。
fanotify 通知グループの各エントリーには 2 つのビットマスクがある。 mark マスクと ignore マスクである。 mark マスクはどのファイル操作についてイベントを作成するかを定義する。 ignore マスクはどの操作についてイベントを作成しないかを定義する。 これらの 2 種類のマスクがあることで、 ファイルシステム、マウントポイント、ディレクトリに対してイベントの受信を mark しておきつつ、 同時にそのマウントポイントやディレクトリ配下の特定のオブジェクトに対するイベントを無視する、 といったことができる。
fanotify_mark(2) システムコールは、ファイル、ディレクトリ、ファイルシステム、マウントポイントを通知グループに追加し、 どのイベントを報告 (もしくは無視) するかを指定する。 また、このようなエントリーの削除、変更も行う。
ignore マスクの考えられる使用方法はファイルキャッシュに対してである。 ファイルキャッシュに関して興味のあるイベントは、ファイルの変更とファイルのクローズである。 それゆえ、 キャッシュされたディレクトリやマウントポイントは、 これらのイベントを受信するようにマークされる。 ファイルが変更されたという最初のイベントを受信した後は、 対応するキャッシュエントリーは無効化される。 そのファイルがクローズされるまでは、 このファイルに対する変更イベントは興味のない情報となる。 したがって、 変更イベントを ignore マスクに追加することができる。 クローズイベントを受信すると、 変更イベントを ignore イベントから削除し、 ファイルキャッシュエントリーを更新することができる。
The entries in the fanotify notification groups refer to files and directories via their inode number and to mounts via their mount ID. If files or directories are renamed or moved within the same mount, the respective entries survive. If files or directories are deleted or moved to another mount or if filesystems or mounts are unmounted, the corresponding entries are deleted.
Two types of events are generated: notification events and permission events. Notification events are merely informative and require no action to be taken by the receiving application with one exception: if a valid file descriptor is provided within a generic event, the file descriptor must be closed. Permission events are requests to the receiving application to decide whether permission for a file access shall be granted. For these events, the recipient must write a response which decides whether access is granted or not.
イベントは、 読み出されると、 fanotify グループのイベントキューから削除される。 読み出されたアクセス許可イベントは、 fanotify ファイルディスクリプターにアクセス許可の判定が書き込まれるか、 fanotify ファイルディスクリプターがクローズされるまで、 fanotify グループの内部のリストに保持される。
The use of one of the flags FAN_REPORT_FID, FAN_REPORT_DIR_FID in fanotify_init(2) influences what data structures are returned to the event listener for each event. Events reported to a group initialized with one of these flags will use file handles to identify filesystem objects instead of file descriptors.
struct fanotify_event_metadata {
__u32 event_len;
__u8 vers;
__u8 reserved;
__u16 metadata_len;
__aligned_u64 mask;
__s32 fd;
__s32 pid;
};
In case of an fanotify group that identifies filesystem objects by file handles, you should also expect to receive one or more additional information records of the structure detailed below following the generic fanotify_event_metadata structure within the read buffer:
struct fanotify_event_info_header {
__u8 info_type;
__u8 pad;
__u16 len;
};
struct fanotify_event_info_fid {
struct fanotify_event_info_header hdr;
__kernel_fsid_t fsid;
unsigned char file_handle[0];
};
性能上の理由から、複数のイベントを一度の read(2) で取得できるように大きめのバッファーサイズ (例えば 4096 バイト) を使用することを推奨する。
read(2) の返り値はバッファーに格納されたバイト数である。 エラーの場合は -1 が返される (ただし、バグも参照)。
fanotify_event_metadata 構造体のフィールドは以下のとおりである。
fanotify イベントを監視しているプログラムは、 この PID を getpid(2) が返す PID と比較することで、 イベントが監視しているプログラム自身から発生したかどうか、 別のプロセスによるファイルアクセスにより発生したか、を判定できる。
mask のビットマスクは、1 つのファイルシステムオブジェクトに対してどのイベントが発生したかを示す。 監視対象のファイルシステムオブジェクトに複数のイベントが発生した場合は、 このマスクに複数のビットがセットされることがある。 特に、 同じファイルシステムオブジェクトに対する連続するイベントが同じプロセスから生成された場合には、 一つのイベントにまとめられることがある。 例外として、 2 つのアクセス許可イベントが一つのキューエントリーにまとめられることは決してない。
mask でセットされている可能性のあるビットは以下のとおりである。
クローズイベントを確認するために以下のビットマスクを使うことができる。
移動イベントを確認するために以下のビットマスクを使うことができる。
The following bits may appear in mask only in conjunction with other event type bits:
fanotify_event_info_fid 構造体のフィールドは以下のとおりである。
fanotify ファイルディスクリプターからの read(2) が返した fanotify イベントメタデータを含むバッファーに対して繰り返しを行うため、 以下のマクロが提供されている。
また、 以下のマクロも用意されている。
struct fanotify_response {
__s32 fd;
__u32 response;
};
この構造体のフィールドは以下のとおりである。
アクセスを拒否した場合、 アクセスを要求したアプリケーションは EPERM エラーを受け取ることになる。
通常の write(2) のエラーに加え、 fanotify ファイルディスクリプターに書き込みを行った際に以下のエラーが発生することがある。
inotify API は mmap(2), msync(2), munmap(2) により起こったファイルのアクセスと変更を報告しない。
ディレクトリのイベントは、ディレクトリ自身がオープン、読み出し、クローズされた場合にしか作成されない。 マークされたディレクトリでの子要素の追加、削除、変更では、監視対象のディレクトリ自身へのイベントは作成されない。
Fanotify monitoring of directories is not recursive: to monitor subdirectories under a directory, additional marks must be created. The FAN_CREATE event can be used for detecting when a subdirectory has been created under a marked directory. An additional mark must then be set on the newly created subdirectory. This approach is racy, because it can lose events that occurred inside the newly created subdirectory, before a mark is added on that subdirectory. Monitoring mounts offers the capability to monitor a whole directory tree in a race-free manner. Monitoring filesystems offers the capability to monitor changes made from any mount of a filesystem instance in a race-free manner.
ベントキューはオーバーフローすることがある。 この場合、 イベントは失われる。
Linux 3.17 時点では、 以下のバグが存在する。
The following shell session shows an example of running this program. This session involved editing the file /home/user/temp/notes. Before the file was opened, a FAN_OPEN_PERM event occurred. After the file was closed, a FAN_CLOSE_WRITE event occurred. Execution of the program ends when the user presses the ENTER key.
# ./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
/* ファイルディスクリプター '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, 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(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);
}
The following shell sessions show two different invocations of this program, with different actions performed on a watched object.
The first session shows a mark being placed on /home/user. This is followed by the creation of a regular file, /home/user/testfile.txt. This results in a FAN_CREATE event being generated and reported against the file's parent watched directory object and with the created file name. Program execution ends once all events captured within the buffer have been processed.
# ./fanotify_fid /home/user
Listening for events.
FAN_CREATE (file created):
Directory /home/user has been modified.
Entry 'testfile.txt' is not a subdirectory.
All events processed successfully. Program exiting.
$ touch /home/user/testfile.txt # In another terminal
The second session shows a mark being placed on /home/user. This is followed by the creation of a directory, /home/user/testdir. This specific action results in a FAN_CREATE event being generated and is reported with the FAN_ONDIR flag set and with the created directory name.
# ./fanotify_fid /home/user
Listening for events.
FAN_CREATE | FAN_ONDIR (subdirectory created):
Directory /home/user has been modified.
Entry 'testdir' is a subdirectory.
All events processed successfully. Program exiting.
$ mkdir -p /home/user/testdir # In another terminal
#define BUF_SIZE 256
int
main(int argc, char **argv)
{
int fd, ret, event_fd, mount_fd;
ssize_t len, path_len;
char path[PATH_MAX];
char procfd_path[PATH_MAX];
char events_buf[BUF_SIZE];
struct file_handle *file_handle;
struct fanotify_event_metadata *metadata;
struct fanotify_event_info_fid *fid;
const char *file_name;
struct stat sb;
if (argc != 2) {
fprintf(stderr, "Invalid number of command line arguments.\n");
exit(EXIT_FAILURE);
}
mount_fd = open(argv[1], O_DIRECTORY | O_RDONLY);
if (mount_fd == -1) {
perror(argv[1]);
exit(EXIT_FAILURE);
}
/* Create an fanotify file descriptor with FAN_REPORT_DFID_NAME as
a flag so that program can receive fid events with directory
entry name. */
fd = fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME, 0);
if (fd == -1) {
perror("fanotify_init");
exit(EXIT_FAILURE);
}
/* Place a mark on the filesystem object supplied in argv[1]. */
ret = fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_ONLYDIR,
FAN_CREATE | FAN_ONDIR,
AT_FDCWD, argv[1]);
if (ret == -1) {
perror("fanotify_mark");
exit(EXIT_FAILURE);
}
printf("Listening for events.\n");
/* Read events from the event queue into a buffer */
len = read(fd, events_buf, sizeof(events_buf));
if (len == -1 && errno != EAGAIN) {
perror("read");
exit(EXIT_FAILURE);
}
/* バッファー内の全イベントを処理する */
for (metadata = (struct fanotify_event_metadata *) events_buf;
FAN_EVENT_OK(metadata, len);
metadata = FAN_EVENT_NEXT(metadata, len)) {
fid = (struct fanotify_event_info_fid *) (metadata + 1);
file_handle = (struct file_handle *) fid->handle;
/* Ensure that the event info is of the correct type */
if (fid->hdr.info_type == FAN_EVENT_INFO_TYPE_FID ||
fid->hdr.info_type == FAN_EVENT_INFO_TYPE_DFID) {
file_name = NULL;
} else if (fid->hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) {
file_name = file_handle->f_handle +
file_handle->handle_bytes;
} else {
fprintf(stderr, "Received unexpected event info type.\n");
exit(EXIT_FAILURE);
}
if (metadata->mask == FAN_CREATE)
printf("FAN_CREATE (file created):\n");
if (metadata->mask == (FAN_CREATE | FAN_ONDIR))
printf("FAN_CREATE | FAN_ONDIR (subdirectory created):\n");
/* metadata->fd is set to FAN_NOFD when the group identifies
objects by file handles. To obtain a file descriptor for
the file object corresponding to an event you can use the
struct file_handle that's provided within the
fanotify_event_info_fid in conjunction with the
open_by_handle_at(2) system call. A check for ESTALE is
done to accommodate for the situation where the file handle
for the object was deleted prior to this system call. */
event_fd = open_by_handle_at(mount_fd, file_handle, O_RDONLY);
if (event_fd == -1) {
if (errno == ESTALE) {
printf("File handle is no longer valid. "
"File has been deleted\n");
continue;
} else {
perror("open_by_handle_at");
exit(EXIT_FAILURE);
}
}
snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d",
event_fd);
/* 変更された dentry のパスを取得し表示する */
path_len = readlink(procfd_path, path, sizeof(path) - 1);
if (path_len == -1) {
perror("readlink");
exit(EXIT_FAILURE);
}
path[path_len] = '\0';
printf("\tDirectory '%s' has been modified.\n", path);
if (file_name) {
ret = fstatat(event_fd, file_name, &sb, 0);
if (ret == -1) {
if (errno != ENOENT) {
perror("fstatat");
exit(EXIT_FAILURE);
}
printf("\tEntry '%s' does not exist.\n", file_name);
} else if ((sb.st_mode & S_IFMT) == S_IFDIR) {
printf("\tEntry '%s' is a subdirectory.\n", file_name);
} else {
printf("\tEntry '%s' is not a subdirectory.\n",
file_name);
}
}
/* Close associated file descriptor for this event */
close(event_fd);
}
printf("All events processed successfully. Program exiting.\n");
exit(EXIT_SUCCESS);
}