- Слежение за изменениями в директории: как это делается в разных ОС
- Mac OS X, FSEvents
- Linux, inotify
- FreeBSD, Mac OS X, kqueue
- Отслеживание изменений в директории с помощью Inotify
- Кунг-фу стиля Linux: наблюдение за файловой системой
- Более симпатичное решение задачи
- Новый cron
- Настройка incron с использованием графического интерфейса
- Итоги
Слежение за изменениями в директории: как это делается в разных ОС
Для операционной системы Windows есть замечательная функция ReadDirectoryChangesW, которая возвращает набор изменений для директории, в том числе содержит флаг для работы рекурсивно (bWatchSubtree). Таким образом, реализация слежения за изменениями в директории не представляет особого труда и в том же dklab_realsync реализация занимает 80 строк кода или 3.5 Кб. Интересно, что в Windows эти события поддерживаются даже через SMB!
Тем не менее, существуют определенные подводные камни:
- конечный размер буфера изменений, после которого очередь событий переполнится и эти события будут потеряны
- согласно документации к watchdog package, событие перемещения посылается раньше, чем изменения становятся видны в ФС
- размер буфера ограничен в 64 Кб для сетевой ФС
Вывод: Функция ReadDirectoryChangesW позволяет легко узнавать обо всех событиях в файлах, но, очередь событий может переполниться и тогда нужно будет выполнять полное сканирование ФС. Также, возможна доставка событий до того, как они станут актуальны.
Mac OS X, FSEvents
В Mac OS X также есть удобный и простой API для слежения за изменениями в файловой системе под названием FSEvents. С использованием этого API простейшая реализация демона составляет 50 строк кода или 1.8 кб. Очередь не может переполниться (!), но полное сканирование все же может потребоваться, если демон fseventsd «упадет». Стоит отметить, что этот API до версии 10.7 не предоставляет изменения по файлам, он сообщает только директории, в которых что-то изменилось. Поскольку события никуда не деваются и пишутся в лог (FSEvents service stores events in a persistent, per-volume database), детализация с точностью для директории позволяет сэкономить место на диске.
Вывод: FSEvents API для Mac OS X является самым необычным из всех подобных API. Очередь не переполняется и даже имеется возможность получить события из прошлого. Тем не менее, детализация событий дается с точностью до директории (до версии 10.7), что означает меньшую эффективность демона для синхронизации файлов.
Linux, inotify
В linux vanilla kernel существует один способ слежения за изменениями в директории — это inotify. Для этого API существует хорошая и подробная документация, но нет поддержки рекурсивного слежения за изменениями! Также, у inotify есть ограничение на максимальное количество объектов, за которыми можно следить. Простейшая реализация демона занимает уже 250 строк кода или 8 кб. Статическая сборка с использованием dietlibc занимает примерно 14 кб. Другим неприятным моментом является то, что приложение должно само поддерживать соответствия между watch descriptor (в нашем случае это всегда директория) и именем. Есть функция inotify_add_watch, которой передается путь до отслеживаемой директории, но нет обратной — inotify_get_path, которая бы возвращала этот самый путь по переданному дескриптору. События же содержат только watch descriptor и относительный путь до изменившегося файла внутри директории.
Подводные камни рекурсивного слежения за директорией через inotify:
- Возможность переполнения очереди (длина очереди задается в /proc/sys/fs/inotify/max_queued_events)
- Ограничение на максимальное количество объектов слежения (задается в /proc/sys/fs/inotify/max_user_watches)
- Отсутствие возможности рекурсивного слежения за директорией
- Необходимость отдельно обрабатывать случай, когда создается директория (например mkdir -p a/b/c). Вы получите событие о том, что создана директория «a», но пока вы навешиваете обработчик на эту директорию, в ней уже могут создать ещё одну директорию и событие об этом вам уже не придет.
- Теоретическая возможность целочисленного переполнения watch descriptor (wd), так как он задается uint32
FreeBSD, Mac OS X, kqueue
FreeBSD и Mac OS X позволяют отслеживать за изменениями с помощью kqueue, который аналогичен inotify по своим характеристикам и также не имеет возможности рекурсивного слежения за директориями. Также, kqueue принимает в качестве аргументов дескрипторы открытых файлов (директорий), поэтому при использовании этого API ограничения на количество отслеживаемых директорий ещё более строгие.
Источник
Отслеживание изменений в директории с помощью 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». Отслеживаемые события это закрытие файла и его изменение.
Источник
Кунг-фу стиля Linux: наблюдение за файловой системой
Пользователи UNIX-подобных ОС, чтобы достичь желаемого результата, привыкли организовывать совместную работу различных программ, рассчитанных на решение какой-то одной задачи. Эти команды, например, помещают в файл Bash-скрипта, а потом, по своей инициативе, вызывают этот скрипт из командной строки. А что если нужно, чтобы система сама реагировала бы на какие-то изменения, действуя без вмешательства пользователя? Например, нужно организовать наблюдение за директорией и автоматически вызывать какую-то программу тогда, когда в эту директорию будет скопирован файл с использованием FTP. При этом не хотелось бы сидеть за компьютером во время передачи этого файла и ждать завершения операции.
Самый простой, но не очень-то красивый способ это сделать заключается в периодическом сканировании директории. Вот весьма примитивный скрипт, который реализует такой функционал:
В этом примере я вывожу содержимое файла в консоль и удаляю его. Но в реальной жизни с подобным файлом можно сделать что-нибудь куда более интересное. Это — пример того, как не стоит писать скрипты. Ведь скрипт всё время выполняется. К тому же, такое решение задачи нельзя назвать красивым или остроумным. (Если вы полагаете, что в данном примере мне стоило бы использовать конструкцию for I in * — попробуйте это сделать в пустой директории и вы поймёте причину использования команды ls .)
Как организовать наблюдение за файловой системой в Linux?
Более симпатичное решение задачи
Если говорить честно, то для решения нашей задачи хорошо было бы сделать нечто, выглядящее лучше вышеприведённого примера. В современных ядрах (начиная с 2.6.13) есть механизм уведомлений о событиях файловой системы, представленный интерфейсом inotify . Соответствующие вызовы можно использовать программно, применяя заголовочный файл sys/inotify.h . Существует и набор инструментов командной строки, который можно установить, обычно представленный пакетом inotify-tools .
Один из инструментов этого пакета, inotifywait , позволяет улучшить код нашего примера. Скажем, переделать код можно так:
Я думаю, что этот вариант лучше предыдущего. Скрипт вызывается не через заданные промежутки времени, а только тогда, когда в файловой системе что-то произошло. Я полагаю, что любая нормальная программа, воздействующая на файлы, находящиеся в директории, либо открывает файлы для записи и закрывает их после завершения операции, либо перемещает файлы в директорию. Наша программа отреагирует на оба эти события, а благодаря %f команда сообщит имя файла. Есть, конечно, и другие события, за которыми можно наблюдать.
Если вас интересует вопрос о том, почему необходимо наблюдать за событием перемещения файла, подумайте о том, как работает большинство текстовых редакторов и программ для загрузки файлов по сети. Обычно новому файлу не дают постоянного имени до тех пор, пока не завершится его обработка. Например, Chrome будет загружать файл с именем test.txt , дав ему временное имя test.txt.crdownload (или имя, подобное этому). Файл будет переименован (перемещён) в файл test.txt только после завершения загрузки.
Если вы хотите попробовать команду inotifywait без написания скрипта, чтобы своими глазами увидеть то, как она работает, откройте пару окон терминала и воспроизведите в этих окнах то, что показано ниже.
Исследование команды inotifywait
В окне, расположенном на заднем плане, выполните команду inotifywait . Не забудьте о точке в конце команды, которая указывает на то, что наблюдать надо за файлами в текущей директории. Затем, в другом окне терминала, создайте в той же самой директории файл. В окне первого терминала будет выведено имя файла, после чего inotifywait завершит работу. В скрипте используется тот же самый механизм, с его помощью скрипт записывает имя файла в переменную FN , выполняет некие действия и перезапускает inotifywait . Кстати, можно сделать так, чтобы утилита inotifywait , после срабатывания, не завершала бы работу, но это усложнит скрипт. Это, правда, устранит проблему, которая может возникнуть в том случае, если имя файла меняется в ходе попытки его обработать средствами скрипта.
Ещё один инструмент для наблюдения за файловой системой, inotifywatch , тоже реагирует на изменения файлов, но он осуществляет мониторинг файловой системы в течение некоторого времени, а потом выдаёт сводку по зафиксированным изменениям. Если вы полагаете, что это именно то, что вам нужно, почитайте справку по inotifywatch .
Новый cron
Хотя мы и улучшили наш скрипт, он всё ещё далёк от идеала. Вполне возможно то, что нужно организовать наблюдение за множеством директорий. Вряд ли кому-то захочется воспроизводить вышеописанную последовательность действий в применении к каждой интересующей его директории.
Для решения нашей задачи можно воспользоваться и ещё одной программой, называемой incron (я почти уверен в том, что вам эта программа пригодится, и что её стоит установить). Эта утилита похожа на cron , но вместо того, чтобы выполнять какие-то действия, ориентируясь на события, связанные с временем, она ориентируется на события, связанные с файлами. После её установки вам, вероятно, если вы планируете ей пользоваться, стоит отредактировать файлы /etc/incron.allow и /etc/incron.deny . Особенно — если вы будете применять её, работая как обычный пользователь.
Предположим, нам надо выполнять некий скрипт в том случае, если в директории hexfiles появится какой-нибудь файл. Для редактирования таблицы событий, за которыми наблюдает incron , можно воспользоваться командой incrontab -e . Данные, вносимые в эту таблицу, представляют собой описания задач и по-особенному форматируются. В частности, тут для разделения столбцов используются не знаки табуляции, а пробелы. Вот строка, которая позволяет решить вышеописанную задачу:
Конструкция $@/$# в конце этой строки позволяет получить полный путь к файлу, на событие, произошедшее с которым, отреагировала программа. Тут, кроме того, можно получить время возникновения события — либо в виде текста ( $% ), либо в виде числа ( $& ). С помощью incron можно наблюдать за обычными событиями. Утилита, кроме того, поддерживает различные опции. Например, можно не разыменовывать символические ссылки. Подробности об incron ищите в справке к утилите.
Настройка incron с использованием графического интерфейса
Я — не большой любитель Linux-инструментов с графическим интерфейсом, но я знаю, что многим они нравятся. Если вы — из их числа — вот incrontab-редактор, написанный на Java. Нельзя сказать, что этот проект отличается подробной документацией, но его использование упрощает то, что в него можно импортировать файл incrontab . Если он у вас есть — ищите его по пути /var/spool/incron/your_user_id . Если посмотреть на окно редактора, показанное ниже, можно заметить, что он позволяет упростить создание incron-таблицы.
Графический интерфейс для incron
Обычно системные файлы можно найти в /etc/incron.d . Пути расположения файлов можно задавать в файле /etc/incron.conf . Поэтому, если вам нужно поменять место расположения файла таблицы, но вы не знаете о том, где его искать, загляните в этот файл.
Итоги
Использование incron выглядит как очень аккуратное решение задачи мониторинга файловой системы. Системная программа берёт на себя заботу о наблюдении за событиями файловой системы, а скрипт запускается лишь тогда, когда это нужно. Кроме того, используя эту программу, можно легко узнать о том, за чем именно организовано наблюдение. В результате incron — это инструмент, с помощью которого можно сделать куда больше, чем наблюдение за текущей папкой в открытом окне терминала.
Источник