前回は、ファイルシステムイベントのモニタリングを実施するためのLinuxカーネルの機能として「inotify API」を取り上げ、サンプルコードから便宜的にエラーハンドリングや汎用性を確保するための処理を抜いていって、APIそのものの使い方を調べていく方法を紹介した。
前回のサンプルコードで結構シンプルなところまでそぎ落としたが、前回のコードにはpoll(2)システムコールの機能が使われていた。inotify APIは、実際にはpoll(2)システムコールを使わなくても同じ処理を実施することができる。サンプルコードでpoll(2)システムコールが使われていたのは、inotify API以外のイベントも捕捉するためだった。今回は、poll(2)システムコールを削除すると共に、関数として分離していた部分をmain()内部に展開し、さらにシンプルなコードにして使い方を調べていく。
さらにシンプルにしてみよう
まず、今回のソースコード用のMakefileを用意する。ソースコードのファイル名は「try_inotify3.c」で、コンパイル後に生成されるバイナリファイル名は「try_inotify3」だ。
SRCS= try_inotify3.c
CMD= try_inotify3
OBJS= ${SRCS:.c=.o}
all: ${OBJS}
cc -o ${CMD} ${OBJS}
.SUFFIXES: .c .c
.c.o:
cc -c $<
clean:
rm -f ${OBJS} ${CMD}
Makefileは、次のように「make」でコンパイルを実施できる。
$ make
ソースコードも書き換えるなら、少し編集するごとにmakeコマンドでコンパイルし、確認しながら作業をしてみてほしい。
先に使い方も説明しておこう。コンパイルして生成されるバイナリファイル「try_inotify3」は次のようにパスを指定して実行する。パスはファイルパスでもディレクトリパスでもかまわない。ディレクトリを指定した場合、ディレクトリ直下のファイルやディレクトリが監視の対象となる。
$ ./try_inotify3 パス [パス パス ...]
try_inotify3を実行したら、別のターミナルからtry_inotify3に指定したファイルに対してテキストデータを追記するなどして、ファイルデータの更新を実施する。すると、try_inotify3側でイベントを検出して、ターミナルにメッセージが表示されるようになる。
$ echo message >> ファイルパス
続いて、poll(2)システムコールを使っていた部分を抜き、さらに別の関数に分離していた部分をmain()の中にマージしたのが次のコードだ。
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
int
main(int argc, char* argv[])
{
int fd;
int wd[1024];
char buf[4096], *ptr;
const struct inotify_event *event;
ssize_t len;
/* inotify API にアクセスするためのファイルディスクリプタを生成 */
fd = inotify_init1(IN_NONBLOCK);
/* モニタリング対象を追加 */
for (int i = 1; i < argc; i++)
/* イベントの種類はIN_MODIFY */
wd[i] = inotify_add_watch(fd, argv[i], IN_MODIFY);
/* モニタリングを実施 */
for (;;) {
/*
* 複数のイベントをバッファに読み込み
* ここでイベントが発生するまで処理が待ち状態になる
*/
len = read(fd, buf, sizeof buf);
/*
* イベントごとに情報を出力
*/
ptr = buf;
while (ptr < buf + len) {
/* バッファにおけるイベントにアクセス */
event = (const struct inotify_event *) ptr;
/* イベント種類を確認 */
if (event->mask & IN_MODIFY)
printf("IN_MODIFY: ");
/* 対象パスを出力
* 引数にファイルパスが指定された場合にはここでファイルパスが出力される
* 引数にディレクトリパスが指定された場合にはここでディレクトリパスが出力される */
for (int i = 1; i < argc; ++i) {
if (wd[i] == event->wd) {
printf("%s", argv[i]);
break;
}
}
/* 対象ファイル名を出力
* 引数にディレクトリパスが指定された場合にはここでファイル名が出力される */
if (event->len)
printf("name: %s", event->name);
/* ファイルシステムオブジェクトの種類を表示 */
if (event->mask & IN_ISDIR)
printf(" [directory]\n");
else
printf(" [file]\n");
/* バッファにおける次のイベントを指し示す */
ptr += sizeof(struct inotify_event) + event->len;
}
}
exit(EXIT_SUCCESS);
}
特に、inotify APIを利用するための本質的な処理を抜き出すと次のようになる。
/* inotify API にアクセスするためのファイルディスクリプタを生成 */
fd = inotify_init1(IN_NONBLOCK);
/* モニタリング対象を追加 */
wd[i] = inotify_add_watch(fd, argv[i], IN_MODIFY);
/*
* 複数のイベントをバッファに読み込み
* ここでイベントが発生するまで処理が待ち状態になる
*/
len = read(fd, buf, sizeof buf);
/* バッファにおけるイベントにアクセス */
ptr = buf;
event = (const struct inotify_event *) ptr;
「C言語はソースコードを見るだけでちょっと頭が痛い……」という方向けに、日本語的にまとめると、次のようになる。
- inotify_init1()でinotify APIを利用するための準備を実行
- inotify_add_watch()でモニタリング対象を追加
- read()で追加したモニタリング対象に何らかのイベントが発生するまで待機
- (const struct inotify_event *)でイベントデータにアクセス
これがinotify APIの最も基本的な使い方である。今回のソースコードで特に注目しておきたいのは「len = read(fd, buf, sizeof buf);」の記述だ。ここでバッファにイベントデータの読み込みを行っているのだが、イベントが発生するまで読み込みは行われない。つまり、監視対象のファイルやディレクトリに何らかの変更が発生するまでは、ここで処理が待ち状態に入り、ストップするのである。動きとしてはこの辺りを把握しておけば、今回掲載したサンプルコードの動作が見えてくるのではないだろうか。
かなりコードをシンプルにして説明してきたが、C言語のソースコードは最近主流の高級言語と比べると難しいところがあるので、いまいちピンと来ないとしても仕方ないのかもしれない。「こういうことができるらしい」ということだけでも、知っておいてもらえればと思う。
実用的なコマンドを作ってみよう
ここまでシンプル化したら、今度はこのサンプルコードを書き換えて実用的なコマンドを作ってみよう。前々回から今回に至るまでは”学習”に過ぎなかったが、実用的なコマンドを作成することで、やっと本連載の目的である「管理の手間を低減する」ことに結び付いてくる。
ファイルやディレクトリの変更をモニタリングして、変更が実施された場合には何らかのコマンドを実行したいというのは比較的ポピュラーな要望だ。簡単な処理だが、実際にこうした処理を行うコマンドがあるととても重宝する。次回は、このAPIを使った簡単なコマンドの開発例を紹介する。