Отслеживание изменений в директории с помощью Inotify
Столкнулся с задачей, где необходимо было отслеживать в ОС Linux изменение файла в директории на чистом С++. Так как чистый С++, QtCreator с его QFileSystemWatcher сразу отпадал, из-за того что необходимо было подключать QObject. В итоге решил пользоваться линуксовой функцией Inotify.
Inotify позволяет через файловый дескриптор наблюдать за директорией или файлом, отслеживая их события. Все события ввода-вывода ссылаются на открытый файл с использованием файлового дескриптора. Файловый дескриптор представляет собой целое число типа int.
Итак, работа типичной программы мониторинга организована следующим образом:
- С помощью inotify_init () открываем файловый дескриптор
- Добавляем одно или несколько событий для наблюдений
- Ожидаем добавленное событие
- Обрабатываем события, после чего снова начинаем ждать в бесконечном цикле
- При отсутствии активных наблюдений или при получении определенного сигнала файловый дескриптор закрывается, выполняется очистка и программа завершает работу.
В 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;
}