INOTIFY(7) | Руководство программиста Linux | INOTIFY(7) |
inotify - наблюдает за событиями файловой системы
Программный интерфейс inotify предоставляет механизм для слежения за событиями в файловой системе. Его можно использовать для слежения за отдельными файлами или каталогами. При слежении за каталогами inotify возвращает события как для самого каталога, так и для файлов внутри каталога.
В программный интерфейс входят следующие системные вызовы:
При корректном программировании, приложение может эффективно использовать inotify для слежения и кэширования состояния набора объектов файловой системы. Однако, в тщательно проработанных приложениях нужно предполагать наличие ошибок в логике слежения или состязательности, описанных далее, которые могут приводить к рассогласованности кэша с состоянием файловой системы. Вероятно, лучше сделать некоторую проверку и перестроить кэш при обнаружении рассогласованности.
Чтобы определить, что события произошли, приложение должно прочитать (read(2)) файловый дескриптор inotify. Если событий не было, то предполагая, что это блокирующий файловый дескриптор, вызов read(2) заблокирует работу до возникновения, по крайней мере, одного события (если не будет прерван сигналом; в этом случае вызов завершается с ошибкой EINTR, смотрите signal(7)).
При успешном выполнении read(2) возвращает буфер с одной или более структурами следующего вида:
struct inotify_event { int wd; /* дескриптор наблюдаемого */ uint32_t mask; /* маска, описывающая событие */ uint32_t cookie; /* уникальный cookie, связывающий относящиеся друг к другу события (для rename(2)) */ uint32_t len; /* размер поля name */ char name[]; /* необязательное имя, завершающееся null */ };
В wd указывается сторожок, к которому относится событие. Это один из дескрипторов сторожка, полученный из вызова inotify_add_watch(2).
В mask содержатся биты, описывающие возникшее событие (смотрите ниже).
Значение cookie — это уникальное целое, которое объединяет связанные события. В настоящее время используется только для событий переименования и позволяет приложению объединить возвращаемые IN_MOVED_FROM и IN_MOVED_TO в пару событий. Для остальных типов событий значение cookie равно 0.
Поле name существует только когда событие возвращается для файла внутри отслеживаемого каталога; им определяется имя файла внутри отслеживаемого каталога. Это имя завершается null и может включать дополнительные байты null («\0») для выравнивания на подходящую границу адреса при последующих операций чтения.
Поле len содержит количество всех байт в name, включая байты null; длина каждой структуры inotify_event равна sizeof(struct inotify_event)+len.
Если буфер, заданный в read(2), слишком мал для возврата информации о следующем событии, то поведение зависит от версии ядра: в ядрах до версии 2.6.21, read(2) возвращает 0; начиная с версии 2.6.21, read(2) завершается с ошибкой EINVAL. Указание размера буфера
sizeof(struct inotify_event) + NAME_MAX + 1
будет достаточно для чтения, по крайней мере, одного события.
В аргументе inotify_add_watch(2) mask и поле mask структуры inotify_event, возвращаемых при чтении файлового дескриптора inotify, содержатся битовые маски, определяющие события inotify. Следующие биты могут быть заданы в mask при вызове inotify_add_watch(2) и возвращены в поле mask, возвращаемом read(2):
Наблюдение inotify ведётся за inode: при наблюдении за файлом (но не когда наблюдение ведётся за каталогом, содержащим файл) событие может генерироваться при активности по любой ссылке на файл (находящейся в том же или в другом каталоге).
При наблюдении за каталогом:
Замечание: при слежении за каталогом события не генерируются для файлов каталога, если событие возникает по пути (т. е., по ссылке), который находится вне отслеживаемого каталога.
Когда события генерируются для объектов внутри отслеживаемого каталога, поле name, возвращаемое в структуре inotify_event, хранит имя файла внутри этого каталога.
Макрос IN_ALL_EVENTS определён как битовая маска всех перечисленных выше событий. Данный макрос можно использовать в качестве аргумента mask в вызове inotify_add_watch(2).
Дополнительно, два удобных макроса:
Также, при вызове inotify_add_watch(2) в mask могут быть указаны следующие биты:
Следующие биты могут быть установлены в поле mask при возврате из read(2):
Предположим, приложение следит за всеми событиями для каталога dir и файла dir/myfile. В примере ниже показаны некоторые события, которые будут сгенерированы для этих двух объектов.
Предположим, приложение следит за всеми событиями для каталогов dir1 и dir2 и файла dir1/myfile. В примере ниже показаны некоторые события, которые могут быть сгенерированы.
Предположим, что dir1/xx и dir2/yy только ссылки на один файл и приложение следит за dir1, dir2, dir1/xx и dir2/yy. При выполнение следующих вызовов в порядке, указанном ниже, будут сгенерированы следующие события:
Предположим, приложение следит за каталогом dir и пустым каталогом dir/subdir. В примере ниже показаны некоторые события, которые могут быть сгенерированы.
Для ограничения потребления inotify памяти ядра, можно использовать следующие интерфейсы:
Программный интерфейс inotify был добавлен в ядро Linux версии 2.6.13. Необходимые библиотечные интерфейсы добавлены в glibc версии 2.4 (IN_DONT_FOLLOW, IN_MASK_ADD и IN_ONLYDIR добавлены в glibc версии 2.5).
Программный интерфейс inotify есть только в Linux.
За файловыми дескрипторами inotify можно наблюдать с помощью select(2), poll(2), и epoll(7). Когда возникает событие, файловый дескриптор указывает на возможность чтения.
Начиная с Linux 2.6.25, для файловых дескрипторов inotify стали доступны уведомления ввода-вывода посредством сигналов; смотрите обсуждение F_SETFL (для установки флага O_ASYNC), F_SETOWN и F_SETSIG в fcntl(2). Структура siginfo_t (описана в sigaction(2)), передаваемая обработчику сигнала, содержит следующие настройки полей: в si_fd указывается номер файлового дескриптора inotify; в si_signo указывается номер сигнала; в si_code указывается POLL_IN; в si_band указывается POLLIN.
Если последующие события inotify, выводимые в файловый дескриптор inotify, одинаковы (содержат одинаковые значения wd, mask, cookie и name), то они сливаются в одно событие, если самое старое событие ещё не прочитано (но смотрите ДЕФЕКТЫ). Это сокращает требуемое количество памяти ядра для очереди событий, но также означает, что приложение не может использовать inotify для надёжного подсчёта файловых событий.
События, возвращаемые при чтении из файлового дескриптора inotify, формируют упорядоченную очередь. То есть, например, это гарантирует, что при переименовании одного каталога в другой, события в файловом дескрипторе inotify будут созданы в правильном порядке.
Набор наблюдаемых дескрипторов, которые отслеживаются через файловый дескриптор inotify, можно увидеть из записи для файлового дескриптора inotify в каталоге процесса /proc/[pid]/fdinfo. Дополнительную информацию смотрите в proc(5). Вызов FIONREAD ioctl(2) возвращает количество байт, доступных для чтения из файлового дескриптора inotify.
Программный интерфейс inotify не предоставляет информацию о пользователе или процессе, из-за которого возникло событие. В частности, для процесса, отслеживающего события через inotify, нет простого способа определить, возникли события из-за его действий или из-за действий других процессов.
Inotify сообщает только о событиях, которые возникли из-за пользовательских программ, использовавших программный интерфейс файловой системы. То есть, не возникает событий для файловых систем, доступных по сети (приложения должны использовать старый метод опроса файловой системы для слежения за такими событиями). Кроме того, различные псевдо-файловый системы, такие как /proc, /sys и /dev/pts, не отслеживаются через inotify.
Программный интерфейс inotify не сообщает о доступе и изменениях, которые могут произойти из-за mmap(2), msync(2) и munmap(2).
Программный интерфейс inotify в качестве идентификаторов объектов использует имена файлов. Однако, в момент обработки приложением события inotify, имя файла может быть уже удалено или переименовано.
Программный интерфейс inotify различает события по их дескрипторам сторожков. Приложение само должно кэшировать сопоставление (если нужно) дескрипторов сторожков и имён. Имейте в виду, что переименование каталога может повлиять на несколько кэшированных путей.
Отслеживание каталогов через inotify ведётся не рекурсивно: чтобы отслеживать подкаталоги, нужно создать дополнительные сторожки. Это может занять много времени при большом дереве каталога.
Если отслеживается полное дерево каталога и создаётся новый каталог в этом дереве или существующий каталог переименовывается в этом дереве, учтите, что на момент создания сторожка за новым подкаталогом, в подкаталоге могут уже существовать новые файлы (и подкаталоги). Поэтому вам может потребоваться сканировать содержимое подкаталога сразу после добавления сторожка (и, если нужно, рекурсивно добавить сторожки для всех подкаталогов, которые в нём есть).
Заметим, что очередь событий может переполниться. В этом случае события теряются. Корректные приложения должны учитывать возможность пропажи событий. Например, может потребоваться перестроить часть или весь кэш приложения (один простой, но, возможно, затратный способ, это закрыть файловый дескриптор inotify, опустошить кэш, создать новый файловый дескриптор inotify и затем пересоздать сторожки и записи в кэше для отслеживаемых объектов).
Если файловая система смонтирована поверх отслеживаемого каталога, то событие не генерируются, а также не генерируются события для объектов, находящихся в новой точке монтирования на первом уровне. Если в дальнейшем файловая система отмонтируется, то события начнут генерироваться для каталога и содержащихся в нём объектов.
Как указывалось выше, из событий IN_MOVED_FROM и IN_MOVED_TO, генерируемых rename(2), можно определить пару по их одинаковому значению cookie. Однако, с этой задачей есть несколько проблем.
Эти два события, обычно, стоят друг за другом в потоке событий, если читать из файлового дескриптора inotify. Однако, это не гарантируется. Если несколько процессов создают события для отслеживаемых объектов, то (в редких случаях) произвольное количество других событий может появиться между событиями IN_MOVED_FROM и IN_MOVED_TO. Кроме того, не гарантируется, что пара событий вставляется в очередь атомарно: может существовать короткий интервал, в котором IN_MOVED_FROM уже появилось, а IN_MOVED_TO ещё нет.
Соответствие IN_MOVED_FROM и IN_MOVED_TO паре событий, сгенерированных rename(2), по сути, просто (не забудьте, что если объект переименовывается вне отслеживаемого каталога, то может не быть даже события IN_MOVED_TO). Можно использовать эвристические предположения (например, что события всегда следуют друг за другом), что работает в большинстве случаев, но неминуемо не сработает в некоторых случаях, в которых приложение посчитает события IN_MOVED_FROM и IN_MOVED_TO несвязными. Если в результате дескрипторы сторожков будут уничтожены и пересозданы, то такие дескрипторы будут несогласованны с дескрипторами сторожков для любых ожидающих событий (пересоздание файлового дескриптора inotify и пересборка кэша может быть полезна в этом случае).
Также приложения должны учитывать возможность того, что событие IN_MOVED_FROM — последнее событие, которое попало в буфер, возвращаемый текущим вызовом read(2), и сопутствующее событие IN_MOVED_TO может быть получено только при следующем чтении read(2), которое должно быть выполнено с (коротким) промежутком, позволяющим фактическую не атомарную вставку пары событий IN_MOVED_FROM-IN_MOVED_TO, и также возможность того, что может отсутствовать событие IN_MOVED_TO.
До Linux 3.19, fallocate(2) не создавал события inotify. Начиная с Linux 3.19, вызов fallocate(2) генерирует событие IN_MODIFY.
В ядрах до 2.6.16 флаг IN_ONESHOT в mask не работает.
В первоначальной задумке и реализации флаг IN_ONESHOT не приводил к генерации события IN_IGNORED, если наблюдение отменялось после одного события. Однако, как непреднамеренный эффект других изменений, начиная с Linux 2.6.36, событие IN_IGNORED в этом случае генерируется.
До ядра версии 2.6.25, код ядра, который отвечал за объединение последующих одинаковых событий (т. е., два самых новых события могли быть объединены, если старое событие ещё не было прочитано), вместо этого проверял, можно ли объединить самое новое событие с самым старым непрочитанным событием.
Когда дескриптор сторожка удаляется вызовом inotify_rm_watch(2) (или из-за удаления отслеживаемого файла, или размонтирования содержащей его файловой системы), все ожидающие непрочитанные события для этого дескриптора сторожка остаются доступными для чтения. Так как дескрипторы сторожков в дальнейшем циклически выделяются inotify_add_watch(2), ядро поступательно проходит через диапазон возможных дескрипторов сторожков (от 0 до INT_MAX). При выделении свободного дескриптора сторожка для выбранного номера не производится проверка того, есть ли какие-то ожидающие непрочитанные события в очереди inotify с таким номером или нет. То есть может случиться так, что дескриптор сторожка выделяется повторно даже когда существуют ожидающие непрочитанные события, оставшиеся от предыдущего выделения дескриптора сторожка с тем же номером; в результате приложение может прочесть эти события и посчитать их как принадлежащие файлу, связанному с новым повторно задействованным дескриптором сторожка. На практике, вероятность столкновения с этой ошибкой может быть чрезвычайно низка, так как для этого требуется, чтобы приложения циклически перебрало все INT_MAX дескрипторов сторожков, освободило дескриптор сторожка и оставило непрочитанные события этого дескриптора сторожка в очереди, а затем повторно задействовало этот дескриптор сторожка. По этой причине и из-за того, что ещё никто не сообщал об этой ошибке в реальности, на момент актуальности Linux 3.15, в ядре ничего не было сделано для того, чтобы устранить этот дефект.
Следующая программа демонстрирует использование программного интерфейса inotify. Она помечает каталоги, переданной в аргументах командной строки, и ждёт событий с типом IN_OPEN, IN_CLOSE_NOWRITE и IN_CLOSE_WRITE.
Следующий вывод был записан при редактировании файла /home/user/temp/foo и просмотра каталога /tmp. Перед открытием файла и каталога произошли события IN_OPEN. После закрытия файла произошло событие IN_CLOSE_WRITE. После закрытия каталога произошло событие IN_CLOSE_NOWRITE. Выполнение программы закончилось после нажатия пользователем клавиши ENTER.
$ ./a.out /tmp /home/user/temp Нажмите ENTER для завершения работы. Ожидание событий. IN_OPEN: /home/user/temp/foo [файл] IN_CLOSE_WRITE: /home/user/temp/foo [файл] IN_OPEN: /tmp/ [каталог] IN_CLOSE_NOWRITE: /tmp/ [каталог] Ожидание событий прекращено.
#include <errno.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys/inotify.h> #include <unistd.h> /* Читаем все доступные события из файлового дескриптора «fd». wd — таблица дескрипторов сторожков для каталогов из argv. argc — длина wd и argv. argv — список наблюдаемых каталогов. Элемент 0 в wd и argv не используется. */ static void handle_events(int fd, int *wd, int argc, char* argv[]) { /* В некоторых системах невозможно прочитать целые переменные, если они неправильно выровнены. В других системах некорректное выравнивание может снижать производительность. Таким образом, буфер, используемый для чтения из файлового дескриптора inotify, должен быть выровнен также как структура struct inotify_event. */ char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; int i; ssize_t len; char *ptr; /* проходим по всем событиям, которые можем прочитать из файлового дескриптора inotify */ for (;;) { /* читаем несколько событий */ len = read(fd, buf, sizeof buf); if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* Если неблокирующий read() не найдёт событий для чтения, то вернёт -1 с errno равным EAGAIN. В этом случае выходим из цикла. */ if (len <= 0) break; /* проходим по всем событиям в буфере */ for (ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; /* печатаем тип события */ if (event->mask & IN_OPEN) printf("IN_OPEN: "); if (event->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE: "); if (event->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE: "); /* печатаем имя наблюдаемого каталога */ for (i = 1; i < argc; ++i) { if (wd[i] == event->wd) { printf("%s/", argv[i]); break; } } /* печатаем имя файла */ if (event->len) printf("%s", event->name); /* печатаем тип объекта файловой системы */ if (event->mask & IN_ISDIR) printf(" [каталог]\n"); else printf(" [файл]\n"); } } } int main(int argc, char* argv[]) { char buf; int fd, i, poll_num; int *wd; nfds_t nfds; struct pollfd fds[2]; if (argc < 2) { printf("Использование: %s ПУТЬ [ПУТЬ …]\n", argv[0]); exit(EXIT_FAILURE); } printf("Нажмите ENTER для завершения работы.\n"); /* Создаём файловый дескриптор для доступа к inotify API */ fd = inotify_init1(IN_NONBLOCK); if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); } /* выделяем память под дескрипторы сторожков */ wd = calloc(argc, sizeof(int)); if (wd == NULL) { perror("calloc"); exit(EXIT_FAILURE); } /* помечаем каталоги для событий - файл был открыт - файл был закрыт */ for (i = 1; i < argc; i++) { wd[i] = inotify_add_watch(fd, argv[i], IN_OPEN | IN_CLOSE); if (wd[i] == -1) { fprintf(stderr, "Невозможно пронаблюдать '%s'\n", argv[i]); perror("inotify_add_watch"); exit(EXIT_FAILURE); } } /* подготовка к опросу */ nfds = 2; /* ввод с консоли */ fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; /* ввод inotify */ fds[1].fd = fd; fds[1].events = POLLIN; /* ждём события и/или ввода с терминала */ printf("Ожидание событий.\n"); while (1) { poll_num = poll(fds, nfds, -1); if (poll_num == -1) { if (errno == EINTR) continue; perror("poll"); exit(EXIT_FAILURE); } if (poll_num > 0) { if (fds[0].revents & POLLIN) { /* доступен ввод с консоли: опустошаем stdin и выходим */ while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n') continue; break; } if (fds[1].revents & POLLIN) { /* доступны события inotify */ handle_events(fd, wd, argc, argv); } } } printf("Ожидание событий прекращено.\n"); /* закрываем файловый дескриптор inotify */ close(fd); free(wd); exit(EXIT_SUCCESS); }
inotifywait(1), inotifywatch(1), inotify_add_watch(2), inotify_init(2), inotify_init1(2), inotify_rm_watch(2), read(2), stat(2), fanotify(7)
Файл Documentation/filesystems/inotify.txt в дереве исходного кода ядра Linux
2019-03-06 | Linux |