Рекурсивное наблюдение за файловой системой на примере librnotify
LVEE 2015
Предположим, стоит задача синхронизировать данные, хранящиеся в некоторых директориях, доступ к которым не ограничен. Очевидно, что мы будем вынуждены периодически рекурсивно просматривать директорию, чтобы находить различия или подписаться на файловые нотификации, которые поставляются такими подсистемами ядра Linux, как inotify и fanotify.
fanotify имеет серьезный недостаток: не поддерживаются нотификации удаления и перемещения. Поэтому применение его в синхронизации данных усложняется.
Рассмотрим inotify. Основных недостатков в нем, пожалуй, три: первый — это то, что нотификация сопровождается только лишь дескриптором, и ничего не известно о пути сработавшего элемента ФС, если об этом не позаботиться заранее.
Второй "—- это ограничения (лимиты)
в ядре на количество запущенных нотификаторов и длину очереди нотификаций. Лимиты можно и нужно менять, и скорее ваше приложение упрется в ограничения, связанные с производительностью, чем в лимит.
И последний недостаток — это то, что совсем не поддерживается рекурсивная нотификация папок. То есть когда, допустим, мы перемещаем папку внутрь нотифицируемой папки, пока интерфейс просигнализирует о новой папке и мы включим эту новую папку в нотификации, несколько файлов “проскочат” мимо, и мы получим рассинхронизацию данных; это случается практически постоянно.
Одним из известных решений по устранению этой проблемы является проект lsync. Действительно, он полностью решает две проблемы: мапинг путей и рекурсивные нотификации. Но он громоздок и непроизводителен в качестве подсистемы.
В одном из наших проектов мы использовали lsync как сервер нотификаций, но если нужно синхронизировать десяток разных корневых папок с сотнями тысяч файлов и папок внутри, решение с lsinc
оказывается дорогим в плане потребления системных ресурсов.
В итоге мы заменили lsync на библиотеку librnotify.
Подробно опишем интерфейс этой библиотеки. Он прост и содержит только три вызова:
Notify* initNotify(const char* path, const uint32_t mask, const char* exclude);
int waitNotify(Notify* ntf, char** const path, uint32_t* mask, const int timeout, uint32_t* cookie);
void freeNotify(Notify* ntf);
Объект Notify является контейнером состояния нотификатора, и с нашей точки зрения нам не интересен. initNotify подписывает некую папку по пути path на список нотификаций, задаваемый в mask (список тот же, что и в man inotify). Естественно, файл sys/inotify.h также должен присутствовать. exclude — это регулярное выражение, которое задает маску для элементов ФС, которые мы не хотим нотифицировать — это удобно, если, например, мы не хотим нотифицировать .git или просто .*.
Вызов waitNotify ждет нотификации timeout секунд (или микросекунд), а при значении 0 ждет вечно. Он возвращает path и битовое поле (mask), где указанно, что именно случилось. Cookie "—- это возвращаемое число, которое позволяет связать нотификации перемещения; оно будет одинаковым для пар операций MOVE_FROM и MOVE_TO.
Наконец, freeNotify удаляет объект нотификатора.
Библиотека librnotify https://github.com/zmushko/librnotify
Abstract licensed under Creative Commons Attribution-ShareAlike 3.0 license
Back