inotify を使ってファイルシステムのアクティビティーを監視する

独自のアプリケーションを作成する、あるいはオープンソースのツール・スイートを使う

inotify は Linux® の機能であり、読み取りや書き込み、作成など、ファイルシステムによる操作を監視します。inotify は受動型であり、驚くほど使い方が簡単であり、例えば cron ジョブで頻繁にポーリングするよりもはるかに効率的です。独自のアプリケーションの中に inotify を統合する方法と、システム管理を自動化するための一連のコマンドライン・ツールについて学びましょう。

Martin Streicher, Web developer, 自由职业者

Photo of Martin StreicherMartin Streicher is a freelance Ruby on Rails developer and the former Editor-in-Chief of Linux Magazine. Martin holds a Masters of Science degree in computer science from Purdue University and has programmed UNIX-like systems since 1986. He collects art and toys.



2008年 9月 16日

システム管理は人生にとてもよく似ています。歯を磨いたり野菜を食べたりするのと同じように、ちょっとした毎日のメンテナンスによってマシンは快調に動作します。一時ファイルや使用済みログ・ファイルなどのゴミは定期的にきれいに片付ける必要があります。こうしたゴミは、フォームへの記入や呼び出しへの応答、更新のダウンロード、プロセスの監視などによる無数の割り込みの残骸として発生します。幸いなことに、シェル・スクリプトによる自動化や Nagios などのツールを使った監視、そしてよくある cron によるジョブ・スケジューリングなどによって、メンテナンス作業を楽にすることができます。

しかし奇妙なことに、こうしたツールはどれも受動型ではありません。もちろん、ある条件を監視するために、cron ジョブが頻繁に実行されるようにスケジューリングすることはできます。しかし、そうした頻繁なポーリングは大幅にリソースを消費し、また推測に基づいて動作するため、あまりスケーラブルではありません。例えば FTP (File Transfer Protocol) で送られてくるデータが格納されるドロップボックスをいくつか監視する場合、各ターゲット・ディレクトリーを find コマンドを使ってスキャンし、新しいものを列挙することができます。この操作は一見問題がなさそうですが、find コマンドを呼び出すたびに新しいシェルが生成され、それぞれのシェルではディレクトリーのオープンやスキャン等のために大量のシステム・コールが必要になります。非常に頻繁に行われる大量のポーリング・ジョブは、すぐに累積されてしまいます (もっと悪いことに、頻繁なポーリングは必ずしも適切ではありません。ファイルシステム・ブラウザー (Mac OS X の Finder など) をポーリングして更新を調べる場合のリソース使用量と複雑さを想像してみてください)。

では、管理者はどうすればよいのでしょう。幸い、この場合にも、いつも頼りになるコンピューターに支援を求めることができます。

inotify について知る

inotify は Linux カーネルの機能であり、ファイルシステムを監視し、重要なイベント (削除、読み取り、書き込み、さらにはアンマウントなどの操作など) があると、対象のアプリケーションに即座に警告します。その他にも魅力的な機能があり、移動元や移動先を追跡することもできます。

inotify の使い方は簡単です。ファイル記述子を作成し、その記述子に 1 つ以上の監視対象 (監視対象というのはパスと一連のイベントです) を記述します。そして read() を使ってその記述子からイベント情報を取得します。inotify では、貴重なサイクルを浪費されることはなく、イベントが発生するまで read() はブロックされます。

もっと良いことに、inotify は従来のファイル記述子で動作するため、従来の select() システム・コールを活用することで、監視対象と共に他の多数の入力ソースを同時に受動的に監視することができます。どちらの手法も (つまりファイル記述子を利用して動作をブロックする方法も、select() によって多重化する方法も) 頻繁なポーリングを避けるために有効です。

では、inotify を深く掘り下げ、少しばかり C コードを作成しましょう。そしてファイルシステムのイベントにコマンドやスクリプトを追加するために作成して使用することができる、一連のコマンドライン・ツールについて調べてみましょう。inotify は真夜中に猫を表に放すわけではありませんが、catwget を走らせる (実行する) ことができ、まさに実行が必要なそのタイミングに実行できるのです。

inotify を使うためにはカーネルが 2.6.13 以降の Linux マシンが必要です。(それ以前のバージョンの Linux カーネルでは、inotify よりもはるかに機能の劣る、dnotify というファイル・モニターを使用しています)。カーネルのバージョンがわからない場合には、シェルで uname -a と入力します。

%uname -a
Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux

表示されるカーネルのバージョンが 2.6.13 以降であれば、そのシステムは inotify をサポートしているはずです。また、マシンの中に /usr/include/sys/inotify.h というファイルがないかどうかを調べる方法もあります。このファイルがあれば、そのカーネルは inotify をサポートしているはずです。

注意: FreeBSD、ひいては Mac OS X には、inotify に似た kqueue が用意されています。詳細については FreeBSD マシンで man 2 kqueue と入力としてみてください。

この記事は、Mac OS X バージョン 10.5 Leopard 上の Parallels Desktop バージョン 3.0 の下で実行される Ubuntu Desktop のバージョン 8.04.1 (別名 Hardy) を基にしています。


inotify の C 言語用 API

inotify には 3 つのシステム・コールが用意されており、あらゆる種類のファイルシステム・モニターを作成することができます。

  • inotify_init() はカーネルの中に inotify サブシステムのインスタンスを作成し、成功するとファイル記述子を返し、失敗すると -1 を返します。他のシステム・コールの場合と同様、inotify_init() が失敗した場合には診断のために errno を調べます。
  • inotify_add_watch() はその名前のとおり、監視対象を追加します。各監視対象は、パス名と、対象とするイベントのリストを提供する必要があります (各イベントは IN_MODIFY などの定数で指定されます)。複数のイベントを監視するためには、単純に各イベントの間で論理 OR (C ではパイプ (|) 演算子) を使います。inotify_add_watch() が成功すると、その呼び出しによって、登録された監視対象に対応する固有の識別子が返されます。失敗した場合には -1 が返されます。関連付けられた監視対象を変更または削除するためには、その識別子を使います。
  • inotify_rm_watch() は監視対象を削除します。

また、read()close() というシステム・コールも必要です。inotify_init() によって記述子が read() を呼び出してアラートが表示されるのを待ちます。ファイル記述子が典型的なものだとすると、アプリケーションは、(ストリームの中のデータとして表現される) イベントを受信するまでブロックされます。inotify_init() によって生成されたファイル記述子に対して close() を呼び出すと、アクティブな監視対象すべてと、inotify インスタンスに関連付けられたすべてのメモリーとが削除され、解放されます。(この場合にも、参照を考慮した通常の注意事項を守る必要があります。つまり監視対象や inotify が使用していたメモリーを解放する前に、インスタンスに関連付けられていたすべてのファイル記述子を閉じる必要があります。)

これで終わりです。たった 3 つの API (application program interface) 呼び出しと、単純でおなじみの「すべてはファイルである」という仕組みによって、強力なツールが得られたのです。では、サンプル・アプリケーションに進むことにしましょう。

サンプル・アプリケーション: イベントの監視

リスト 1 は、ディレクトリーを監視して 2 つのイベント (ファイルの作成と削除) を検出する簡単な C プログラムです。

リスト 1. ディレクトリーを監視し、作成、削除、変更イベントを検出する簡単な inotify アプリケーション
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

int main( int argc, char **argv ) 
{
  int length, i = 0;
  int fd;
  int wd;
  char buffer[BUF_LEN];

  fd = inotify_init();

  if ( fd < 0 ) {
    perror( "inotify_init" );
  }

  wd = inotify_add_watch( fd, "/home/strike", 
                         IN_MODIFY | IN_CREATE | IN_DELETE );
  length = read( fd, buffer, BUF_LEN );  

  if ( length < 0 ) {
    perror( "read" );
  }  

  while ( i < length ) {
    struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
    if ( event->len ) {
      if ( event->mask & IN_CREATE ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was created.\n", event->name );       
        }
        else {
          printf( "The file %s was created.\n", event->name );
        }
      }
      else if ( event->mask & IN_DELETE ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was deleted.\n", event->name );       
        }
        else {
          printf( "The file %s was deleted.\n", event->name );
        }
      }
      else if ( event->mask & IN_MODIFY ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was modified.\n", event->name );
        }
        else {
          printf( "The file %s was modified.\n", event->name );
        }
      }
    }
    i += EVENT_SIZE + event->len;
  }

  ( void ) inotify_rm_watch( fd, wd );
  ( void ) close( fd );

  exit( 0 );
}

このアプリケーションでは fd = inotify_init(); により inotify インスタンスを作成し、さらに /home/strike でのファイルの変更、新規作成、破棄を監視するために wd = inotify_add_watch(...) で指定される1 つの監視対象を追加しています。read() は 1 つ以上のアラートが到着するまでブロックされます。アラートの詳細である (各ファイルとイベント) は、バイト・ストリームとして送信されるため、アプリケーションの中のループによって、そのバイト・ストリームが一連のイベント構造体の中にキャストされます。

イベント構造体の定義 (C の struct) はファイル /usr/include/sys/inotify.h. の中にあります (リスト 2)。

リスト 2. イベント構造体の定義
struct inotify_event 
{
  int wd; 		/* The watch descriptor */
  uint32_t mask; 	/* Watch mask */
  uint32_t cookie;	/* A cookie to tie two events together */
  uint32_t len;		/* The length of the filename found in the name field */
  char name __flexarr;	/* The name of the file, padding to the end with NULs */	
}

wd フィールドは、そのイベントに関連付けられた監視対象を指しています。1 つの inotify インスタンスに対して複数の監視対象がある場合には、このフィールドを利用してその後の処理方法を判断します。mask フィールドは何が起きたかを特定するための一連のビットです。それぞれのビットを別々にテストする必要があります。

cookie を使うと、あるディレクトリーから別のディレクトリーにファイルが移動されたような場合に 2 つのイベントを結びつけることができます。移動元のディレクトリーと移動先のディレクトリーを監視している場合にのみ、inotify は 2 つの move イベント (移動元と移動先のディレクトリーに対してそれぞれ 1 つ) を生成し、cookie を設定することでその 2 つを結びつけます。移動を監視するためには、IN_MOVED_FROM または IN_MOVED_TO を指定するか、あるいは両方を監視する簡略型の IN_MOVE を使います。IN_MOVED_FROMIN_MOVED_TO を使うとイベント・タイプをテストすることができます。

最後に、namelen には影響を受けたファイルの (パスを含まない) ファイル名とファイル名の長さが含まれています。

サンプル・アプリケーションのコードをビルドする

コードをビルドするためには、ディレクトリー /home/strike を例えばホーム・ディレクトリーに変更し、そのコードをファイルに保存して、C コンパイラー (大部分の Linux システムでは通常は gcc) を呼び出します。すると実行可能ファイルが生成されるので、このファイルを実行します (リスト 3)。

リスト 3. 実行可能ファイルを実行する
%cc -o watcher watcher.c
%./watcher

watcher が実行している状態で 2 番目のターミナル・ウィンドウを開き、touchcatrm を使ってホーム・ディレクトリーの内容を変更します (リスト 4)。それぞれを実験した後、新しいアプリケーションを再起動します。

リスト 4. touch、cat、rm を使う
%cd $HOME
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher  &
%rm a b c
The file a was deleted.
The file b was deleted.
The file c was deleted.

%./watcher  &
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher  &
%cat /etc/passwd >> a
The file a was modified.

%./watcher  &
%mkdir d
The directory d was created.

他に利用可能な監視対象フラグを使って実験してみてください。パーミッションへの変更をキャッチするためには、マスクに IN_ATTRIB を追加します。

inotify を使うためのヒント

動作がブロックされるのを避けるために、select()pselect()poll()epoll() を使って実験することもできます。これは監視対象の監視をグラフィック・アプリケーションのメインのイベント処理ループの一部として、あるいは他の種類の着信接続を監視するデーモンの一部として行いたい場合に便利です。単純に inotify 記述子を一連の記述子に追加すると、並行して監視を行うことができます。リスト 5 は正規形の select() を示しています。

リスト 5. 正規形の select()
int return_value;
fd_set descriptors;
struct timeval time_to_wait;

FD_ZERO ( &descriptors );
FD_SET( ..., &descriptors );
FD_SET ( fd, &descriptors );

...

time_to_wait.tv_sec = 3;
time.to_waittv_usec = 0;

return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait);

if ( return_value < 0 ) {
	/* Error */
}

else if ( ! return_value ) {
	/* Timeout */
}

else if ( FD_ISSET ( fd, &descriptors ) ) {
	/* Process the inotify events */
	...
}

else if ...

select() によって time_to_wait 秒の間プログラムが一時停止します。しかし設定された記述子の中のいずれかのファイル記述子に対して、その停止時間の間に何らかのアクティビティーが発生すると、即座に実行が再開されます。それ以外の場合は、time_to_wait 秒経過すると、アプリケーションは他の処理を行うことができます (例えば GUI (graphical user interface) ツールでマウス・イベントまたはキーボード・イベントに応答するなど)。

inotify を使う上でのヒントには他に次のようなものがあります。

  • 監視対象のファイルまたはディレクトリーが削除されると、そのための監視対象は自動的に削除されます (削除イベントを送信した方が適切な場合には、削除イベントを送信してから削除されるようにもできます)。
  • アンマウントされたファイルまたはディレクトリー上のファイルを監視している場合には、その監視対象がアンマウント・イベントを受信し、その後、影響を受けるすべての監視対象が削除されます。
  • 監視対象マスクに IN_ONESHOT フラグを追加すると、1 度限りのアラートを設定することができます。そのアラートは 1 度送信されると削除されます。
  • イベントを変更するためには、同じパス名を異なるマスクで提供します。新しい監視対象によって古い監視対象は置き換えられます。
  • 現実的には、inotify インスタンスをどのように指定しても監視対象が足りなくなる可能性は低いものです。しかしイベントを処理する頻度によってはイベント・キューのスペースが足りなくなる場合があります。キューがオーバーフローすると IN_Q_OVERFLOW イベントが発生します。
  • close() メソッドは、inotify のインスタンスとそのインスタンスに関連するすべての監視対象を破棄し、キューの中にある処理待ちのイベントをすべてキューから削除します。

inotify-tools スイートをインストールする

inotify のプログラミング・インターフェースの使い方は簡単ですが、独自のツールを作成したくない場合には、オープンソースの inotify-tools ライブラリー (「参考文献」にリンクがあります) が便利で柔軟な代わりの手段となります。このライブラリーには、ファイルシステムのアクティビティーを監視するための (以下に示す) 1 対のコマンドライン・ユーティリティーが用意されています。

  • inotifywait は単純に動作をブロックし、inotify イベントを待機します。任意のファイル・セットやディレクトリー・セットのみならず、ディレクトリー・ツリー全体 (ディレクトリーとそのサブディレクトリー、さらにそのサブディレクトリー等々) を監視対象にすることもできます。inotifywait はシェル・スクリプトの中で使います。
  • inotifywatch は監視対象のファイルシステムに関する統計 (例えば各 inotify イベントが何度発生したかなど) を収集します。

この記事の執筆時点で、inotify-tools ライブラリーの最新バージョンは 2008年1月1日にリリースされたバージョン 3.13 です。inotify-tools をインストールする方法は 2 つあります。このソフトウェアを自分でダウンロードしてビルドする方法と、既知のリポジトリーに inotify-tools が含まれている場合には Linux ディストリビューションのパッケージ・マネージャーを使ってバイナリーの集合をインストールする方法があります。Debian ベースのディストリビューションにより後者の方法でインストールするためには、apt-cache search inotify を実行し、inotify-tools を検索します (リスト 6)。私がこの記事を書くために使用したサンプル・システム (Ubuntu Desktop バージョン 8.04) では、inotify-tools は既に用意されています。

リスト 6. inotify-tools を検索する
%apt-cache search inotify
incron - cron-like daemon which handles filesystem events
inotail - tail replacement using inotify
inoticoming - trigger actions when files hit an incoming directory
inotify-tools - command-line programs providing a simple interface to inotify
iwatch - realtime filesystem monitoring program using inotify
libinotify-ruby - Ruby interface to Linux's inotify system
libinotify-ruby1.8 - Ruby interface to Linux's inotify system
libinotify-ruby1.9 - Ruby interface to Linux's inotify system
libinotifytools0 - utility wrapper around inotify
libinotifytools0-dev - Development library and header files for libinotifytools0
liblinux-inotify2-perl - scalable directory/file change notification
muine-plugin-inotify - INotify Plugin for the Muine music player
python-kaa-base - Base Kaa Framework for all Kaa Modules
python-pyinotify - Simple Linux inotify Python bindings
python-pyinotify-doc - Simple Linux inotify Python bindings
%sudo apt-get install inotify-tools
...
Setting up inotify-tools.

コードのビルドは簡単です。ソース・コードをダウンロードして解凍し、構成してコンパイルし、そしてインストールします (リスト 7)。このプロセス全体に要する時間は 3 分程度です。

リスト 7. コードをビルドする
%wget \
    http://internap.dl.sourceforge.net/sourceforge/inotify-tools/inotify-tools-3.13.tar.gz
%tar zxvf inotify-tools-3.13.tar.gz
inotify-tools-3.13/
inotify-tools-3.13/missing
inotify-tools-3.13/src/
inotify-tools-3.13/src/Makefile.in
...
inotify-tools-3.13/ltmain.sh

%cd inotify-tools.3.13
%./configure
%make
%make install

これで inotify-tools を使う準備が整いました。例えばホーム・ディレクトリー全体を監視して変更を検出する場合には inotifywait を実行します。最も単純な呼び出し方は inotifywait -r -m です。この呼び出しは引数に指定されたファイルやディレクトリーを再帰的に監視し (-r)、各イベントの後でもこのユーティリティーの実行を継続させます (-m)。

%inotifywait -r  -m $HOME
Watches established.

別のターミナル・ウィンドウを立ち上げ、ホーム・ディレクトリーを少し変更してみます。興味深いことに、Is により単純なディレクトリー・リストを表示してもイベントが発生します。

/home/strike OPEN,ISDIR

inotifywait の man ページを読むと、イベントを特定のリストに記載されたイベントに制限するためのオプション (そのリストを作成するためには -e event_name オプションを繰り返し使用します) や、また再帰的な監視対象から一定の条件に一致するファイルを除外するためのオプション (--exclude pattern) などが説明されています。


inotify を使用してください

上記の apt-cache からわかるように、他にも知っておくと便利な inotify ベースのユーティリティーがあります。incron ユーティリティーは cron の仲間ですが、スケジュールに反応するのではなく、inotify のイベントに反応します。inoticoming ユーティリティーはドロップボックスの監視専用に設計されたものです。また皆さんが Perl、Ruby、あるいは Python の開発者の場合には、お好みのスクリプト言語から簡単に inotify を呼び出すためのモジュールやライブラリーがあります。

例えば Perl でコーディングする人であれば、Linux::Inotify2 (詳細は「参考文献」を参照) を使って inotify の機能を Perl アプリケーションに組み込むことができます。リスト 8 のコードは Linux::Inotify2 の README ファイルから引用したものですが、イベントを監視するためのコールバック・インターフェースを示しています。

リスト 8. イベントを監視するためのコールバック・インターフェース
use Linux::Inotify2;

my $inotify = new Linux::Inotify2 
	or die "Unable to create new inotify object: $!"; 

 # for Event:
 Event->io (fd =>$inotify->fileno, poll => 'r', cb => sub { $inotify->poll });

 # for Glib:
 add_watch Glib::IO $inotify->fileno, in => sub { $inotify->poll };

 # manually:
 1 while $inotify->poll;

 # add watchers
 $inotify->watch ("/etc/passwd", IN_ACCESS, sub {
    my $e = shift;
    my $name = $e->fullname;
    print "$name was accessed\n" if $e->IN_ACCESS;
    print "$name is no longer mounted\n" if $e->IN_UNMOUNT;
    print "$name is gone\n" if $e->IN_IGNORED;
    print "events for $name have been lost\n" if $e->IN_Q_OVERFLOW; 

    # cancel this watcher: remove no further events
    $e->w->cancel;
 });

Linux ではすべてがファイルであるため、誰でも inotify の監視対象の使い方を無数に見つけられるはずです。

したがって実際に問題となるのは、「誰が監視対象を監視するのか」ということになります。

参考文献

学ぶために

製品や技術を入手するために

  • Linux::Inotify2 について学んでください。
  • ファイルシステムのイベント監視するための一連のコマンドライン・ユーティリティー、inotify-tools のソース・コードをダウンロードしてください。
  • developerWorks から直接ダウンロードできる IBM ソフトウェアの試用版を利用して皆さんの次期 Linux 開発プロジェクトを構築してください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=345950
ArticleTitle=inotify を使ってファイルシステムのアクティビティーを監視する
publish-date=09162008