Библиотека inotify — слежение за изменением файлов

      Комментарии к записи Библиотека inotify — слежение за изменением файлов отключены

Главная Форумы Программирование Программирование на С++ Библиотека inotify — слежение за изменением файлов

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 4 мес., 3 нед. назад.

  • Автор
    Сообщения
  • #3567

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

    Inotify – стандартная подсистема мониторинга обращений к файлам, включённая в ядро linux начиная с версии 2.6.13 – т.е. вполне давно. Несмотря на то, что функционал inotify реализован в ядре, его API требует соответствующей поддержки в libc (в glibc, конечно же, она имеется). Для доступа к inotify достаточно подключить один заголовочный файл:

    #include <sys/inotify.h>

    Функций inotify всего три – inotify_init(), inotify_add_watch() и inotify_rm_watch() (есть ещё inotify_init1() – то же что init, но с явными флагами открытия дескриптора) – остальные операции делаются посредством стандартных операций с файловыми дескрипторами – select, poll, read, close.
    inotify_init() создаёт новый объект слежения inotify и возвращает его файловый дескриптор; функция без параметров. Все операции inotify используют этот дескриптор. По завершении работы с inotify закрываем дескриптор обычным close() – который корректно удалит все активные точки слежения.
    inotify_add_watch() получает три параметра – дескриптор активного объекта inotify, путь до каталога, за которым хотим следить, и битовую маску флагов, и возвращает дескриптор объекта слежения. Функция выполняет внутренние проверки и исключает дубликаты – если добавить watch, за которым inotify уже следит – будет возвращён уже существующий дескриптор.
    inotify_rm_watch() получает дескриптор inotify и дескриптор watch, и удаляет его.
    Битовая маска для inotify_add_watch() – комбинация из типов изменений, на которые мы хотим получать уведомления. Наиболее интересными представляются IN_CLOSE_WRITE – оповещении о закрытии файла, который до этого был открыт для записи, IN_MODIFY – оповещении об изменении файла посредством вызова write(), и IN_ATTRIB – изменение метаданных файла. IN_MODIFY срабатывает при каждой записи – т.е. возможно несколько раз за сохранение файла – вполне вероятно, не наш случай. IN_ATTRIB сообщает об изменении прав доступа к файлу, времени последней модификации и т.п.. IN_CLOSE_WRITE, вероятно – самое полезное в контексте игр.
    Итак, устанавливаем точку слежения:

    int watchpoint_wd = inotify_add_watch(fd, path, IN_CLOSE_WRITE);

    Ok, inotify готов, точки слежения установлены (к слову – inotify не поддерживает рекурсивное слежение – придётся добавлять по точке слежения на каждый каталог). Узнать, что доступны новые оповещения, можно посредством штатного механизма опроса дескрипторов – select или poll, например:

    struct pollfd pfd;
    pfd.fd = fd;        // дескриптор inotify
    pfd.events = POLLIN;
    pfd.revents = 0;
    if(poll(&pfd, 1, 0) == 1) {
            // доступны новые оповещения

    Реальные данные получаются вызовом read() на дескриптор inotify; доступ к данным осуществляется посредством структуры struct inotify_event.

    struct inotify_event {
        int      wd;       /* Watch descriptor */
        uint32_t mask;     /* Mask of events */
        uint32_t cookie;   /* Unique cookie associating related events */
        uint32_t len;      /* Size of name field */
        char     name[];   /* Optional null-terminated name */
    };

    Тут:

    1. wd – дискриптор активной точки слежения, в которой произошло событие
    2. mask – битовая маска произошедшего события (тип события)
    3. len – длина inotify_event, в байтах
    4. name – имя файла, C-строка

    Тут я не буду рассматривать cookie – это значение служит для связи двух событий, например при перемещении файла будет сгенерированно два события – IN_MOVE_FROM и IN_MOVE_TO.
    Поскольку name[] объявлен как безразмерный массив, его длина может быть произвольной и не учитывается в sizeof(struct inotify_event). Чтобы узнать реальную длину структуры – нужно воспользоваться значением параметра len.

    За один read можно получить несколько событий – лишь бы для них хватало памяти:

    #define BUF_LEN ((sizeof(struct inotify_event) + 16) * 8)
    char buf[BUF_LEN];

    Резервирует память условно для 8 событий – реально может быть меньше, в зависимости от длины имени файла. Читаем данные:

    read(fd, buf, sizeof(buf));

    В buf последовательно расположены struct inotify_event, по которым можно просто последовательно пробежать, каждый раз смещаясь на соответствующий len.
    Какой нужен размер buf? На самом деле, много не нужно. Ничего страшного не случится, если событий накопится много и они не влезут в буфер за один readinotify_event всегда копируется только полностью, или не копируется вовсе. Если событие не влазит в buf – оно будет прочитано при следующем вызове read (на блокирующих дискрипторах, как в нашем случае, нужно заново вызвать poll/select, чтобы не “подвесить” основной поток ожиданием данных). Важно лишь, чтобы хотя бы одно событие входило в buf – в противном случае read вернёт -1.

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

Для ответа в этой теме необходимо авторизоваться.