Отслеживание изменений в директории с помощью Inotify

4c24a6fa84030acedbd9d0a0aead20e7

Столкнулся с задачей, где необходимо было отслеживать в ОС Linux изменение файла в директории на чистом С++. Так как чистый С++, QtCreator с его QFileSystemWatcher сразу отпадал, из-за того что необходимо было подключать QObject. В итоге решил пользоваться линуксовой функцией Inotify.
Inotify позволяет через файловый дескриптор наблюдать за директорией или файлом, отслеживая их события. Все события ввода-вывода ссылаются на открытый файл с использованием файлового дескриптора. Файловый дескриптор представляет собой целое число типа int.

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

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


В API Inotify используются следующие системные вызовы:

inotify_init () инициализирует новый экземпляр inotify и возвращает файловый дескриптор. На данный момент используется более новый системный вызов inotify_init1(int flags). Если используется флаг 0 или флаги вообще не указаны, то 
inotify_init1(int flags) будет работать как inotify_init (). Так же используются другие флаги:
IN_NONBLOCK — устанавливает файловый дескриптор в неблокирующий режим.
IN_CLOEXEC — устанавливает флаг закрытия при выполнении.

Системный вызов inotify_and_watch (int fd, const char *pathname, uint32_t mask) добавляет новый элемент в список наблюдения для объекта inotify, ссылка на который осуществляется с помощью файлового дескриптора. Аргумент mask добавляет тип событий, которые должны мониториться файловым дескриптором. Рассмотрим флаги событий, за которыми можно наблюдать:

IN_ACCESS — Файл был прочитан (read ())
IN_ATTRIB — Метаданные файла изменены
IN_CLOSE_WRITE — Файл был открыт для записи, а потом закрыт
IN_CLOSE_NOWRITE — Файл был открыт для записи, а потом закрыт
IN_CREATE — Файл/каталог создан внутри наблюдаемого каталога
IN_DELETE — Файл/каталог удален из наблюдаемого каталога
IN_DELETE_SELF — Наблюдаемый файл/каталог был удален
IN_MODIFY — Файл был изменен
IN_MOVE_SELF — Наблюдаемый файл/каталог был перемещен
IN_MOVED_FROM — Наблюдаемый файл/каталог был перемещен из наблюдаемого каталога
IN_MOVED_TO — Наблюдаемый файл/каталог был перемещен в наблюдаемый каталог
IN_OPEN — Файл был открыт
IN_ALL_EVENTS — Сокращение для всех вышеперечисленных событий ввода
IN_MOVE — Сокращение для IN_MOVED_FROM | IN_MOVED_TO
IN_CLOSE — Сокращение для IN CLOSE WRITE | IN CLOSE NOWRITE
IN_DONT_FОLLOW — He разыменовывать символьную ссылку (начиная с Linux 2.6.15)
IN_MASK_ADD — Добавить события в маску текущего элемента списка наблюдения для файла pathname
IN_ONESHOT — Наблюдать для файла pathname только одно событие
IN_ONLYDIR — Ошибка, если pathname не каталог (начиная с Linux 2.6.15)

Для удаления события из списка наблюдаемых событий используется функция inotify_rm_watch ().

Функция read () читает данные из буфера об одном или нескольких событиях.

close () — закрывает файловый дескриптор и удаляет все связанные с ним наблюдения. Когда все файловые дескрипторы экземпляра inotify закрываются, все системные ресурсы освобождаются, чтобы ядро могло их повторно использовать.

Итак рассмотрим листинг программы. В программе будем отслеживать изменения, происходящие в директории по абсолютному пути «home/user/dev». Отслеживаемые события это закрытие файла и его изменение.

#define MAX_EVENTS 1024 /*Максимальное кличество событий для обработки за один раз*/
#define LEN_NAME 16 /*Будем считать, что длина имени файла не превышает 16 символов*/
#define EVENT_SIZE  ( sizeof (struct inotify_event) ) /*размер структуры события*/
#define BUF_LEN     ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME )) /*буфер для хранения данных о событиях*/

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

     /* Инициализация Inotify*/
    fd = inotify_init1(0);
    if ( fd < 0 ) {
        perror( "Couldn't initialize inotify");
    }
    const char path[] = "/home/user/dev";
     /* добавим наблюдение для директории*/
    wd = inotify_add_watch(fd, path, IN_CLOSE | IN_MODIFY);
    if (wd == -1)
    {
        printf("Couldn't add watch to %s\n",path);
    }
    else
    {
        printf("Watching:: %s\n",path);
    }

    while(1)
    {
        i = 0;
  	/* высчитываем размер файлового дескриптора*/
        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_CLOSE) {
                if (event->mask & IN_ISDIR)
                    printf( "The directory %s was closed.\n", event->name );
                else
                    printf( "The file %s was closed with WD %d\n", event->name, event->wd );
            }
            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 with WD %d\n", event->name, event->wd );
            }
                i += EVENT_SIZE + event->len;
            }
        }
    }
    /* Освобождение ресурсов*/
    inotify_rm_watch( fd, wd );
    close( fd );
    return 0;
}

© Habrahabr.ru