dwatch/source/fanotify_wrapper.d
2025-08-22 22:09:19 +03:00

200 lines
16 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Модуль fanotify_wrapper.d
// Этот модуль предоставляет обёртку вокруг API fanotify для упрощения мониторинга событий файловой системы в Linux.
// Fanotify позволяет получать уведомления о действиях с файлами, такими как открытие, модификация, создание и удаление.
// Обёртка включает структуру для событий, класс для управления дескриптором и методы для инициализации, маркировки и чтения событий.
// Импорты: public import fanotify - предполагается, что это низкоуровневый модуль с определениями из <linux/fanotify.h>.
// Другие импорты из core.sys.posix для системных вызовов (read, close и т.д.), std.exception для обработки ошибок,
// std.string и std.conv для работы со строками, core.stdc.errno для errno и strerror для детальных сообщений об ошибках,
// core.stdc.stdint для типов вроде uint64_t.
module fanotify_wrapper;
public import fanotify; // Импорт низкоуровневых определений fanotify (структуры, константы, функции вроде fanotify_init, fanotify_mark).
import core.sys.posix.unistd : read, close, ssize_t; // Импорт функций для чтения (read), закрытия (close) дескрипторов и типа ssize_t для возвращаемых значений.
import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD; // Импорт флагов для open (O_RDONLY - только чтение, O_LARGEFILE - поддержка больших файлов) и AT_FDCWD для текущей директории.
import std.exception : enforce; // Импорт enforce для проверки условий и бросания исключений при ошибках.
import std.string : toStringz, fromStringz; // Импорт функций для конвертации строк D в C-строки (toStringz) и обратно (fromStringz).
import std.conv : to; // Импорт to для конвертации типов (например, int в string).
import core.stdc.errno : errno; // Импорт errno для получения кода последней ошибки.
import core.stdc.string : strerror; // Импорт strerror для получения строкового описания ошибки по errno.
import core.stdc.stdint; // Импорт стандартных целочисленных типов (uint64_t и т.п.).
// Структура FanotifyEvent: представляет одно событие fanotify.
// Расширена по сравнению с базовой fanotify_event_metadata: добавлено поле name для извлечённого имени файла (если используются флаги FAN_REPORT_NAME).
// Также добавлены свойства для доступа к ключевым полям и методы для проверки конкретных типов событий.
// Это упрощает работу с событиями, делая код более читаемым (вместо прямого доступа к meta.mask и т.д.).
struct FanotifyEvent
{
fanotify_event_metadata meta; // Базовая структура метаданных события из fanotify (содержит mask, fd, pid, event_len и т.д.).
string name; // Извлечённое имя файла или директории (относительное имя, если FAN_REPORT_NAME включено; парсится из дополнительной информации в буфере).
// Свойство mask: возвращает маску событий (битовая маска, где каждый бит соответствует типу события, например FAN_OPEN).
@property uint64_t mask() const
{
return meta.mask; // Просто возвращает значение из meta; const гарантирует, что структура не модифицируется.
}
// Свойство eventFd: возвращает дескриптор файла события (fd). В режиме FAN_REPORT_FID это FAN_NOFD (-1), иначе реальный fd.
@property int eventFd() const
{
return meta.fd; // Доступ к fd из meta; полезно, если нужно работать с файлом напрямую (но в этом коде используется режим без fd).
}
// Свойство pid: возвращает PID процесса, который вызвал событие.
@property int pid() const
{
return meta.pid; // Доступ к pid из meta; помогает идентифицировать, какой процесс взаимодействовал с файлом.
}
// Метод isOpen: проверяет, включает ли маска событие открытия файла (FAN_OPEN).
// Использует битовую операцию & для проверки наличия бита FAN_OPEN в mask.
bool isOpen() const
{
return (mask & FAN_OPEN) != 0; // Если бит установлен, возвращает true; это стандартный способ работы с битoвыми масками.
}
// Метод isModify: проверяет событие модификации файла (FAN_MODIFY, например, запись в файл).
bool isModify() const
{
return (mask & FAN_MODIFY) != 0; // Аналогично, проверка бита FAN_MODIFY.
}
// Метод isCloseWrite: проверяет закрытие файла после записи (FAN_CLOSE_WRITE).
bool isCloseWrite() const
{
return (mask & FAN_CLOSE_WRITE) != 0; // Проверка бита для закрытия с модификацией.
}
// Метод isCloseNoWrite: проверяет закрытие файла без записи (FAN_CLOSE_NOWRITE, например, после чтения).
bool isCloseNoWrite() const
{
return (mask & FAN_CLOSE_NOWRITE) != 0; // Проверка бита для закрытия без модификации.
}
// Метод isAccess: проверяет событие доступа (FAN_ACCESS, например, чтение).
bool isAccess() const
{
return (mask & FAN_ACCESS) != 0; // Проверка бита FAN_ACCESS.
}
// Метод isCreate: проверяет создание файла или директории (FAN_CREATE).
bool isCreate() const
{
return (mask & FAN_CREATE) != 0; // Проверка бита для создания.
}
// Метод isDelete: проверяет удаление файла или директории (FAN_DELETE).
bool isDelete() const
{
return (mask & FAN_DELETE) != 0; // Проверка бита для удаления.
}
}
// Класс Fanotify: основной класс для работы с fanotify.
// Управляет дескриптором (fd), инициализирует его, маркирует пути для мониторинга, читает события и закрывает дескриптор.
// Расширен для парсинга дополнительной информации (FID, NAME) в методе readEvents.
// Использует RAII: дескриптор закрывается в деструкторе автоматически.
class Fanotify
{
private int fd = -1; // Приватный дескриптор fanotify; инициализирован -1 (недействительный), чтобы избежать использования до инициализации.
// Конструктор: инициализирует fanotify с заданными флагами.
// initFlags: флаги для fanotify_init (например, FAN_CLASS_NOTIF для уведомлений без контроля доступа).
// eventFFlags: флаги для событий (по умолчанию O_RDONLY | O_LARGEFILE для чтения больших файлов).
this(uint initFlags, uint eventFFlags = O_RDONLY | O_LARGEFILE)
{
fd = fanotify_init(initFlags, eventFFlags); // Вызов системной функции fanotify_init для создания дескриптора.
enforce(fd >= 0, "Ошибка инициализации fanotify: " ~ to!string(fd)); // Проверка: если fd < 0, бросить исключение с сообщением; enforce упрощает обработку ошибок.
}
// Деструктор: автоматически вызывается при уничтожении объекта.
~this()
{
if (fd >= 0) // Проверка, валиден ли fd (чтобы избежать закрытия -1).
{
close(fd); // Закрытие дескриптора через системный вызов close; освобождает ресурсы.
fd = -1; // Установка в -1 для безопасности (хотя объект уничтожается).
}
}
// Метод mark: маркирует путь (файл или директорию) для мониторинга.
// markFlags: флаги маркировки (например, FAN_MARK_ADD для добавления, FAN_MARK_ONLYDIR для только директорий).
// eventMask: маска событий, которые нужно мониторить (битовая маска, например FAN_OPEN | FAN_MODIFY).
// dirFd: дескриптор директории (по умолчанию AT_FDCWD - текущая).
// path: путь к маркируемому объекту (null для текущей директории).
void mark(uint markFlags, uint64_t eventMask, int dirFd = AT_FDCWD, string path = null)
{
const(char)* cPath = path ? path.toStringz() : null; // Конвертация пути в C-строку (toStringz добавляет null-терминатор); если path null, то null.
int res = fanotify_mark(fd, markFlags, eventMask, dirFd, cPath); // Вызов системной функции fanotify_mark для добавления марки.
if (res == -1) // Проверка на ошибку (-1 означает неудачу).
{
// Сборка детального сообщения об ошибке: включает res, errno и описание от strerror.
string errMsg = "Ошибка маркировки fanotify: " ~ to!string(
res) ~ " (errno: " ~ to!string(errno) ~ ", " ~ strerror(errno)
.fromStringz.to!string ~ ")";
throw new Exception(errMsg); // Бросить исключение для прерывания выполнения при ошибке.
}
}
// Метод readEvents: читает события из дескриптора fanotify.
// bufferSize: размер буфера для чтения (по умолчанию 4096 байт - размер страницы памяти, достаточно для нескольких событий).
// Возвращает массив FanotifyEvent; расширен для парсинга дополнительной информации (FID, DFID, NAME).
FanotifyEvent[] readEvents(size_t bufferSize = 4096)
{
ubyte[] buffer = new ubyte[bufferSize]; // Выделение буфера unsigned byte[] для сырых данных (fanotify возвращает байты).
ssize_t len = read(fd, buffer.ptr, buffer.length); // Чтение данных из fd через системный вызов read; len - количество прочитанных байт (блокирующий вызов, ждёт событий).
if (len <= 0) // Если ничего не прочитано или ошибка, вернуть пустой массив.
{
return []; // Нет событий или ошибка (не бросаем исключение, чтобы цикл мог продолжаться).
}
FanotifyEvent[] events; // Массив для собранных событий.
size_t offset = 0; // Смещение в буфере для парсинга (события идут подряд).
while (offset + FAN_EVENT_METADATA_LEN <= len) // Цикл по буферу: пока хватает места для минимальной структуры metadata.
{
auto meta = cast(fanotify_event_metadata*)(buffer.ptr + offset); // Кастинг байтов в структуру metadata (unsafe, но стандартно для C-API).
if (meta.event_len < FAN_EVENT_METADATA_LEN || offset + meta.event_len > len) // Проверка валидности: длина события должна быть >= минимальной и не выходить за буфер.
{
break; // Если некорректно, прервать цикл (защита от повреждённых данных).
}
FanotifyEvent ev = FanotifyEvent(*meta); // Создание структуры события на основе meta (копирует данные).
// Парсинг дополнительной информации (info blocks): если флаги включают FAN_REPORT_FID/NAME, в буфере после meta идут блоки с FID, NAME и т.д.
size_t infoOffset = offset + fanotify_event_metadata.sizeof; // Смещение после meta.
while (infoOffset < offset + meta.event_len) // Цикл по блокам info внутри события.
{
auto hdr = cast(fanotify_event_info_header*)(buffer.ptr + infoOffset); // Кастинг в заголовок info (содержит type и len).
if (hdr.len == 0 || infoOffset + hdr.len > offset + meta.event_len) // Проверка валидности блока.
{
break; // Если некорректно, прервать.
}
if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) // Если тип - DFID + NAME (директория FID + имя).
{
// Расчёт смещения: пропускаем hdr, fsid (filesystem ID), затем file_handle.
size_t fidOffset = infoOffset + fanotify_event_info_header.sizeof + __kernel_fsid_t
.sizeof;
auto handle = cast(file_handle*)(buffer.ptr + fidOffset); // Кастинг в структуру file_handle (содержит handle_bytes - размер handle).
size_t handleEnd = fidOffset + file_handle.sizeof + handle.handle_bytes; // Конец handle в буфере.
if (handleEnd < offset + meta.event_len) // Проверка, что за handle есть место для имени.
{
// Извлечение имени: null-terminated C-строка после handle; конвертируем в string D.
ev.name = (cast(char*)(buffer.ptr + handleEnd)).fromStringz.to!string;
}
}
infoOffset += hdr.len; // Переход к следующему блоку info.
}
events ~= ev; // Добавление parsed события в массив.
offset += meta.event_len; // Переход к следующему событию в буфере.
}
return events; // Возврат массива событий.
}
// Свойство handle: возвращает дескриптор fd для низкоуровневого доступа (если нужно, например, для select или других вызовов).
@property int handle() const
{
return fd; // Просто геттер.
}
}