Отслеживание событий файловой системы Linux с помощью inotify

Эффектный и эффективный инструмент отслеживания событий файловой системы, встроенный в ядре 2.6

Подсистема ядра inotify предоставляет средства для эффективного, хорошо настраиваемого, асинхронного мониторинга событий файловых систем Linux®. Ее можно использовать для отслеживания событий в пользовательском пространстве в целях обеспечения безопасности, мониторинга производительности и т.д.

Связаться с Иэном

Иэн - один из наших самых популярных и плодотворных авторов. Ознакомьтесь со всеми статьями Иэна на developerWorks. Взгляните на профиль Иэна в My developerWorks и общайтесь в этом сообществе с ним, с другими авторами и нашими читателями.

Я в долгу перед Эли Доу из IBM, написавшим предыдущую версию этой статьи еще до окончательной интеграции inotify в ядро Linux. В частности, код, представленный в разделе Загрузки, в очень большой степени основан на изначальном коде примеров из статьи Эли.

Знакомимся с inotify

Мониторинг событий файловой системы важен для многих типов программ - от файловых менеджеров до инструментов обеспечения безопасности системы. В ядре Linux, начиная с версии 2.6.13, имеется подсистема inotify, которая позволяет программам мониторинга открывать один файловый дескриптор и наблюдать через него за одним или несколькими файлами или директориями, отслеживая для них указанный набор событий, таких как открытие, закрытие, перемещение/переименование, удаление, создание или изменение атрибутов. В последних версиях ядра функциональность подсистемы была расширена, поэтому проверьте версию ядра своей системы, прежде чем полагаться на новые возможности.

В этой статье мы узнаем, как на основе функций inotify сделать простое приложение мониторинга. Прежде чем двигаться дальше, загрузите исходный код нашего примера и скомпилируйте его на своей системе.


Немного истории

Предшественницей inotify была подсистема dnotify. К сожалению, в dnotify был ряд досадных ограничений, заставлявших ее пользователей ждать чего-либо лучшего. Вот некоторые преимущества inotify:

  • В inotify используется один файловый дескриптор, тогда как dnotify открывает по одному файловому дескриптору для каждой директории, за изменениями в которой мы хотим наблюдать. Это может быть весьма затратно при одновременном наблюдении за многими директориями и даже может привести к достижению ограничения на количество файловых дескрипторов, открываемых процессом.
  • Используемый в inotify файловый дескриптор получается посредством системного вызова и не связан с каким-либо устройством или файлом. В случае dnotify файловый дескриптор привязан к директории. Это не позволяет демонтировать устройство, на котором она расположена - типичная проблема при работе со сменными носителями. В случае inotify при демонтировании файловой системы расположенные на ней наблюдаемый файл или директория генерируют специальное событие, после чего наблюдение автоматически удаляется.
  • Inotify может наблюдать как за файлами, так и за директориями, тогда как Dnotify - только за директориями. Поэтому для того, чтобы узнать, что произошло с содержимым директории, разработчикам приходилось хранить структуру stat или эквивалентную ей структуру данных, содержащую информацию о находящихся в директории файлах, а затем при наступлении события сравнивать ее с текущим состоянием
  • Как мы уже отмечали выше, в inotify используется файловый дескриптор. Это предоставляет программистам возможность использовать для наблюдения за событиями стандартные функции select или poll, позволяющие организовать эффективный параллельный ввод/вывод или интегрироваться с основным циклом событий (mainloop) библиотеки Glib. В dnotify же используются сигналы, работа с которыми многим разработчикам кажется более сложной и совсем не элегантной. Начиная с версии ядра 2.6.25 в inotify также были добавлены уведомления на основе сигналов.

API, предоставляемый inotify

Inotify предоставляет простой API, использующий минимальное количество файловых дескрипторов и позволяющий очень тонко настраивать отслеживание событий. Взаимодействие с inotify осуществляется посредством системных вызовов. В интерфейсе имеются следующие функции:

inotify_init
- системный вызов, который создает экземпляр inotify и возвращает файловый дескриптор, указывающий на этот экземпляр.
inotify_init1
- похожа на inotify_init, но предоставляет дополнительные флаги. Если флаги не указаны, работает так же, как inotify_init.
inotify_add_watch
- добавляет наблюдение для файла или директории и указывает, за какими событиями следует наблюдать. Имеются флаги, которые определяют, нужно ли добавлять события к существующему наблюдению, осуществлять ли наблюдение только в случае, если наблюдаемый объект является директорией, нужно ли следовать по символическим ссылкам и является ли наблюдение "одноразовым", которое следует остановить при возникновении первого события.
inotify_rm_watch
- удаляет наблюдение из списка наблюдений.
read
- читает из буфера данные об одном или нескольких событиях.
close
- закрывает файловый дескриптор и удаляет все связанные с ним наблюдения. Когда все файловые дескрипторы экземпляра inotify закрываются, все системные ресурсы освобождаются, чтобы ядро могло их повторно использовать.

Итак, работа типичной программы мониторинга организована следующим образом:

  1. С помощью inotify_init открываем файловый дескриптор
  2. Добавляем одно или несколько наблюдений
  3. Ожидаем событий
  4. Обрабатываем события, после чего снова начинаем ждать
  5. При отсутствии активных наблюдений или при получении определенного сигнала файловый дескриптор закрывается, выполняется очистка и программа завершает работу.

В следующем разделе мы узнаем, за какими событиями можно наблюдать, и увидим на примере, как они работают. И, наконец, мы увидим, как работает мониторинг событий.


Уведомления

Когда приложение получает уведомление, в его буфер попадает одно или несколько событий. Как показано в листинге 1, события хранятся в структурах переменной длины. Данные могут заполнить весь буфер, поэтому следует быть готовым к тому, что в конце буфера может находиться неполная структура события.

Листинг 1. Структура события в inotify
struct inotify_event
{
  int wd;               /* Дескриптор наблюдения.  */
  uint32_t mask;        /* Маска наблюдения.  */
  uint32_t cookie;      /* Cookie-элемент для синхронизации двух событий.  */
  uint32_t len;         /* Длина имени (в том числе и нулевая).  */
  char name __flexarr;  /* Имя.  */
  };

Обратите внимание, что поле с именем имеется только в случае, если наблюдаемый элемент является директорией, а событие связано с элементом, находящимся в директории, а не с самой директорией. Поле cookie используется для связывания событий IN_MOVED_FROM и IN_MOVED_TO, если они оба относятся к наблюдаемым элементам. Тип события возвращается в поле маски наряду с флагами, которые может выставлять ядро. Например, если событие связано с директорией, ядро выставляет флаг IN_ISDIR.


События, за которыми можно наблюдать

Можно наблюдать за различными типами событий. Некоторые, например, событие IN_DELETE_SELF, применимы только для самого наблюдаемого элемента, тогда как другие, например IN_ATTRIB или IN_OPEN, применимы как для наблюдаемого элемента, так и, в случае директории, для файлов внутри директории.

IN_ACCESS
Был осуществлен доступ к наблюдаемому объекту или файлу внутри наблюдаемой директории. Например, был прочитан открытый файл.
IN_MODIFY
Был изменен наблюдаемый объект или файл внутри наблюдаемой директории. Например, был обновлен открытый файл.
IN_ATTRIB
Были изменены метаданные наблюдаемого объекта или файла внутри наблюдаемой директории. Например, были изменены временные метки или права доступа.
IN_CLOSE_WRITE
Были закрыты файл или директория, открытые ранее на запись.
IN_CLOSE_NOWRITE
Были закрыты файл или директория, открытые ранее только для чтения.
IN_CLOSE
Маска, представляющая собой логическое ИЛИ двух предыдущих событий закрытия (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE).
IN_OPEN
Были открыты файл или директория.
IN_MOVED_FROM
Наблюдаемый объект или элемент в наблюдаемой директории был перемещен из места наблюдения. Событие включает в себя cookie, по которому его можно сопоставить с событием IN_MOVED_TO.
IN_MOVED_TO
Файл или директория были перемещены в наблюдаемую директорию. Событие включает в себя cookie, как и событие IN_MOVED_FROM. Если файл или директория просто переименовать, то произойдут оба события. Если объект перемещается в/из директории, за которой не установлено наблюдение, мы увидим только одно событие. При перемещении или переименовании объекта наблюдение за ним продолжается. См. также событие IN_MOVE-SELF ниже.
IN_MOVE
Маска, представляющая собой логическое ИЛИ для двух предыдущих событий перемещения (IN_MOVED_FROM | IN_MOVED_TO).
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 показаны обработчик сигнала и функция main. В этом простом примере мы устанавливаем наблюдение за каждым файлом и директорией, переданными в аргументах командной строки. Для каждого объекта наблюдения мы, используя маску IN_ALL_EVENTS, будем отслеживать все события. В настоящем приложении мы можем отслеживать только создание или удаление файлов и директорий, для чего с помощью маски можно выключить события открытия, закрытия и изменения атрибутов. Если вам неинтересны переименования и перемещения файлов или директорий, также в маске можно выключить различные события перемещения. С более подробной информацией на эту тему можно ознакомиться на man-странице inotify.

Листинг 2. Функция main из inotify-test.c
/* Обработчик сигнала, в котором мы просто сбрасываем значение флага, 
   приводящего к остановке программы */
void signal_handler (int signum)
{
  keep_running = 0;
}

int main (int argc, char **argv)
{
  /* Это файловый дескриптор наблюдения  inotify */
  int inotify_fd;

  keep_running = 1;

  /* Задаем обработчик сигнала ctrl-c */
  if (signal (SIGINT, signal_handler) == SIG_IGN)
    {
      /* Сбрасываем сигнал в SIG_IGN (игнорировать), 
если это было предыдущим состоянием */
      signal (SIGINT, SIG_IGN);
    }

  /* Сначала мы создаем экземпляр inotify */
  inotify_fd = open_inotify_fd ();
  if (inotify_fd > 0)
    {

      /* Нам нужно место для помещения в очередь событий
         inotify, так как если мы не будем достаточно быстро
         считывать события, мы будем их терять. Может случиться,
         что данная очередь окажется слишком мала, например, 
         при удалении наблюдаемой директории,
         содержащей большое количество файлов.

       */
      queue_t q;
      q = queue_create (128);

      /* Это файловый дескриптор наблюдения, возвращаемый для каждого 
         наблюдаемого элемента. В настоящем приложении их можно хранить
         и в дальнейшем каким-либо образом использовать. В данном 
         примере мы просто проверяем, что ни один из дескрипторов 
         наблюдения не меньше нуля.
       */
      int wd;


      /* Мы будем отслеживать все события (IN_ALL_EVENTS) для 
         файлов и директорий, переданных при запуске в 
         аргументах командной строки. Далее в статье мы расскажем,
         почему для более эффективного использования inotify 
         в приложении это значение стоит изменить.
       */
      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) 
	{
	  /* Ожидаем событий и обрабатываем их до тех пор, пока мы 
         не обнаружим условия завершения
 	  */
	  process_inotify_events (q, inotify_fd);
	}
      printf ("\nTerminating\n");

      /* Завершаем работу: закрываем файловый дескриптор, разрушаем
         очередь и возвращаем корректный код
       */
      close_inotify_fd (inotify_fd);
      queue_destroy (q);
    }
  return 0;
}

Открываем файловый дескриптор с помощью inotify_init

В листинге 3 показана простая функция для создания экземпляра inotify и получения для него файлового дескриптора. Файловый дескриптор возвращается вызываемой стороне. При возникновении ошибки возвращаемое значение будет отрицательным.

Листинг 3. Используем inotify_init
/* Функция создает экземпляр inotify и открывает файловый дескриптор
   для доступа к нему */
int open_inotify_fd ()
{
  int fd;

  watched_items = 0;
  fd = inotify_init ();

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

Добавляем наблюдение с помощью inotify_add_watch

Мы получили файловый дескриптор экземпляра inotify. Теперь давайте добавим к нему одно или несколько наблюдений. Чтобы задать набор событий, которые мы хотим отслеживать, используется маска. В нашем примере мы используем маску 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);
  /* Ждем наступления события или прерывания по отлавливаемому нами 
     сигналу */
  return select (FD_SETSIZE, &rfds, NULL, NULL, NULL);
  }

Считываем события

При наступлении события мы считываем столько событий, сколько помещается в буфер, и помещаем их в очередь для последующей обработки. В нашем коде мы не обрабатываем случай, когда все события не могут поместиться в наш 16 384-байтный буфер. Нужно уметь обрабатывать ситуацию, когда в конце буфера находится неполное событие. Текущие ограничения на длину имени не должны быть проблемой, однако хорошим защитным механизмом была бы проверка того, что имена не могут переполнить буфер.

Листинг 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)
    {
      /* Разбираем события и помещаем их в очередь. */
      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)
{
  /* Если событие было связано с именем файла, мы его будем хранить 
     здесь */
  char *cur_event_filename = NULL;
  char *cur_event_file_or_dir = NULL;
  /* Это дескриптор наблюдения, на котором произошло событие */
  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 );

  /* Выполняем зависящие от события действия обработчика */
  /* Маска сообщает нам, какая файловая операция произошла */
  switch (event->inot_ev.mask & 
	  (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED))
    {
      /* Был произведен доступ к файлу */
    case IN_ACCESS:
      printf ("ACCESS: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Файл был изменен */
    case IN_MODIFY:
      printf ("MODIFY: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Были изменены атрибуты файла */
    case IN_ATTRIB:
      printf ("ATTRIB: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Был закрыт файл, открытый для записи */
    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;

      /* Был закрыт файл, открытый только для чтения */
    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;

      /* Файл был открыт */
    case IN_OPEN:
      printf ("OPEN: %s \"%s\" on WD #%i\n",
	      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Файл был перемещен из 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)
.
      /* Наблюдение было удалено либо явно вызовом inotify_rm_watch, 
         либо автоматически в связи с удалением файла или 
         демонтированием файловой системы.  */
    case IN_IGNORED:
      watched_items--;
      printf ("IGNORED: WD #%d\n", cur_event_wd);
      printf("Watching = %d items\n",watched_items); 
      break;

      /* Получено неизвестное сообщение */
    default:
      printf ("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n",
	      event->inot_ev.mask, cur_event_filename, cur_event_wd);
      break;
    }
  /* Если были заданы какие-либо флаги, отличные от IN_ISDIR, 
     мы о них сообщаем */
  if (flags & (~IN_ISDIR))
    {
      flags = event->inot_ev.mask;
      printf ("Flags=%lX\n", flags);
    }
    }

В этом простом примере мы показали, как работает inotify и какие события можно отслеживать с его помощью. Пусть ваши собственные потребности будут вам диктовать, за какими событиями наблюдать и как их обрабатывать.


Использование примера

В этом разделе мы создадим простую двухуровневую структуру директорий, содержащую файл, а затем запустим наше приложение, чтобы проиллюстрировать некоторые события, которые можно отслеживать с помощью inotify. Мы запустим нашу программу, работающую с inotify, в терминале в фоновом режиме (с помощью &), чтобы видеть вывод программы одновременно с нашими командами. Также эту программу можно запустить в одном окне терминала, а выполнять команды в другом или других окнах. В листинге 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. Сначала мы видим событие, говорящее о том, что что-то было открыто в директории, наблюдаемой на дескрипторе 1 (это что-то - директория dir2). Вторая запись относится к дескриптору наблюдения 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 некоторый текст. Обратите внимание, что мы опять видим уведомление о событиях открытия, закрытия и модификации в двух экземплярах: для файла и содержащей его директории. Также обратите внимание, что не все события были прочитаны за один раз. Функция помещения событий в очередь вызывалась три раза, и каждый раз она возвращала по два события. Если перезапустить приложение и выполнить данные действия снова, то результат может оказаться и другим.

Листинг 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. В этот раз у нас нет дублирования сообщений. Мы видим три сообщения - по одному для каждой директории и одно для самого файла. Обратите внимание, что 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

Теперь давайте создадим жесткую ссылку с именем file2, указывающую на файл file1. Так как количество ссылок, указывающих на индексный дескриптор изменяется, мы видим событие ATTRIB для файла file1. Также мы видим событие CREATE для файла file2.

Листинг 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_TO, соответствующего событию MOVED_FROM.

Листинг 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 пуста, поэтому давайте удалим ее. Обратите внимание, что мы получаем событие 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. Почему? И почему мы видим событие ATTRIB для файла file 3 (который изначально находился по адресу dir1/dir2/file1)?

Листинг 16. Удаляем файл file3
ian@attic4:~/inotify-sample$ rm file3

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

Вспомните, что мы создали жесткую ссылку file2, указывающую на файл file1. В листинге 17 показано, что мы наблюдаем за файлом file2 с помощью дескриптора наблюдения 3, несмотря на то, что файла file2 не существовало на момент запуска программы!

Листинг 17. Мы наблюдаем за файлом 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 и рассмотрим каскад событий, которые будут сгенерированы при завершении программы (программа завершается, так как у нее больше нет объектов наблюдения).

Листинг 18. Удаляем директорию 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 можно использовать в различных целях. Вот некоторые варианты:

Отслеживание производительности
С помощью inotify можно определить, какие файлы приложения открывают чаще всего. Если будет видно, что какой-нибудь маленький файл постоянно открывается и закрывается, возможно, стоит подумать о том, чтобы хранить данные в памяти или организовать в приложении совместный доступ к данным иным способом.
Метаинформация
Можно журналировать дополнительную информацию о файлах, например изначальное время создания или идентификатор пользователя, который последним изменял файл.
Безопасность
В целях безопасности можно отслеживать все операции с каким-либо файлом или директорией.

В нашем примере приложения мы просто отслеживаем все события и сообщаем о них. На практике вы, возможно, захотите наблюдать только за определенным набором сообщений, отвечающих вашим потребностям. Также для разных объектов наблюдения можно отслеживать различные события. Например, для файлов можно отслеживать события открытия и закрытия, а для директорий - только события создания и удаления. Везде, где только возможно, следует ограничивать наблюдения минимальным набором интересующих вас событий.


Заключение

inotify - мощный, хорошо настраиваемый механизм мониторинга файловых систем Linux. Его можно использовать в таких целях, как мониторинг производительности, отладка и автоматизация. Взяв за основу представленный в данной статье пример кода, вы можете начать писать собственные приложения, реагирующие на события файловой системы или регистрирующие их в реальном времени и с минимальным влиянием на производительность.


Загрузка

ОписаниеИмяРазмер
Пример кода, используемый в данной статьеinotify-sample.tgz4 КБ

Ресурсы

Научиться

  • Monitor Linux file system events with inotify (developerWorks, апрель 2010 г.): ознакомьтесь с оригиналом статьи.(EN)
  • "Event Management Best Practices" (IBM Redbook, июнь 2004 г.) - в этом руководстве широко и глубоко раскрывается тема управления событиями с акцентами на фильтрации событий, обнаружении дубликатов, коррелировании, уведомлении, эскалации, синхронизации событий, интеграции с системами технической поддержки (trouble-ticket), режимах сопровождения и автоматизации.(EN)
  • В статье "Use autonomic computing for problem determination" (developerWorks, июнь 2004 г.) описывается, как можно использовать автономную систему для отслеживания событий, диагностики ошибок ИТ-системы и выполнения корректирующих действий.(EN)
  • Следите за техническими событиями и Web-трансляциями developerWorks, посвященными разнообразным продуктам IBM и различным темам ИТ-индустрии. (EN)
  • Посетите бесплатный брифинг developerWorks Live!, чтобы быстро научиться работать с продуктами и инструментами IBM, а также познакомиться с новыми трендами ИТ-индустрии. (EN)
  • Смотрите доступные по требованию демонстрации developerWorks на различные темы, связанные с продуктами IBM: от демонстраций по установке и настройке продуктов для новичков до рассказов о продвинутой функциональности, интересной опытным разработчикам. (EN)

Получить продукты и технологии

  • Работайте с ознакомительными версиями продуктов IBM наиболее удобным для вас способом: загрузите локальную версию, поработайте с продуктом через Интернет, используйте его в облачной среде или поработайте несколько часов в "песочнице" SOA Sandbox, чтобы узнать, как эффективно реализовать сервис-ориентированную архитектуру.(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=629976
ArticleTitle=Отслеживание событий файловой системы Linux с помощью inotify
publish-date=03012011