diff --git a/source/app.d b/source/app.d index cd0dc67..6a13c16 100644 --- a/source/app.d +++ b/source/app.d @@ -1,28 +1,15 @@ -// Модуль app.d -// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper. -// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.), -// затем в бесконечном цикле читает события и выводит информацию о них в консоль. -// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима), -// std.format для форматирования строк, core.sys.posix для констант. +import dfanotify; -import dfanotify; // Импорт обёртки для fanotify. - -import std.stdio; // Импорт для writeln, writefln (вывод в консоль). -import std.file : readLink; // Импорт readLink для чтения симлинков (не используется здесь, но оставлено для возможного расширения). -import std.format : format; // Импорт format для форматирования строк (хотя здесь используется writefln напрямую). -import core.sys.posix.fcntl : AT_FDCWD; // Импорт AT_FDCWD для текущей директории. -import core.sys.posix.unistd : close; // Импорт close (не используется здесь, но для возможного расширения с fd). +import std.stdio; +import std.file : readLink; +import std.format : format; +import core.sys.posix.fcntl : AT_FDCWD; +import core.sys.posix.unistd : close; import core.stdc.stdlib : exit; -// Функция main: точка входа приложения. void main() { Fanotify fan; - // Инициализация объекта Fanotify с флагами: - // FAN_CLASS_NOTIF - режим уведомлений (без контроля доступа), - // FAN_CLOEXEC - закрытие дескриптора при exec, - // FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME - отчёт с FID (идентификатор файла) вместо fd, плюс имя файла. - // Это позволяет получать имя без реального fd (fd будет FAN_NOFD). try { fan = new Fanotify( FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME); @@ -31,14 +18,8 @@ void main() exit(1); } - // Определение маски событий: битовая OR флагов для мониторинга. - // FAN_OPEN - открытие, FAN_MODIFY - модификация, FAN_CLOSE - закрытие (включает WRITE и NOWRITE), - // FAN_CREATE - создание, FAN_DELETE - удаление, FAN_EVENT_ON_CHILD - события в поддиректориях. auto eventMask = FAN_OPEN | FAN_MODIFY | FAN_CLOSE | FAN_CREATE | FAN_DELETE | FAN_EVENT_ON_CHILD; - // Маркировка директории /tmp/scripts: - // FAN_MARK_ADD - добавить марку, FAN_MARK_ONLYDIR - только для директории (ошибка, если не директория). - // AT_FDCWD - базовая директория текущая, путь "/tmp/scripts". try { fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, eventMask, AT_FDCWD, "/tmp/scripts"); } catch (Exception e) { @@ -46,37 +27,29 @@ void main() exit(1); } - writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга. + writeln("Мониторинг запущен для /tmp/scripts..."); - // Бесконечный цикл: постоянно читает события и обрабатывает их. while (true) { - auto events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий). + auto events = fan.readEvents(); - foreach (ref e; events) // Цикл по каждому событию в массиве. + foreach (ref e; events) { - // Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown". - // name - относительное имя файла/директории относительно маркированной. - string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное) + string path = e.name.length ? e.name : "unknown"; - // Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути. - // fd теперь FAN_NOFD, так что пропускаем readLink/close - - // Вывод общей информации о событии: маска в hex, PID, путь/имя. writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path); - // Проверки и вывод конкретных типов событий с использованием методов структуры. - if (e.isOpen) // Если открытие. + if (e.isOpen) writeln(" - Открытие файла"); - if (e.isModify) // Если модификация. + if (e.isModify) writeln(" - Модификация файла"); - if (e.isCloseWrite) // Если закрытие после записи. + if (e.isCloseWrite) writeln(" - Закрытие после записи"); - if (e.isCloseNoWrite) // Если закрытие без записи. + if (e.isCloseNoWrite) writeln(" - Закрытие без записи"); - if (e.isCreate) // Если создание. + if (e.isCreate) writeln(" - Создание файла/директории"); - if (e.isDelete) // Если удаление. + if (e.isDelete) writeln(" - Удаление файла/директории"); } } diff --git a/source/dfanotify.d b/source/dfanotify.d index 120272e..d678bc1 100644 --- a/source/dfanotify.d +++ b/source/dfanotify.d @@ -1,199 +1,154 @@ -// Модуль fanotify_wrapper.d -// Этот модуль предоставляет обёртку вокруг API fanotify для упрощения мониторинга событий файловой системы в Linux. -// Fanotify позволяет получать уведомления о действиях с файлами, такими как открытие, модификация, создание и удаление. -// Обёртка включает структуру для событий, класс для управления дескриптором и методы для инициализации, маркировки и чтения событий. -// Импорты: public import fanotify - предполагается, что это низкоуровневый модуль с определениями из . -// Другие импорты из core.sys.posix для системных вызовов (read, close и т.д.), std.exception для обработки ошибок, -// std.string и std.conv для работы со строками, core.stdc.errno для errno и strerror для детальных сообщений об ошибках, -// core.stdc.stdint для типов вроде uint64_t. - module dfanotify; -public import fanotify; // Импорт низкоуровневых определений fanotify (структуры, константы, функции вроде fanotify_init, fanotify_mark). +public import fanotify; -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 и т.п.). +import core.sys.posix.unistd : read, close, ssize_t; +import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD; +import std.exception : enforce; +import std.string : toStringz, fromStringz; +import std.conv : to; +import core.stdc.errno : errno; +import core.stdc.string : strerror; +import core.stdc.stdint; -// Структура 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 включено; парсится из дополнительной информации в буфере). + fanotify_event_metadata meta; + string name; - // Свойство mask: возвращает маску событий (битовая маска, где каждый бит соответствует типу события, например FAN_OPEN). @property uint64_t mask() const { - return meta.mask; // Просто возвращает значение из meta; const гарантирует, что структура не модифицируется. + return meta.mask; } - // Свойство eventFd: возвращает дескриптор файла события (fd). В режиме FAN_REPORT_FID это FAN_NOFD (-1), иначе реальный fd. @property int eventFd() const { - return meta.fd; // Доступ к fd из meta; полезно, если нужно работать с файлом напрямую (но в этом коде используется режим без fd). + return meta.fd; } - // Свойство pid: возвращает PID процесса, который вызвал событие. @property int pid() const { - return meta.pid; // Доступ к pid из meta; помогает идентифицировать, какой процесс взаимодействовал с файлом. + return meta.pid; } - // Метод isOpen: проверяет, включает ли маска событие открытия файла (FAN_OPEN). - // Использует битовую операцию & для проверки наличия бита FAN_OPEN в mask. bool isOpen() const { - return (mask & FAN_OPEN) != 0; // Если бит установлен, возвращает true; это стандартный способ работы с битoвыми масками. + return (mask & FAN_OPEN) != 0; } - // Метод isModify: проверяет событие модификации файла (FAN_MODIFY, например, запись в файл). bool isModify() const { - return (mask & FAN_MODIFY) != 0; // Аналогично, проверка бита FAN_MODIFY. + return (mask & FAN_MODIFY) != 0; } - // Метод isCloseWrite: проверяет закрытие файла после записи (FAN_CLOSE_WRITE). bool isCloseWrite() const { - return (mask & FAN_CLOSE_WRITE) != 0; // Проверка бита для закрытия с модификацией. + return (mask & FAN_CLOSE_WRITE) != 0; } - // Метод isCloseNoWrite: проверяет закрытие файла без записи (FAN_CLOSE_NOWRITE, например, после чтения). bool isCloseNoWrite() const { - return (mask & FAN_CLOSE_NOWRITE) != 0; // Проверка бита для закрытия без модификации. + return (mask & FAN_CLOSE_NOWRITE) != 0; } - // Метод isAccess: проверяет событие доступа (FAN_ACCESS, например, чтение). bool isAccess() const { - return (mask & FAN_ACCESS) != 0; // Проверка бита FAN_ACCESS. + return (mask & FAN_ACCESS) != 0; } - // Метод isCreate: проверяет создание файла или директории (FAN_CREATE). bool isCreate() const { - return (mask & FAN_CREATE) != 0; // Проверка бита для создания. + return (mask & FAN_CREATE) != 0; } - // Метод isDelete: проверяет удаление файла или директории (FAN_DELETE). bool isDelete() const { - return (mask & FAN_DELETE) != 0; // Проверка бита для удаления. + return (mask & FAN_DELETE) != 0; } } -// Класс Fanotify: основной класс для работы с fanotify. -// Управляет дескриптором (fd), инициализирует его, маркирует пути для мониторинга, читает события и закрывает дескриптор. -// Расширен для парсинга дополнительной информации (FID, NAME) в методе readEvents. -// Использует RAII: дескриптор закрывается в деструкторе автоматически. class Fanotify { - private int fd = -1; // Приватный дескриптор fanotify; инициализирован -1 (недействительный), чтобы избежать использования до инициализации. + private int fd = -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 упрощает обработку ошибок. + fd = fanotify_init(initFlags, eventFFlags); + enforce(fd >= 0, "Ошибка инициализации fanotify: " ~ to!string(fd)); } - // Деструктор: автоматически вызывается при уничтожении объекта. ~this() { - if (fd >= 0) // Проверка, валиден ли fd (чтобы избежать закрытия -1). + if (fd >= 0) { - close(fd); // Закрытие дескриптора через системный вызов close; освобождает ресурсы. - fd = -1; // Установка в -1 для безопасности (хотя объект уничтожается). + close(fd); + fd = -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 означает неудачу). + const(char)* cPath = path ? path.toStringz() : null; + int res = fanotify_mark(fd, markFlags, eventMask, dirFd, cPath); + if (res == -1) { - // Сборка детального сообщения об ошибке: включает res, errno и описание от strerror. string errMsg = "Ошибка маркировки fanotify: " ~ to!string( res) ~ " (errno: " ~ to!string(errno) ~ ", " ~ strerror(errno) .fromStringz.to!string ~ ")"; - throw new Exception(errMsg); // Бросить исключение для прерывания выполнения при ошибке. + 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) // Если ничего не прочитано или ошибка, вернуть пустой массив. + ubyte[] buffer = new ubyte[bufferSize]; + ssize_t len = read(fd, buffer.ptr, buffer.length); + if (len <= 0) { - return []; // Нет событий или ошибка (не бросаем исключение, чтобы цикл мог продолжаться). + return []; } - FanotifyEvent[] events; // Массив для собранных событий. - size_t offset = 0; // Смещение в буфере для парсинга (события идут подряд). - while (offset + FAN_EVENT_METADATA_LEN <= len) // Цикл по буферу: пока хватает места для минимальной структуры metadata. + FanotifyEvent[] events; + size_t offset = 0; + while (offset + FAN_EVENT_METADATA_LEN <= len) { - 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) // Проверка валидности: длина события должна быть >= минимальной и не выходить за буфер. + auto meta = cast(fanotify_event_metadata*)(buffer.ptr + offset); + if (meta.event_len < FAN_EVENT_METADATA_LEN || offset + meta.event_len > len) { - break; // Если некорректно, прервать цикл (защита от повреждённых данных). + break; } - FanotifyEvent ev = FanotifyEvent(*meta); // Создание структуры события на основе meta (копирует данные). + FanotifyEvent ev = FanotifyEvent(*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 внутри события. + size_t infoOffset = offset + fanotify_event_metadata.sizeof; + while (infoOffset < offset + meta.event_len) { - auto hdr = cast(fanotify_event_info_header*)(buffer.ptr + infoOffset); // Кастинг в заголовок info (содержит type и len). - if (hdr.len == 0 || infoOffset + hdr.len > offset + meta.event_len) // Проверка валидности блока. + auto hdr = cast(fanotify_event_info_header*)(buffer.ptr + infoOffset); + if (hdr.len == 0 || infoOffset + hdr.len > offset + meta.event_len) { - break; // Если некорректно, прервать. + break; } - if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) // Если тип - DFID + NAME (директория FID + имя). + if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) { - // Расчёт смещения: пропускаем 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 есть место для имени. + auto handle = cast(file_handle*)(buffer.ptr + fidOffset); + size_t handleEnd = fidOffset + file_handle.sizeof + handle.handle_bytes; + if (handleEnd < offset + meta.event_len) { - // Извлечение имени: null-terminated C-строка после handle; конвертируем в string D. ev.name = (cast(char*)(buffer.ptr + handleEnd)).fromStringz.to!string; } } - infoOffset += hdr.len; // Переход к следующему блоку info. + infoOffset += hdr.len; } - events ~= ev; // Добавление parsed события в массив. - offset += meta.event_len; // Переход к следующему событию в буфере. + events ~= ev; + offset += meta.event_len; } - return events; // Возврат массива событий. + return events; } - // Свойство handle: возвращает дескриптор fd для низкоуровневого доступа (если нужно, например, для select или других вызовов). @property int handle() const { - return fd; // Просто геттер. + return fd; } }