FANOTIFY(7) | Руководство программиста Linux | FANOTIFY(7) |
ИМЯ¶
fanotify - отслеживание событий в файловой системе
ОПИСАНИЕ¶
Программный интерфейс fanotify уведомляет о событиях в файловой системе и перехватывает их. Например, его можно использовать для сканирования файлов на вирусы и управления иерархическим хранилищем. В настоящее время, поддерживается только ограниченный набор событий. В частности, не поддерживаются события создания, удаления и перемещения (о программном интерфейсе для этих событий смотрите в inotify(7)).
Дополнительные возможности по сравнению с программным интерфейсом inotify(7): способность отслеживать все объекты в смонтированной файловой системе, давать права на доступ и читать или изменять файлы перед тем как доступ получат другие приложения.
В программный интерфейс входят следующие системные вызовы: fanotify_init(2), fanotify_mark(2), read(2), write(2) и close(2).
Вызовы fanotify_init(), fanotify_mark() и группы уведомлений¶
Системный вызов fanotify_init(2) создаёт и инициализирует группу уведомления fanotify и возвращает указывающий на неё файловый дескриптор.
Группа уведомления fanotify — это внутренний объект ядра, в котором хранится список файлов, каталогов и точек монтирования, для которых должны создаваться события.
For each entry in an fanotify notification group, two bit masks exist: the mark mask and the ignore mask. The mark mask defines file activities for which an event shall be created. The ignore mask defines activities for which no event shall be generated. Having these two types of masks permits a mount point or directory to be marked for receiving events, while at the same time ignoring events for specific objects under that mount point or directory.
The fanotify_mark(2) system call adds a file, directory, or mount to a notification group and specifies which events shall be reported (or ignored), or removes or modifies such an entry.
Возможное применение маски игнорирования — кэш файлов. Интересующие события для файлового кэша — изменение файла и закрытие. Для этого добавляем кэшируемый каталог или точку монтирования для приёма этих событий. После получения первого события об изменении файла, соответствующая запись кэша помечается как недействительная. Дальнейшие события об изменении файла нас не интересуют, пока файл не будет закрыт. Для этого событие об изменении можно добавить в маску игнорирования. При получении события о закрытии, событие об изменении можно удалить из маски игнорирования и запись файлового кэша можно обновить.
The entries in the fanotify notification groups refer to files and directories via their inode number and to mounts via their mount ID. If files or directories are renamed or moved within the same mount, the respective entries survive. If files or directories are deleted or moved to another mount or if mounts are unmounted, the corresponding entries are deleted.
Очередь событий¶
Для возникающих событий с объектами файловой системы, которые отслеживаются группой уведомления, система fanotify генерирует события и помещает их в очередь. После этого события можно прочитать (с помощью read(2) и подобных) из файлового дескриптора fanotify, возвращённого fanotify_init(2).
Two types of events are generated: notification events and permission events. Notification events are merely informative and require no action to be taken by the receiving application except for closing the file descriptor passed in the event (see below). Permission events are requests to the receiving application to decide whether permission for a file access shall be granted. For these events, the recipient must write a response which decides whether access is granted or not.
Событие удаляется из очереди событий группы fanotify после прочтения. События доступа, которые были прочитаны, остаются во внутреннем списке группы fanotify до тех пор, пока решение о доступе не будет записано в файловый дескриптор fanotify, или файловый дескриптор fanotify не будет закрыт.
Чтение событий fanotify¶
Вызов read(2) с файловым дескриптором, полученным от fanotify_init(2), блокирует выполнение (если не указан флаг FAN_NONBLOCK в вызове fanotify_init(2)) до тех пор, пока не произойдёт файловое событие или вызов не будет прерван сигналом (смотрите signal(7)).
After a successful read(2), the read buffer contains one or more of the following structures:
struct fanotify_event_metadata {
__u32 event_len;
__u8 vers;
__u8 reserved;
__u16 metadata_len;
__aligned_u64 mask;
__s32 fd;
__s32 pid; };
Для увеличения производительности рекомендуется использовать буфер большого размера (например, 4096 байт) для того, чтобы получить несколько событий за один вызов read(2).
Возвращаемое read(2) значение — количество байт помещённых в буфер, или -1 в случае ошибки (но смотрите ДЕФЕКТЫ).
Поля структуры fanotify_event_metadata:
- event_len
- This is the length of the data for the current event and the offset to the next event in the buffer. In the current implementation, the value of event_len is always FAN_EVENT_METADATA_LEN. However, the API is designed to allow variable-length structures to be returned in the future.
- vers
- Номер версии структуры. Он должен сравниваться с FANOTIFY_METADATA_VERSION для проверки того, что структуры, возвращаемые во время выполнения, соответствуют структурам, определённым во время компиляция. В случае несоответствия приложение должно прекратить попытки использовать файловый дескриптор fanotify.
- reserved
- Не используется.
- metadata_len
- Длина структуры. Это поле было добавлено для облегчения реализации необязательных заголовков разных типов событий. В текущей реализации такие необязательные заголовки отсутствуют.
- mask
- Битовая маска, описывающая событие (смотрите далее).
- fd
- This is an open file descriptor for the object being accessed, or FAN_NOFD if a queue overflow occurred. The file descriptor can be used to access the contents of the monitored file or directory. The reading application is responsible for closing this file descriptor.
- Когда вызывается fanotify_init(2) вызывающий может указать (в аргументе event_f_flags) различные флаги состояния файла, которые будут установлены на открытом файловом дескрипторе, соответствующем этому файловому дескриптору. Также, на отрываемом файловом дескрипторе устанавливается (внутри ядра) флаг состояния файла FMODE_NONOTIFY. Этот флаг подавляет генерацию событий fanotify. Таким образом, когда получатель события fanotify обратится к отслеживаемому файлу или каталогу через этот файловый дескриптор, дополнительных событий создано не будет.
- pid
- This is the ID of the process that caused the event. A program listening to fanotify events can compare this PID to the PID returned by getpid(2), to determine whether the event is caused by the listener itself, or is due to a file access by another process.
В битовой маске mask указывают события, произошедшие с одиночным объектом файловой системы. В маске может быть установлено несколько бит, если было более одного события с отслеживаемым объектом файловой системы. В частности, возникшие друг за другом события с одним объектом файловой системы и произошедшие из-за одного процесса могут быть объединены в одно событие, за исключением того, что два события доступа никогда не объединяются в одном элементе очереди.
Биты маски mask:
- FAN_ACCESS
- Доступ (на чтение) к файлу или каталогу (но смотрите ДЕФЕКТЫ).
- FAN_OPEN
- Файл или каталог открыт.
- FAN_MODIFY
- Файл изменён.
- FAN_CLOSE_WRITE
- Файл, открытый на запись (O_WRONLY или O_RDWR), закрыт.
- FAN_CLOSE_NOWRITE
- Файл или каталог, открытый только для чтения (O_RDONLY), закрыт.
- FAN_Q_OVERFLOW
- Очередь событий превысила ограничение в 16384 записи. Это ограничение можно изменить, указав флаг FAN_UNLIMITED_QUEUE при вызове fanotify_init(2).
- FAN_ACCESS_PERM
- Приложение хочет прочитать файл или каталог, например, с помощью read(2) или readdir(2). Читатель события должен написать ответ (описано далее) о разрешении доступа к объекту файловой системы.
- FAN_OPEN_PERM
- Приложение хочет открыть файл или каталог. Читатель события должен написать ответ о разрешении открытия объекта файловой системы.
Для проверки любого события закрытия может использоваться следующая битовая маска:
- FAN_CLOSE
- Файл закрыт. Это синоним:
-
FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE
Следующие макросы позволяют обходить буфер с метаданными событий fanotify, возвращаемый read(2) из файлового дескриптора fanotify:
- FAN_EVENT_OK(meta, len)
- Этот макрос сверяет оставшуюся длину len буфера meta с длиной структуры метаданных и полем event_len из первой структуры метаданных в буфере.
- FAN_EVENT_NEXT(meta, len)
- Этот макрос использует длину из поля event_len структуры метаданных, на которую указывает meta, для вычисления адреса следующей структуры метаданных, которая находится после meta. В поле len указано количество байт метаданных, оставшихся в буфере. Макрос возвращает указатель на следующую структуру метаданных после meta и уменьшает len на количество байт в структуре метаданных, которая была пропущена (т. е., вычитает meta->event_len из len).
Дополнительно есть:
- FAN_EVENT_METADATA_LEN
- Этот макрос возвращает размер (в байтах) структуры fanotify_event_metadata. Это минимальный размер (и, в настоящее время, единственный) метаданных любого события.
Отслеживание событий через файловый дескриптор fanotify¶
Когда возникает событие fanotify файловый дескриптор fanotify помечается как доступный для чтения при его передаче в epoll(7), poll(2) или select(2).
Работа с событиями доступа¶
Для событий доступа приложение должно записать (write(2)) в файловый дескриптор fanotify следующую структуру:
struct fanotify_response {
__s32 fd;
__u32 response; };
Поля этой структуры имеют следующее назначение:
- fd
- Файловый дескриптор из структуры fanotify_event_metadata.
- response
- В этом поле указывает о разрешении доступа или запрещении. Данное значение должно быть равно FAN_ALLOW, чтобы разрешить операцию с файлом, или FAN_DENY для запрета.
Если доступ запрещается, то запрашивающее приложение получит ошибку EPERM.
Закрытие файлового дескриптора fanotify¶
Когда все файловые дескрипторы, указывающие на группу уведомления fanotify, закрыты, группа fanotify освобождается и её ресурсы становятся доступны ядру для повторного использования. После close(2) все оставшиеся непросмотренные события доступа будут разрешены.
/proc/[pid]/fdinfo¶
Файл /proc/[pid]/fdinfo/[fd] содержит информацию о метках fanotify для файлового дескриптора fd процесса pid Подробности смотрите в proc(5).
ОШИБКИ¶
Кроме обычных ошибок read(2) при чтении из файлового дескриптора fanotify могут возникать следующие ошибки:
- EINVAL
- Буфер слишком мал для хранения события.
- EMFILE
- Достигнуто максимальное попроцессное количество открытых файлов. Смотрите описание RLIMIT_NOFILE в getrlimit(2).
- ENFILE
- Достигнут предел на общее количество открытых файлов в системе. Смотрите /proc/sys/fs/file-max в proc(5).
- ETXTBSY
- Эта ошибка возвращается read(2), если при вызове fanotify_init(2) в аргументе event_f_flags был указан O_RDWR или O_WRONLY и произошло событие с отслеживаемым файлом, который в данный момент выполняется.
Кроме обычных ошибок write(2) при записи в файловый дескриптор fanotify могут возникать следующие ошибки:
ВЕРСИИ¶
Программный интерфейс fanotify представлен в версии 2.6.36 ядра Linux и включён в версии 2.6.37. Поддержка fdinfo была добавлена в версии 3.8.
СООТВЕТСТВИЕ СТАНДАРТАМ¶
Программный интерфейс fanotify есть только в Linux.
ЗАМЕЧАНИЯ¶
Программный интерфейс fanotify доступен только, если ядро собрано с включённым параметром настройки CONFIG_FANOTIFY. Также, работа с доступом в fanotify доступна только, если включён параметр настройки CONFIG_FANOTIFY_ACCESS_PERMISSIONS.
Ограничения и подводные камни¶
Fanotify сообщает только о событиях, которые возникли при использовании пользовательскими программами программного интерфейса файловой системы. Поэтому события об обращении к файлам в сетевых файловых системах не отлавливаются.
Программный интерфейс fanotify не сообщает о доступе и изменениях, которые могут произойти из-за mmap(2), msync(2) и munmap(2).
События для каталогов создаются только, если сам каталог открывается, читается и закрывается. Добавление, удаление и изменение потомков отслеживаемого каталога не приводит к возникновению событий.
Fanotify не следит за каталогами рекурсивно: чтобы следить за подкаталогами каталога, нужно их явно пометить (и, заметим, что программный интерфейс fanotify не позволяет отслеживать создание подкаталога, что затрудняет рекурсивное слежение). Отслеживание точек монтирования позволяет следить за всем деревом каталогов.
Очередь событий может переполниться. В этом случае события теряются.
ДЕФЕКТЫ¶
До Linux 3.19, fallocate(2) не генерировал событий fanotify. Начиная с Linux 3.19, вызовы fallocate(2) генерируют событие FAN_MODIFY.
В Linux 3.17 существуют следующие дефекты:
- В Linux объект файловой системы может быть доступен через несколько путей, например, часть файловой системы может быть перемонтирована mount(8) с использованием параметра --bind. Ожидающий слушатель получит уведомления об объекте файловой системы только из запрошенной точки монтирования. О событиях из других точек уведомлений не поступит.
- При генерации события не делается проверка, что пользовательскому ID получающего процесса разрешено читать или писать в файл перед передачей файлового дескриптора на этот файл. Это представляет некоторый риск безопасности, когда у программ, выполняющихся непривилегированными пользователями, есть мандат CAP_SYS_ADMIN.
- Если вызов read(2) получает несколько событий из очереди fanotify и возникает ошибка, будет возвращена полная длина событий, которые были успешно скопированы в буфер пользовательского пространства до ошибки. Возвращаемое значение не будет равно -1, и в errno не записывается код ошибки. То есть читающее приложение не может обнаружить ошибку.
ПРИМЕР¶
The following program demonstrates the usage of the fanotify API. It marks the mount point passed as a command-line argument and waits for events of type FAN_PERM_OPEN and FAN_CLOSE_WRITE. When a permission event occurs, a FAN_ALLOW response is given.
The following output was recorded while editing the file /home/user/temp/notes. Before the file was opened, a FAN_OPEN_PERM event occurred. After the file was closed, a FAN_CLOSE_WRITE event occurred. Execution of the program ends when the user presses the ENTER key.
Пример вывода¶
# ./fanotify_example /home Нажмите enter для завершения работы. Ожидание событий. FAN_OPEN_PERM: Файл /home/user/temp/notes FAN_CLOSE_WRITE: Файл /home/user/temp/notes Ожидание событий прекращено.
Исходный код программы¶
#define _GNU_SOURCE /* для получения определения O_LARGEFILE */ #include <errno.h> #include <fcntl.h> #include <limits.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys/fanotify.h> #include <unistd.h> /* читаем все доступные события fanotify из файлового дескриптора 'fd' */ static void handle_events(int fd) {
const struct fanotify_event_metadata *metadata;
struct fanotify_event_metadata buf[200];
ssize_t len;
char path[PATH_MAX];
ssize_t path_len;
char procfd_path[PATH_MAX];
struct fanotify_response response;
/* проходим по всем событиям, которые можем прочитать
из файлового дескриптора fanotify */
for(;;) {
/* читаем несколько событий */
len = read(fd, (void *) &buf, sizeof(buf));
if (len == -1 && errno != EAGAIN) {
perror("read");
exit(EXIT_FAILURE);
}
/* проверяем, достигнут ли конец доступных данных */
if (len <= 0)
break;
/* выбираем первое событие в буфере */
metadata = buf;
/* проходим по всем событиям в буфере */
while (FAN_EVENT_OK(metadata, len)) {
/* проверяем, что структуры, использовавшиеся при сборке,
идентичны структурам при выполнении */
if (metadata->vers != FANOTIFY_METADATA_VERSION) {
fprintf(stderr,
"Версия метаданных fanotify не совпадает.\n");
exit(EXIT_FAILURE);
}
/* metadata->fd содержит или FAN_NOFD, указывающее
на переполнение очереди, или файловый дескриптор
(неотрицательное целое). Здесь мы просто игнорируем
переполнение очереди. */
if (metadata->fd >= 0) {
/* обрабатываем событие на право открытия */
if (metadata->mask & FAN_OPEN_PERM) {
printf("FAN_OPEN_PERM: ");
/* разрешаем открыть файл */
response.fd = metadata->fd;
response.response = FAN_ALLOW;
write(fd, &response,
sizeof(struct fanotify_response));
}
/* обрабатываем событие закрытия записываемого файла */
if (metadata->mask & FAN_CLOSE_WRITE)
printf("FAN_CLOSE_WRITE: ");
/* получаем и выводим имя файла, к которому
отслеживается доступ */
snprintf(procfd_path, sizeof(procfd_path),
"/proc/self/fd/%d", metadata->fd);
path_len = readlink(procfd_path, path,
sizeof(path) - 1);
if (path_len == -1) {
perror("readlink");
exit(EXIT_FAILURE);
}
path[path_len] = '\0';
printf("Файл %s\n", path);
/* закрываем файловый дескриптор из события */
close(metadata->fd);
}
/* переходим на следующее событие */
metadata = FAN_EVENT_NEXT(metadata, len);
}
} } int main(int argc, char *argv[]) {
char buf;
int fd, poll_num;
nfds_t nfds;
struct pollfd fds[2];
/* проверяем заданную точку монтирования */
if (argc != 2) {
fprintf(stderr, "Использование: %s ТОЧКА_МОНТИРОВАНИЯ\n",
argv[0]);
exit(EXIT_FAILURE);
}
printf("Нажмите enter для завершения работы.\n");
/* Создаём файловый дескриптор для доступа к fanotify API */
fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
O_RDONLY | O_LARGEFILE);
if (fd == -1) {
perror("fanotify_init");
exit(EXIT_FAILURE);
}
/* Помечаем точку монтирования для:
- событий доступа перед открытием файлов
- событий уведомления после закрытия файлового дескриптора
для файла открытого для записи */
if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
FAN_OPEN_PERM | FAN_CLOSE_WRITE, AT_FDCWD,
argv[1]) == -1) {
perror("fanotify_mark");
exit(EXIT_FAILURE);
}
/* подготовка к опросу */
nfds = 2;
/* ввод с консоли */
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
/* ввод из fanotify */
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; /* перезапуск poll() */
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) {
/* доступны события fanotify */
handle_events(fd);
}
}
}
printf("Ожидание событий прекращено.\n");
exit(EXIT_SUCCESS); }
СМ. ТАКЖЕ¶
ЗАМЕЧАНИЯ¶
Эта страница является частью проекта Linux man-pages версии 4.16. Описание проекта, информацию об ошибках и последнюю версию этой страницы можно найти по адресу https://www.kernel.org/doc/man-pages/.
ПЕРЕВОД¶
Русский перевод этой страницы руководства был сделан Azamat Hackimov <azamat.hackimov@gmail.com>, Dmitry Bolkhovskikh <d20052005@yandex.ru>, Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>
Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.
Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.
15 сентября 2017 г. | Linux |