filemonでファイルの変更イベントを監視する


NetBSD Advent Calendar 2021 6日目の記事です。
今日はファイルの変更を監視するfilemonの紹介をしようと思います。

filemon

filemonはファイルの変更イベントを監視するための疑似デバイスで、NetBSD-6以降でサポートされています。

マニュアルを参照すると、ファイルに対する以下のイベントを監視できます。

イベントタイプ 意味
C ディレクトリの変更(chdir(2))
D ファイルの削除(unlink(2))
E ファイルの実行(exec(3))
F プロセスのフォーク(fork(2), vfork(2))
L シンボリックリンクの操作(link(2), symlink(2))
M ファイル名の変更(rename(2))
R 読み込み専用でのファイルのオープン(open(2))
W 読み書きするファイルのオープン(open(2))
X プロセスの終了(exit(3))
V filemonのバージョン

filemonを試してみる

さて、このfilemonですが、マニュアルだけだとイマイチ使い方が分かりません。そこで、実際に filemon を動かして振る舞いを見てみます。

filemonの準備

filemon は疑似デバイスとして提供されており、カーネルコンフィグに以下を追記することで利用可能となります。

pseudo-device filemon

が、NetBSD-amd64の GENERIC カーネルには pseudo-device filemon は組み込まれておらず、カーネルモジュールとしてビルドしておく必要があります。

# cd /usr/src/sys/modules/filemon/
# make
...
# file filemon.kmod
filemon.kmod: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

あとは filemon.kmod をロードして準備完了です。 /dev/filemon は最初から存在しているようです(なので mknod は不要)。

filemonによるファイルイベントの監視

/dev/filemon を使う準備はできたので、実際にファイルイベントを監視してみます。filemonには /dev/filemon を用いたファイルイベント監視のサンプルコードがあるのですが、実はこのコードはそのままではビルドできない+ mkstemp("/tmp/filemon.XXXXXXX")SEGV してしまいます…(文字列リテラル "/tmp/filemon.XXXXXXX" は実行ファイルのread onlyな領域に配置されるため、 char temp_path[] = "/tmp/filemonXXXXXXXX"; みたいな感じでスタックorヒープ領域に文字列データを置く必要があるっぽい…)。

とはいえ、大まかな処理の流れは把握できるので、このコードを参考にサンプルコードを作成してみました。

さっそくサンプルを動かして振る舞いを見てみましょう。コードの挙動としては、 /dev/filemonopen() したのち、 fork() した子プロセスの側で ioctl(filemon_fd, FILEMON_SET_PID, &pid); でファイルイベントを監視したいプロセスのID(この場合は子プロセスID)を指定し、シェルを立ち上げるという流れになります。

発生したファイルイベントは、あらかじめ temp_fd = mkstemp(temp_path); で作成しておいたファイルに逐次記録される形になります。

# gcc -Wall -Werror -g -o filemon_sample filemon_sample.c
# ./filemon_sample
pid= 484
filemon= /tmp/filemondnp1bePE
# 

ここで cal コマンドを実行してみます。シェル上は普通にコマンドの実行結果が表示されます。

# cal
   December 2021
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

ファイルイベントを記録している /tmp/filemondnp1bePE を見ると、以下が出力されています。

F 893 752
E 752 /usr/bin/cal
R 752 /usr/lib/libterminfo.so.1
R 752 /usr/lib/libc.so.12
R 752 /etc/localtime
R 752 /usr/share/zoneinfo/posixrules
X 752 0

プロセスが fork() ( F )したのち、 cal が実行( E )され、 /usr/lib/libc.so.12 等が読み込み専用でオープンされ( R )、最後にプロセスが終了( X )したことが見て取れます。

この機能を応用すれば、特定のファイルが更新された場合の処理を(ポーリング等を行わない)小さな負荷で実現できそうです。

まとめ

filemonの機能を簡単なサンプルを踏まえて紹介してみました。
GENERIC カーネルに含まれていないのと、マニュアルのサンプルを少し修正する必要があるのが難点ですが、ファイルイベントの監視は応用範囲が広そうです。