inotify で Linux ファイルシステムのイベントを監視する

2.6 カーネルでの効率的かつ効果的なファイルシステムのイベント監視

Linux® ファイルシステムのイベントを効率的に、細かい粒度で非同期に監視する必要があるときには、inotify を使用してください。inotify は、セキュリティーやパフォーマンスなど、さまざまな目的でユーザー空間を監視するために使用することができます。

Ian Shields, Senior Programmer, IBM

Ian ShieldsIan Shields は、developerWorks Linux ゾーンの様々な Linux プロジェクトに関わっています。彼はノースキャロライナ州 Research Triangle Park にある IBM のシニア・プログラマーです。1973年にオーストラリアのキャンベラでシステム・エンジニアとして IBM に入社して以来、カナダのモントリオールやノースキャロライナ州 Research Triangle Park で、コミュニケーション・システムやパーベイシブ・コンピューティングに携わってきました。彼はいくつかの特許を保持しています。Australian National University にて純粋数学および哲学で学位を取得し、また North Carolina State University にてコンピューター・サイエンスで修士号と博士号を取得しています。Ian について詳しく知るには、My developerWorks で彼のプロフィールを見てください。


developerWorks 貢献著者レベル

2010年 4月 06日

Ian とつながるには

Ian は developerWorks で人気の高いお馴染みの著者の 1 人です。Ian が書いたすべての developerWorks 記事を閲覧してみてください。また、My developerWorks では、Ian のプロフィールを調べることや、彼やその他の著者、そして他の読者とつながることができます。

Linux カーネルに inotify が組み込まれるようになる前に、この記事の前身となる記事を書いた IBM の Eli Dow 氏に深く感謝します。特に、「ダウンロード」セクションに用意したサンプル・コードは、今でもその大部分が Eli によって作成されたオリジナルのサンプル・コードに基づいています。

inotify の紹介

ファイルシステムのイベント監視は、ファイル・マネージャーからセキュリティー・ツールに至るまでのさまざまなタイプのプログラムにとって必要不可欠です。Linux 2.6.13 カーネルから Linux に組み込まれた inotify により、監視プログラムは 1 つのファイル記述子を開くだけで、1 つ以上のファイルやディレクトリーでオープン、クローズ、移動/名前変更、削除、作成、属性の変更などといった指定のイベント一式を監視できるようになりました。2.6.13 に続くカーネルではいくつかの機能拡張が行われたので、inotify の機能を利用する場合は、その前にカーネル・レベルを確認してください。

この記事では、単純な監視アプリケーションで inotify の機能を利用する方法を説明します。サンプル・コードをダウンロードし、お使いのシステムでコンパイルして詳しく調べてください。


inotify 略史

inotify が登場する以前には dnotify がありましたが、残念ながら dnotify には制限があったため、ユーザーはこれよりも優れたメカニズムを望んでいました。dnotify にはない inotify の利点としては、例えば以下の点が挙げられます。

  • inotify が使用するファイル記述子は 1 つだけですが、dnotify では変更を監視するディレクトリーごとに 1 つのファイル記述子を開く必要があります。そのため、一度に複数のディレクトリーを監視する場合には非常にコストがかかるだけでなく、プロセスごとのファイル記述子の制限に達する可能性もあります。
  • inotify で使用するファイル記述子はシステム・コールによって取得されるため、デバイスやファイルには関連付けられません。dnotify の場合、ファイル記述子によってディレクトリーが固定されることから、補助デバイスをアンマウントすることができません。このことは、リムーバブル・メディアでは特に問題になります。inotify を使用する場合は、ファイルシステムがアンマウントされると、そこで監視対象となっていたファイルやディレクトリーによってイベントが生成され、監視が自動的に解除されます。
  • inotify はファイルまたはディレクトリーを監視することができます。一方、dnotify が監視するのはディレクトリーです。そのため、ディレクトリー内のファイルやサブディレクトリーに何が起こっているかを知るには、プログラマーが監視対象のディレクトリー内のファイルを stat 構造体、またはそれに相当するデータ構造体に反映し続け、イベントの発生後にこれらの構造体と現状とを比較しなければなりませんでした。
  • 前述のとおり、inotify はファイル記述子を使用するため、プログラマーが標準の select 関数や poll 関数を使ってイベントを監視することができます。これにより、効率的な多重化 I/O や Glib の mainloop との統合が可能になります。これに対し、dnotify が使用するシグナルは、プログラマーにとって扱いにくく、複雑になりがちです。カーネル 2.6.25 の inotify には、シグナル駆動の I/O 通知も追加されています。

inotify の API

inotify は、最小限のファイル記述子を使用し、細かい粒度で監視できる単純な API を提供しています。inotify との通信を確立する手段はシステム・コールです。inotify API で使用できる関数には以下のものがあります。

inotify_init
inotify インスタンスを作成し、そのインスタンスを参照するファイル記述子を返すシステム・コールです。
inotify_init1
inotify_init と同様のシステム・コールですが、inotify_init にフラグが追加されています。フラグが指定されない場合の振る舞いは、inotify_init と同じです。
inotify_add_watch
ファイルまたはディレクトリーの監視を追加し、監視するイベントを指定します。フラグによって、イベントを既存の監視に追加するかどうか、パスがディレクトリーを表す場合にのみ監視を行うかどうか、シンボリック・リンクを後に続けるかどうか、そして監視を 1 回限りの監視とし、最初のイベントの後に監視を停止するかどうかを制御します。
inotify_rm_watch
監視対象の項目を監視リストから削除します。
read
1 つ以上のイベントに関する情報が含まれるバッファーを読み取ります。
close
ファイル記述子を閉じます。ファイル記述子に監視対象の項目が残っている場合には、すべての項目を削除します。inotify インスタンスのファイル記述子がすべて閉じられると、リソースおよびベースとなるオブジェクトが解放され、カーネルが再びそれらのリソースを使用できるようになります。

典型的な監視プログラムの操作の流れは以下のとおりです。

  1. inotify_init を使用してファイル記述子を開きます。
  2. 1 つ以上の監視を追加します。
  3. イベントを待機します。
  4. イベントを処理した後、イベントの待機状態に戻ります。
  5. アクティブな監視がなくなった場合やシグナルを受信した場合、ファイル記述子を閉じ、クリーンアップを行って終了します。

以降のセクションでは、監視可能なイベントと、これらのイベントがサンプル・プログラムでどのように機能するかを見ていきます。そして最後に、イベント監視の動作を説明します。


通知

アプリケーションが通知を読み込むと、1 つ以上のイベントのシーケンスが指定のバッファーに読み込まれます。イベントは可変長の構造体の形で返されます (リスト 1 を参照)。バッファーがデータでいっぱいになった場合には、最後にエントリーされているイベント情報の一部を扱ったり、イベントの名前の一部を扱ったりする必要が出てくるかもしれません。

リスト 1. inotify のイベント構造体
struct inotify_event
{
  int wd;               /* Watch descriptor.  */
  uint32_t mask;        /* Watch mask.  */
  uint32_t cookie;      /* Cookie to synchronize two events.  */
  uint32_t len;         /* Length (including NULs) of name.  */
  char name __flexarr;  /* Name.  */
  };

注意する点として、名前フィールドが存在するのは、監視対象の項目がディレクトリーであって、しかもイベントの対象がディレクトリーそのものではなく、ディレクトリー内の項目である場合のみです。IN_MOVED_FROM イベントとそれに対応する IN_MOVED_TO イベントが、どちらも監視対象の項目に関連するイベントの場合、この 2 つのイベントを関連付けるために cookie が使用されます。マスク・フィールドに返されるイベント・タイプは、カーネルによってフラグがセットされる場合があります。例えばディレクトリーに関するイベントの場合、カーネルによって IN_ISDIR フラグがセットされます。


監視可能なイベント

監視できるイベントは複数あります。そのうちの一部は、監視対象の項目にだけ適用されるイベントです (IN_DELETE_SELF など)。他のイベント (IN_ATTRIB や IN_OPEN など) については、監視対象の項目に適用されることもあれば、監視対象の項目がディレクトリーの場合には、ディレクトリーまたはディレクトリー内のファイルに適用されることもあります。

IN_ACCESS
監視対象の項目、または監視対象ディレクトリー内のファイルやサブディレクトリーへのアクセスが行われたことを示すイベント。例えば、開いているファイルの読み取りなどです。
IN_MODIFY
監視対象の項目、または監視対象ディレクトリー内のファイルやサブディレクトリーが変更されたことを示すイベント。例えば、開いているファイルの更新などです。
IN_ATTRIB
監視対象の項目、または監視対象ディレクトリー内のファイルやサブディレクトリーで、メタデータが変更されたことを示すイベント。例えば、タイムスタンプまたはアクセス権の変更などです。
IN_CLOSE_WRITE
書き込み用に開いていたファイルまたはディレクトリーが閉じられたことを示すイベント。
IN_CLOSE_NOWRITE
読み取り専用で開いていたファイルまたはディレクトリーが閉じられたことを示すイベント。
IN_CLOSE
上記の 2 つのイベント (IN_CLOSE_WRITE と IN_CLOSE_NOWRITE) の論理 OR となる便利なマスクです。
IN_OPEN
ファイルまたはディレクトリーが開かれたことを示すイベント。
IN_MOVED_FROM
監視対象の項目、または監視対象ディレクトリー内のファイルやサブディレクトリーが監視位置から移動されたことを示すイベント。このイベントには、IN_MOVED_FROM と IN_MOVED_TO を相互に関連付けられるようにする cookie も組み込まれます。
IN_MOVED_TO
ファイルまたはディレクトリーが監視位置に移動されたことを示すイベント。このイベントには、IN_MOVED_FROM の場合と同じく cookie が組み込まれます。ファイルまたはディレクトリーの名前が変更されただけの場合には、IN_MOVED_TO と IN_MOVED_FROM の両方のイベントが示されます。監視していない位置に移動、またはそこから移動された場合には、一方のイベントしか示されません。監視対象の項目を移動または名前変更した場合、監視は続行されます。以下の IN_MOVE_SELF を参照してください。
IN_MOVE
上記の 2 つの移動イベント (IN_MOVED_FROM と IN_MOVED_TO) の論理 OR となる便利なマスクです。
IN_CREATE
監視対象のディレクトリー内にサブディレクトリーまたはファイルが作成されたことを示すイベント。
IN_DELETE
監視対象のディレクトリー内のサブディレクトリーまたはファイルが削除されたことを示すイベント。
IN_DELETE_SELF
監視対象の項目自体が削除されたことを示すイベント。監視は終了し、IN_IGNORED イベントを受け取ります。
IN_MOVE_SELF
監視対象の項目自体が移動されたことを示すイベント。

inotify ヘッダー (/usr/include/sys/inotify.h) を見ると、イベント・フラグの他にも複数のフラグがあることがわかります。例えば、監視を追加するときに IN_ONESHOT フラグを設定すると、最初のイベンドだけを監視することができます。


単純な inotify アプリケーション

この記事のサンプル・アプリケーション (「ダウンロード」セクションを参照) は、上記の一般的なロジックに従ったものです。このアプリケーションではシグナル・ハンドラーを使用して ctrl-c (SIGINT) をキャッチし、フラグ (keep_running) をリセットすることによって、アプリケーションを終了します。実際の inotify 呼び出しは、ユーティリティー・ルーチンで行われます。またキューを作成することで、イベントと inotify オブジェクトを切り離し、後で処理できるようにしていることにも注目してください。実際のアプリケーションでは、イベントの処理に使用するスレッドとは別の (そして優先度の高い) スレッドでキューを作成したほうがよいと思います。このアプリケーションは、単に一般原則を説明するためのものです。アプリケーションが使用するイベントのリンク・リストはごく単純で、各キュー・エントリーはイベントそのものと、キュー内の次のイベントへのポインター用スペースで構成されます。

メイン・プログラム

リスト 2 に、シグナル・ハンドラーとメイン・ルーチンを記載します。この単純な例では、コマンドラインで渡されたファイルまたはディレクトリーごとに監視ができるように設定し、イベント・マスク IN_ALL_EVENTS を使用して、ファイル/ディレクトリーのそれぞれですべてのイベントを監視します。実際のアプリケーションで、ファイルおよびディレクトリーの作成イベントまたは削除イベントだけを追跡することにした場合には、オープン・イベントとクローズ・イベント、ならびに属性の変更をマスクから外します。ファイルまたはディレクトリーの名前変更や移動は追跡する必要がなければ、各種の移動イベントもマスクから外して構いません。詳細は、inotify の man ページを参照してください。

リスト 2. inotify-test.c のサンプル・メイン・ルーチン
/* Signal handler that simply resets a flag to cause termination */
void signal_handler (int signum)
{
  keep_running = 0;
}

int main (int argc, char **argv)
{
  /* This is the file descriptor for the inotify watch */
  int inotify_fd;

  keep_running = 1;

  /* Set a ctrl-c signal handler */
  if (signal (SIGINT, signal_handler) == SIG_IGN)
    {
      /* Reset to SIG_IGN (ignore) if that was the prior state */
      signal (SIGINT, SIG_IGN);
    }

  /* First we open the inotify dev entry */
  inotify_fd = open_inotify_fd ();
  if (inotify_fd > 0)
    {

      /* We will need a place to enqueue inotify events,
         this is needed because if you do not read events
         fast enough, you will miss them. This queue is 
         probably too small if you are monitoring something
         like a directory with a lot of files and the directory 
         is deleted.
       */
      queue_t q;
      q = queue_create (128);

      /* This is the watch descriptor returned for each item we are 
         watching. A real application might keep these for some use 
         in the application. This sample only makes sure that none of
         the watch descriptors is less than 0.
       */
      int wd;


      /* Watch all events (IN_ALL_EVENTS) for the directories and 
         files passed in as arguments.
         Read the article for why you might want to alter this for 
         more efficient inotify use in your app.      
       */
      int index;
      wd = 0;
      printf("\n");
      for (index = 1; (index < argc) && (wd >= 0); index++) 
	{
	  wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS);
	}

      if (wd > 0) 
	{
	  /* Wait for events and process them until a 
         termination condition is detected
 	  */
	  process_inotify_events (q, inotify_fd);
	}
      printf ("\nTerminating\n");

      /* Finish up by closing the fd, destroying the queue,
         and returning a proper code
       */
      close_inotify_fd (inotify_fd);
      queue_destroy (q);
    }
  return 0;
}

inotify_init を使用してファイル記述子を開く

リスト 3 に、サンプル・アプリケーションで inotify インスタンスを作成し、そのインスタンスのファイル記述子を取得するために使用している単純なユーティリティー関数を記載します。ファイル記述子は呼び出し側に返されます。エラーが発生した場合には、負の値が返されます。

リスト 3. inotify_init を使用する
/* Create an inotify instance and open a file descriptor
   to access it */
int open_inotify_fd ()
{
  int fd;

  watched_items = 0;
  fd = inotify_init ();

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

inotify_add_watch を使用して監視の対象を追加する

inotify インスタンスのファイル記述子を取得したら、監視の対象を 1 つ以上追加する必要があります。監視したい特定のイベントを設定するには、マスクを使用します。この例では IN_ALL_EVENTS マスクを使用して、すべての有効なイベントを監視します。

リスト 4. inotify_add_watch を使用する
int watch_dir (int fd, const char *dirname, unsigned long mask)
{
  int wd;
  wd = inotify_add_watch (fd, dirname, mask);
  if (wd < 0)
    {
      printf ("Cannot add watch for \"%s\" with event mask %lX", dirname,
	      mask);
      fflush (stdout);
      perror (" ");
    }
  else
    {
      watched_items++;
      printf ("Watching %s WD=%d\n", dirname, wd);
      printf ("Watching = %d items\n", watched_items); 
    }
  return wd;
}

イベント処理ループ

監視するための設定ができたところで、次に行うことはイベントの待機です。監視の設定がされたままで、シグナル・ハンドラーによって keep_running フラグがリセットされていない限り、ループ処理を行います。このループは何らかのイベントが発生するまで待機します。有効なイベントが発生すると、それをキューに入れ、その後キューを処理してから再び新しいイベントを待機します。実際のアプリケーションでは、あるスレッドでイベントをキューに送信し、別のスレッドでキューを処理することになるはずです。リスト 5 にサンプル・アプリケーションのループを記載します。

リスト 5. イベント処理ループ
int process_inotify_events (queue_t q, int fd)
{
  while (keep_running && (watched_items > 0))
    {
      if (event_check (fd) > 0)
	{
	  int r;
	  r = read_events (q, fd);
	  if (r < 0)
	    {
	      break;
	    }
	  else
	    {
	      handle_events (q);
	    }
	}
    }
  return 0;
  }

イベントを待機する

サンプル・アプリケーションでは、監視対象のイベントが発生するか、あるいはシグナルによる割込みが発生するまで無期限に待機します。このコードをリスト 6 に記載します。

リスト 6. イベントまたは割り込みの待機
int event_check (int fd)
{
  fd_set rfds;
  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);
  /* Wait until an event happens or we get interrupted 
     by a signal that we catch */
  return select (FD_SETSIZE, &rfds, NULL, NULL, NULL);
  }

イベントを読み込む

イベントが発生すると、大きなバッファーに収まるだけの数のイベントを読み込んでから、イベント・ハンドラーが処理できるようにイベントをキューに入れます。サンプル・コードは、16384 バイトのバッファーに収まりきらない数のイベントがある場合には対応していないため、バッファーの終わりに入る完全にはバッファーに収まりきらないイベントを処理できなければなりません。名前の長さが現行のように制限されていれば問題にならないはずですが、事前対策を講じた賢いプログラムを作成するのであれば、名前がバッファーに収まるかどうかをチェックします。

リスト 7. イベントを読み込んでキューに入れる
int read_events (queue_t q, int fd)
{
  char buffer[16384];
  size_t buffer_i;
  struct inotify_event *pevent;
  queue_entry_t event;
  ssize_t r;
  size_t event_size, q_event_size;
  int count = 0;

  r = read (fd, buffer, 16384);
  if (r <= 0)
    return r;
  buffer_i = 0;
  while (buffer_i < r)
    {
      /* Parse events and queue them. */
      pevent = (struct inotify_event *) &buffer[buffer_i];
      event_size =  offsetof (struct inotify_event, name) + pevent->len;
      q_event_size = offsetof (struct queue_entry, inot_ev.name) + 
                                  pevent->len;
      event = malloc (q_event_size);
      memmove (&(event->inot_ev), pevent, event_size);
      queue_enqueue (event, q);
      buffer_i += event_size;
      count++;
    }
  printf ("\n%d events queued\n", count);
  return count;
}

イベントを処理する

いよいよイベントを処理する段階に来ました。このアプリケーションではイベントを処理して、単に発生したイベントをレポートするだけです。名前がイベント構造体の中に含まれる場合には、それがファイルであるか、ディレクトリーであるかをレポートします。ファイルまたはディレクトリーが移動された場合には、移動イベントあるいは名前変更イベントを相互に関連付けられるように、cookie 情報を併せてレポートします。リスト 8 に抜粋したコードの一部に、いくつかのイベントの処理方法を記載します。完全なコードを入手するには、「ダウンロード」セクションを参照してください。

リスト 8. イベントを処理する
void handle_event (queue_entry_t event)
{
  /* If the event was associated with a filename, we will store it here */
  char *cur_event_filename = NULL;
  char *cur_event_file_or_dir = NULL;
  /* This is the watch descriptor the event occurred on */
  int cur_event_wd = event->inot_ev.wd;
  int cur_event_cookie = event->inot_ev.cookie;

  unsigned long flags;

  if (event->inot_ev.len)
    {
      cur_event_filename = event->inot_ev.name;
    }
  if ( event->inot_ev.mask & IN_ISDIR )
    {
      cur_event_file_or_dir = "Dir";
    }
  else 
    {
      cur_event_file_or_dir = "File";
    }
  flags = event->inot_ev.mask & 
    ~(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED );

  /* Perform event dependent handler routines */
  /* The mask is the magic that tells us what file operation occurred */
  switch (event->inot_ev.mask & 
	  (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED))
    {
      /* File was accessed */
    case IN_ACCESS:
      printf ("ACCESS: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was modified */
    case IN_MODIFY:
      printf ("MODIFY: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File changed attributes */
    case IN_ATTRIB:
      printf ("ATTRIB: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File open for writing was closed */
    case IN_CLOSE_WRITE:
      printf ("CLOSE_WRITE: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File open read-only was closed */
    case IN_CLOSE_NOWRITE:
      printf ("CLOSE_NOWRITE: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was opened */
    case IN_OPEN:
      printf ("OPEN: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was moved from X */
    case IN_MOVED_FROM:
      printf ("MOVED_FROM: %s \"%s\" on WD #%i. Cookie=%d\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd, 
              cur_event_cookie);
      break;
.
. (other cases)
.
      /* Watch was removed explicitly by inotify_rm_watch or automatically
         because file was deleted, or file system was unmounted.  */
    case IN_IGNORED:
      watched_items--;
      printf ("IGNORED: WD #%d\n", cur_event_wd);
      printf("Watching = %d items\n",watched_items); 
      break;

      /* Some unknown message received */
    default:
      printf ("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n",
	      event->inot_ev.mask, cur_event_filename, cur_event_wd);
      break;
    }
  /* If any flags were set other than IN_ISDIR, report the flags */
  if (flags & (~IN_ISDIR))
    {
      flags = event->inot_ev.mask;
      printf ("Flags=%lX\n", flags);
    }
    }

この単純な例は、inotify がどのように機能するか、そして監視できるイベントについて説明することを目的としています。監視対象のイベントとその処理方法は、それぞれのニーズによって左右されます。


使用例

このセクションでは inotify で監視できるイベントをいくつか説明するために、まず 2 つの階層からなる単純なディレクトリー構造を作成し、ディレクトリーに空のファイルを作成した後、サンプル・アプリケーションを実行します。ターミナル・セッションから inotify サンプル・プログラムを起動しますが、プログラムはバックグラウンドで実行されるため (& を使用)、プログラムからの出力はコマンドによってインターリーブされます。プログラムを実行するターミナル・ウィンドウとは別の、1 つまたは複数のターミナル・ウィンドウでコマンドを実行することも可能です。リスト 9 に、サンプルのディレクトリー構造と空のファイルの作成に続き、サンプル・プログラムを最初に起動したときの出力を記載します。

リスト 9. サンプル環境を作成する
ian@attic4:~/inotify-sample$ mkdir -p dir1/dir2
ian@attic4:~/inotify-sample$ touch dir1/dir2/file1
ian@attic4:~/inotify-sample$ ./inotify_test dir1/ dir1/dir2/ dir1/dir2/file1&
[2] 8733
ian@attic4:~/inotify-sample$ 
Watching dir1/ WD=1
Watching = 1 items
Watching dir1/dir2/ WD=2
Watching = 2 items
Watching dir1/dir2/file1 WD=3
Watching = 3 items

ian@attic4:~/inotify-sample$

リスト 10 に、dir2 の内容を表示した結果の出力を記載します。最初のイベントは dir1 に関してレポートされているもので、監視記述子 1 で監視中のディレクトリー内で何かが開かれたことを示しています。その何かとは、すなわち dir2 です。2 番目のエントリーは監視記述子 2 に関するイベントで、監視中の項目 (この例では dir2) が開かれたことを示しています。ディレクトリー・ツリー内で多数の項目を監視している場合には、このように重複する出力が頻繁に表示されるはずです。

リスト 10. dir2 の内容を表示する
ian@attic4:~/inotify-sample$ ls dir1/dir2
file1

4 events queued
OPEN: Dir "dir2" on WD #1
OPEN: Dir "(null)" on WD #2
CLOSE_NOWRITE: Dir "dir2" on WD #1
CLOSE_NOWRITE: Dir "(null)" on WD #2

リスト 11 では、file1 にテキストを追加します。この場合も、ファイルとこのファイルが含まれるディレクトリーで、オープン、クローズ、変更イベントが重複してレポートされていることに注意してください。さらに、すべてのイベントがまとめて一度に読み込まれるわけではないことにも注意が必要です。上記では、キューイングのルーチンが全部で 3 回呼び出されていて、イベントは毎回 2 つずつキューに入れられています。アプリケーションをもう一度実行して同じことを繰り返した場合、この特定の振る舞いが繰り返されることもあれば、別の振る舞いになることもあります。

リスト 11. file1 にテキストを追加する
ian@attic4:~/inotify-sample$ echo "Some text" >> dir1/dir2/file1

2 events queued
OPEN: File "file1" on WD #2
OPEN: File "(null)" on WD #3

2 events queued
MODIFY: File "file1" on WD #2
MODIFY: File "(null)" on WD #3

2 events queued
CLOSE_WRITE: File "file1" on WD #2
CLOSE_WRITE: File "(null)" on WD #3

リスト 12 では、file1 の属性を変更します。この場合もやはり、監視対象の項目とその項目が含まれるディレクトリーで出力が重複しています。

リスト 12. ファイルの属性を変更する
ian@attic4:~/inotify-sample$ chmod a+w dir1/dir2/file1

2 events queued
ATTRIB: File "file1" on WD #2
ATTRIB: File "(null)" on WD #3

今度は file1 をディレクトリーの上の階層に移動し、dir1 に配置します。その結果が、リスト 13 の出力です。今回は、重複するエントリーがありません。出力には、ディレクトリーのそれぞれ、そしてファイル自体に対する合計 3 つのエントリーがあります。cookie (569) によって、MOVED-FROM イベントと MOVED_TO イベントを関連付けられることに注目してください。

リスト 13. file1 を dir1 に移動する
ian@attic4:~/inotify-sample$ mv dir1/dir2/file1 dir1

3 events queued
MOVED_FROM: File "file1" on WD #2. Cookie=569
MOVED_TO: File "file1" on WD #1. Cookie=569
MOVE_SELF: File "(null)" on WD #3

次に、file1 から file2 へのハード・リンクを作成してみましょう。i ノードへのリンクの数が変わることから、file1 での ATTRIB イベントが示されるとともに、file2 での CREATE イベントも示されています。

リスト 14. ハード・リンクを作成する
ian@attic4:~/inotify-sample$ ln dir1/file1 dir1/file2

2 events queued
ATTRIB: File "(null)" on WD #3
CREATE: File "file2" on WD #1

次に、file1 をカレント・ディレクトリーに移動し、名前を file3 に変更してみます。カレント・ディレクトリーは監視の対象になっていないため、MOVED_FROM イベントは示されていますが、これに関連する MOVED_TO イベントは出力されていません。

リスト 15. 監視されていないディレクトリーに file1 を移動する
ian@attic4:~/inotify-sample$ mv dir1/file1 ./file3

2 events queued
MOVED_FROM: File "file1" on WD #1. Cookie=572
MOVE_SELF: File "(null)" on WD #3

この時点で dir2 は空になったので、このディレクトリーを削除します。すると、監視記述子 2 に対する IGNORED イベントが出力されることに注意してください。したがって、現在監視中の項目は 2 つだけとなりました。

リスト 16. dir2 を削除する
ian@attic4:~/inotify-sample$ rmdir dir1/dir2

3 events queued
DELETE: Dir "dir2" on WD #1
DELETE_SELF: File "(null)" on WD #2
IGNORED: WD #2
Watching = 2 items

次に file3 を削除します。しかし今回は IGNORED イベントが出力されません。このイベントが出力されない理由、そして (元は dir1/dir2/file1 だった) file3 の ATTRIB イベントが出力されている理由がわかりますか?

リスト 17. file3 を削除する
ian@attic4:~/inotify-sample$ rm file3

1 events queued
ATTRIB: File "(null)" on WD #3

file1 から file2 へのハード・リンクを作成したことを思い出してください。リスト 18 を見ると、監視を開始したときには file2 がなかったにも関わらず、監視記述子 3 では依然として file2 が監視されています。

リスト 18. 継続中の file2 の監視
ian@attic4:~/inotify-sample$ touch dir1/file2

6 events queued
OPEN: File "file2" on WD #1
OPEN: File "(null)" on WD #3
ATTRIB: File "file2" on WD #1
ATTRIB: File "(null)" on WD #3
CLOSE_WRITE: File "file2" on WD #1
CLOSE_WRITE: File "(null)" on WD #3

そこで、今度は dir1 を削除してイベントの連鎖を監視すると、監視する対象が 1 つもなくなったことから、最終的にはサンプル・プログラムが自動終了します。

リスト 19. dir1 を削除する
ian@attic4:~/inotify-sample$ rm -rf dir1

8 events queued
OPEN: Dir "(null)" on WD #1
ATTRIB: File "(null)" on WD #3
DELETE_SELF: File "(null)" on WD #3
IGNORED: WD #3
Watching = 1 items
DELETE: File "file2" on WD #1
CLOSE_NOWRITE: Dir "(null)" on WD #1
DELETE_SELF: File "(null)" on WD #1
IGNORED: WD #1
Watching = 0 items

Terminating

inotify に考えられる使用法

inotify はさまざまな目的で使用することができます。以下に、その例をいくつか紹介します。

パフォーマンス監視
アプリケーションがどのファイルを頻繁に開いているかを判別するために使用することができます。小さなファイルが繰り返し開いて閉じられていることがわかった場合には、メモリー内ファイルのバージョンを使用するか、他の何らかの方法でデータが共有されるようにアプリケーションを変更することを検討してください。
メタ情報
ファイルに関する追加情報として、例えばファイルが最初に作成された時刻、ファイルを最後に変更したユーザーの ID などをログに記録することができます。
セキュリティー
セキュリティー上の理由で、特定のファイルまたはディレクトリーに対するすべてのアクセスを監視することができます。

この記事のサンプル・コードはすべてのイベントを監視し、そのすべてをレポートしますが、実際には、必要に応じて一部の特定のイベントを確認するだけで済むはずです。また、監視対象の項目ごとに異なるイベントを監視することもできます。例えば、ファイルではオープンおよびクローズ・イベントを監視する一方、ディレクトリーでは作成または削除イベントだけを監視する必要がある場合などが、これに該当します。監視対象のイベントの数は、関心のあるできるだけ少数のイベントに絞り込むよう心掛けてください。


まとめ

inotify は、パフォーマンス監視やデバッグ、そして自動化などの分野に適用する場合、強力で極めて粒度の細かい Linux ファイルシステムの監視メカニズムとなります。この記事に付属のサンプル・コードを利用して、最小限のパフォーマンス・オーバーヘッドでリアルタイムにファイルシステムのイベントに応答したり、これらのイベントを記録したりする、独自のアプリケーションを作成してください。


ダウンロード

内容ファイル名サイズ
Sample code used in this articleinotify-sample.tgz4 KB

参考文献

学ぶために

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

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

  • My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

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, Open source
ArticleID=228554
ArticleTitle=inotify で Linux ファイルシステムのイベントを監視する
publish-date=04062010