diff --git a/.vscode/launch.json b/.vscode/launch.json index 35d0dcd..028b890 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/bin/dwatch", - "args": ["-d", "/tmp/scripts"], // Аргументы командной строки для программы, если нужны + "args": [], // Аргументы командной строки для программы, если нужны "stopAtEntry": false, // Остановить на входе в main "cwd": "${workspaceFolder}", "environment": [], diff --git a/c/main.c b/c/main.c deleted file mode 100644 index aaddeb6..0000000 --- a/c/main.c +++ /dev/null @@ -1,123 +0,0 @@ -// gcc -O2 -Wall -Wextra -o main main.c - -#define _GNU_SOURCE -#include // <-- прототипы fanotify_* -#include -#include -#include -#include -#include -#include -#include -#include -#include // PATH_MAX - -#ifndef AT_FDCWD -# include -#endif - -/* Настройки из твоего D-кода */ -#define INIT_FLAGS (FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK) -#define EVENT_FLAGS (O_RDONLY | O_LARGEFILE | O_CLOEXEC) -#define BASE_FLAGS (FAN_MARK_ADD) -#define PERM_MASK (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM | FAN_OPEN | FAN_CLOSE_WRITE) -#define BUF_SZ (64 * 1024) - -/* Простой лог */ -static void log_line(const char *s) { - struct iovec v[2]; - static const char nl = '\n'; - v[0].iov_base = (void*)s; v[0].iov_len = strlen(s); - v[1].iov_base = (void*)&nl; v[1].iov_len = 1; - writev(STDERR_FILENO, v, 2); -} - -static ssize_t fd_path(int fd, char *buf, size_t cap) { - char linkpath[64]; - int m = snprintf(linkpath, sizeof(linkpath), "/proc/self/fd/%d", fd); - if (m <= 0 || (size_t)m >= sizeof(linkpath)) return -1; - ssize_t r = readlink(linkpath, buf, cap - 1); - if (r >= 0) buf[r] = '\0'; - return r; -} - -int main(void) { - int fan = fanotify_init(INIT_FLAGS, EVENT_FLAGS); - if (fan == -1) { perror("fanotify_init"); return 1; } - - /* метим /tmp и /home как файловые системы */ - if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/tmp") == -1) { - perror("fanotify_mark(/tmp)"); return 1; - } - if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/home") == -1) { - perror("fanotify_mark(/home)"); return 1; - } - - int ep = epoll_create1(EPOLL_CLOEXEC); - if (ep == -1) { perror("epoll_create1"); return 1; } - - struct epoll_event ev = { .events = EPOLLIN | EPOLLONESHOT }; - ev.data.u64 = 1; /* тег */ - if (epoll_ctl(ep, EPOLL_CTL_ADD, fan, &ev) == -1) { perror("epoll_ctl ADD"); return 1; } - - char *buf = malloc(BUF_SZ); - if (!buf) { perror("malloc"); return 1; } - - log_line("fanomon: started (watching /tmp and /home)"); - - for (;;) { - struct epoll_event out; - int n = epoll_wait(ep, &out, 1, -1); - if (n == -1) { if (errno == EINTR) continue; perror("epoll_wait"); break; } - - if ((out.events & EPOLLIN) && out.data.u64 == 1) { - for (;;) { - ssize_t len = read(fan, buf, BUF_SZ); - if (len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) break; - if (len <= 0) break; - - struct fanotify_event_metadata *meta; - for (meta = (struct fanotify_event_metadata*)buf; - FAN_EVENT_OK(meta, len); - meta = FAN_EVENT_NEXT(meta, len)) { - - if (meta->vers != FANOTIFY_METADATA_VERSION) { log_line("metadata version mismatch"); continue; } - if (meta->mask & FAN_Q_OVERFLOW) { log_line("queue overflow"); continue; } - - /* Быстро отвечаем на PERM-события */ - if (meta->mask & (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM)) { - struct fanotify_response resp = { .fd = meta->fd, .response = FAN_ALLOW }; - if (write(fan, &resp, sizeof(resp)) == -1) perror("fanotify_response"); - } - - /* Лёгкий лог пути (после ответа) */ - if (meta->fd >= 0) { - char path[PATH_MAX]; - if (fd_path(meta->fd, path, sizeof(path)) >= 0) { - char line[PATH_MAX + 16]; - const char *tag = - (meta->mask & FAN_OPEN) ? "OPEN " : - (meta->mask & FAN_CLOSE_WRITE) ? "CLOSEW" : - (meta->mask & FAN_ACCESS_PERM) ? "APERM " : - (meta->mask & FAN_OPEN_PERM) ? "OPERM " : - (meta->mask & FAN_OPEN_EXEC_PERM) ? "XPERM " : "EVENT "; - snprintf(line, sizeof(line), "%s %s", tag, path); - log_line(line); - } - close(meta->fd); - } - } - } - - /* Реарм ONE_SHOT */ - ev.events = EPOLLIN | EPOLLONESHOT; - ev.data.u64 = 1; - if (epoll_ctl(ep, EPOLL_CTL_MOD, fan, &ev) == -1) { perror("epoll_ctl MOD"); break; } - } - } - - free(buf); - close(ep); - close(fan); - return 0; -} diff --git a/dub.json b/dub.json index 60ff96a..4cf8d3a 100644 --- a/dub.json +++ b/dub.json @@ -9,6 +9,9 @@ "targetPath": "bin", "targetType": "executable", "dependencies": { - "fanotify": "~>0.1.0" + "fanotify": { + "repository": "git+https://git.zhirov.kz/dlang/fanotify.git", + "version": "97edc0d795c93ef773ff60d260951e5ff6ae6215" + } } } \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json index 3de4f99..322586b 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,6 +1,5 @@ { "fileVersion": 1, "versions": { - "fanotify": "0.1.0" } } diff --git a/scheme.md b/scheme.md deleted file mode 100644 index 99e5fb7..0000000 --- a/scheme.md +++ /dev/null @@ -1,74 +0,0 @@ -```mermaid -sequenceDiagram -%%{init: { 'theme': 'default', 'themeVariables': { - 'primaryColor': '#ff0000', - 'nodeTextColor': '#ffffff', - 'edgeLabelBackground': '#f0f0f0' -}}}%% - autonumber - participant Watcher as Демон-наблюдатель - participant Kernel as Ядро (fanotify) - participant Editor as Процесс-редактор (PID X) - participant FS as Файловая система (целевой каталог/файл) - participant Ep as epoll - participant Pfd as pidfd(PID X) - participant Map as Карта соответствий (в памяти) - participant ChLog as Журнал изменений - - Note over Watcher: Инициализация - Watcher->>Kernel: fanotify_init(PRE_CONTENT|NONBLOCK|CLOEXEC, O_RDONLY|O_CLOEXEC|O_LARGEFILE) - Watcher->>Kernel: fanotify_mark(ADD|ONLYDIR, EVENT_ON_CHILD | OPEN[_EXEC]_PERM | OPEN | CLOSE_WRITE, watchDir) - Watcher->>Ep: add(fanotify_fd, tag=FAN) - - Note over Editor: Запрос доступа к файлу - Editor->>FS: open(target, O_RDONLY/O_RDWR/EXEC) - FS-->>Kernel: системный вызов доступа - Kernel-->>Watcher: PERM-событие (+fd, mask, pid) - - Note over Watcher: Сбор контекста - Watcher->>FS: fstat(fd) → (inode, dev) - FS-->>Watcher: inode, dev - Watcher->>Watcher: readlink(/proc/self/fd/FD) → file_path_open - Watcher->>Watcher: read /proc/PID/comm → proc_name - Watcher->>Watcher: read /proc/PID/status(Uid:) → RUID - Watcher->>Watcher: read /proc/PID/loginuid → UID(loginuid) - - %% Кладём связку - Watcher->>Map: PUT key=(PID,inode,dev), val={proc_name, RUID, UID, file_path_open, changed=false} - Note over Map: Добавлена связка key=(PID,inode,dev) - - %% pidfd и epoll - Watcher->>Pfd: pidfd_open(PID, flags=0) - Pfd-->>Watcher: pidfd - Watcher->>Ep: add(pidfd, tag=PID, events=IN|HUP|RDHUP|ERR) - - %% Разрешаем доступ - Watcher->>Kernel: fanotify_response{fd, FAN_ALLOW} - Watcher->>FS: close(fd) - - Note over Editor: Работа с файлом - Editor->>FS: write(...) - Editor->>FS: close() - Kernel-->>Watcher: CLOSE_WRITE (+fd) - - Note over Watcher: Фиксация изменения - Watcher->>FS: fstat(fd) → (inode, dev) - FS-->>Watcher: inode, dev - Watcher->>Map: GET key=(PID,inode,dev) - Map-->>Watcher: {…} - Watcher->>Map: SET changed=true - Watcher->>FS: close(fd) - - Note over Editor: Завершение процесса - Editor->>Editor: exit() - Pfd-->>Ep: EPOLLIN/HUP (процесс завершён) - Ep-->>Watcher: событие(tag=PID) - - %% Пишем журнал и чистим - Watcher->>Map: GET_ALL where pid=PID - Map-->>Watcher: список записей - Watcher->>ChLog: APPEND для changed=true → {proc=proc_name, file=file_path_open, uid=UID(loginuid), ruid=RUID} - Watcher->>Ep: remove(pidfd); Watcher->>Pfd: close(pidfd) - Watcher->>Map: DELETE_ALL where pid=PID - Note over Map: Удалены все связки для PID -``` diff --git a/source/app.d b/source/app.d index 911410c..2cc78c9 100644 --- a/source/app.d +++ b/source/app.d @@ -1,234 +1,71 @@ -public import fanotify; +// Модуль app.d +// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper. +// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.), +// затем в бесконечном цикле читает события и выводит информацию о них в консоль. +// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима), +// std.format для форматирования строк, core.sys.posix для констант. -import core.sys.posix.unistd : read, write, close, ssize_t; -import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD, O_CLOEXEC; -import std.exception : enforce, collectException; -import std.string : toStringz, fromStringz; -import std.conv : to; -import core.stdc.errno : errno, EINTR; -import core.stdc.string : strerror; -import core.stdc.stdint; -import std.stdio : writeln; -import std.format : format; -import std.file : readLink, FileException; +import fanotify_wrapper; // Импорт обёртки для fanotify. -string readlinkFdPath(int fd) +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). + +// Функция main: точка входа приложения. +void main() { - string link = "/proc/self/fd/%d".format(fd); - string path; - // Заглушка для FileException - collectException!FileException(path = link.readLink()); - return path; -} + // Инициализация объекта Fanotify с флагами: + // FAN_CLASS_NOTIF - режим уведомлений (без контроля доступа), + // FAN_CLOEXEC - закрытие дескриптора при exec, + // FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME - отчёт с FID (идентификатор файла) вместо fd, плюс имя файла. + // Это позволяет получать имя без реального fd (fd будет FAN_NOFD). + auto fan = new Fanotify( + FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME); -struct FanotifyMetadataRange { - private fanotify_event_metadata* current; // Current position in buffer. - private size_t remainingLen; // Remaining bytes in buffer from current. + // Определение маски событий: битовая 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; - // Private helper functions (now non-static const methods inside the class): - private @nogc nothrow @trusted - bool FAN_EVENT_OK(const(fanotify_event_metadata)* meta, size_t len) const { - enum long BASE = cast(long) FAN_EVENT_METADATA_LEN; - const long L = cast(long) len; - const long EL = cast(long) meta.event_len; - return (L >= BASE) && (EL >= BASE) && (EL <= L); - } + // Маркировка директории /tmp/scripts: + // FAN_MARK_ADD - добавить марку, FAN_MARK_ONLYDIR - только для директории (ошибка, если не директория). + // AT_FDCWD - базовая директория текущая, путь "/tmp/scripts". + fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, eventMask, AT_FDCWD, "/tmp/scripts"); - private @nogc nothrow @trusted - fanotify_event_metadata* FAN_EVENT_NEXT(fanotify_event_metadata* meta, ref size_t len) const { - const uint step = meta.event_len; - len -= step; - return cast(fanotify_event_metadata*)((cast(ubyte*) meta) + step); - } + writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга. - // Constructor: Takes the buffer slice and read length. - this(ubyte[] buffer, size_t len) @nogc nothrow @trusted { - if (len < FAN_EVENT_METADATA_LEN) { - remainingLen = 0; // Empty if too small. - return; - } - current = cast(fanotify_event_metadata*) buffer.ptr; - remainingLen = len; - } - - // Range primitives: - @nogc nothrow @trusted - bool empty() const { - return !FAN_EVENT_OK(current, remainingLen); - } - - @nogc nothrow @trusted - ref const(fanotify_event_metadata) front() const { - assert(!empty, "Range is empty"); - return *current; // Returns by const ref to avoid copies. - } - - @nogc nothrow @trusted - void popFront() { - assert(!empty, "Range is empty"); - current = FAN_EVENT_NEXT(current, remainingLen); - } -} - -// Класс для представления события fanotify (ООП-стиль, с методами для проверки и обработки) -struct FanotifyEvent -{ - private fanotify_event_metadata _meta; - private int _fanFd; - - // Деструктор: автоматически закрывает fd события, если он валиден (RAII) - // ~this() - // { - // if (_meta.fd >= 0 && _meta.fd != FAN_NOFD) - // { - // close(_meta.fd); - // } - // } - - // Геттеры (value types) - @property uint64_t mask() const { return _meta.mask; } - @property int eventFd() const { return _meta.fd; } - @property int pid() const { return _meta.pid; } - - // Методы проверки событий - bool isOpen() const { return (mask & FAN_OPEN) != 0; } - bool isModify() const { return (mask & FAN_MODIFY) != 0; } - bool isCloseWrite() const { return (mask & FAN_CLOSE_WRITE) != 0; } - bool isCloseNoWrite() const { return (mask & FAN_CLOSE_NOWRITE) != 0; } - bool isAccess() const { return (mask & FAN_ACCESS) != 0; } - bool isMoved() const { return (mask & FAN_MOVED_FROM) != 0; } - bool isCreate() const { return (mask & FAN_CREATE) != 0; } - bool isDelete() const { return (mask & FAN_DELETE) != 0; } - bool isOpenPerm() const { return (mask & FAN_OPEN_PERM) != 0; } - bool isAccessPerm() const { return (mask & FAN_ACCESS_PERM) != 0; } - bool isOpenExecPerm() const { return (mask & FAN_OPEN_EXEC_PERM) != 0; } - bool isOverflow() const { return (mask & FAN_Q_OVERFLOW) != 0; } - bool isFsError() const { return (mask & FAN_FS_ERROR) != 0; } - - // Метод для отправки response (для permission-событий), закрывает fd автоматически после - void respond(uint response) + // Бесконечный цикл: постоянно читает события и обрабатывает их. + while (true) { - if (eventFd < 0 || eventFd == FAN_NOFD) + auto events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий). + + foreach (ref e; events) // Цикл по каждому событию в массиве. { - return; // Нет fd для response - } + // Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown". + // name - относительное имя файла/директории относительно маркированной. + string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное) - fanotify_response resp; - resp.fd = eventFd; - resp.response = response; + // Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути. + // fd теперь FAN_NOFD, так что пропускаем readLink/close - ssize_t res = write(_fanFd, &resp, fanotify_response.sizeof); - enforce(res == fanotify_response.sizeof, "Ошибка записи response: " ~ strerror(errno) - .fromStringz.to!string); + // Вывод общей информации о событии: маска в hex, PID, путь/имя. + writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path); - // Закрываем fd сразу после response (не ждем деструктора, но деструктор на всякий случай) - close(_meta.fd); - _meta.fd = -1; - } -} - -// Основной ООП-класс для управления fanotify -class Fanotify -{ - private int _fd = -1; - - // Конструктор: инициализация с флагами - this(uint initFlags, uint eventFFlags = O_RDONLY | O_LARGEFILE) - { - _fd = fanotify_init(initFlags, eventFFlags); - enforce(_fd >= 0, "Ошибка инициализации fanotify: " ~ strerror(errno) - .fromStringz.to!string); - } - - // Деструктор: автоматически закрывает fanotify fd - ~this() - { - if (_fd >= 0) - { - close(_fd); - _fd = -1; - } - } - - // Метод для добавления/удаления/модификации меток (управление событиями) - void mark(uint markFlags, uint64_t eventMask, int dirFd = AT_FDCWD, string path = null) - { - const(char)* cPath = path ? path.toStringz() : null; - int res = fanotify_mark(_fd, markFlags, eventMask, dirFd, cPath); - enforce(res == 0, "Ошибка маркировки fanotify: " ~ strerror(errno) - .fromStringz.to!string); - } - - // Метод для чтения событий (возвращает массив объектов событий) - FanotifyEvent[] readEvents(size_t bufferSize = 4096) - { - size_t sz = bufferSize; - if (sz < FAN_EVENT_METADATA_LEN) sz = FAN_EVENT_METADATA_LEN; - - ubyte[] buffer = new ubyte[sz]; - - ssize_t len; - while (true) - { - len = read(_fd, buffer.ptr, buffer.length); - if (len < 0 && errno == EINTR) continue; - break; - } - if (len <= 0) return []; - - auto range = FanotifyMetadataRange(buffer, cast(size_t) len); - - import std.array : Appender; - auto events = Appender!(FanotifyEvent[])(); - // FanotifyEvent[] events; - foreach (ref const meta; range) { - events ~= FanotifyEvent(meta, _fd); - } - - return events.data; - } - - // Геттер для fd (если нужно внешне, но лучше использовать методы) - @property int handle() const - { - return _fd; - } -} - -ulong dirMask() -{ - return FAN_EVENT_ON_CHILD | - FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM | FAN_ACCESS_PERM | - FAN_OPEN | FAN_CLOSE_WRITE; -} - -void main(string[] args) -{ - enum initFlags = FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK; - auto fan = new Fanotify(initFlags, O_RDONLY | O_LARGEFILE | O_CLOEXEC); - - // fan.mark(FAN_MARK_ADD | FAN_MARK_FILESYSTEM, dirMask(), AT_FDCWD, "/home"); - fan.mark(FAN_MARK_ADD | FAN_MARK_FILESYSTEM, dirMask(), AT_FDCWD, "/tmp"); - - for (;;) - { - auto list = fan.readEvents(8_192); - foreach (fev; list) - { - if (fev.isOverflow) - { - return; - } - if (fev.isOpenPerm || fev.isAccessPerm || fev.isOpenExecPerm) - { - writeln(fev.eventFd.readlinkFdPath); - fev.respond(FAN_ALLOW); - continue; - } - if (fev.isCloseWrite) - { - continue; - } + // Проверки и вывод конкретных типов событий с использованием методов структуры. + if (e.isOpen) // Если открытие. + writeln(" - Открытие файла"); + if (e.isModify) // Если модификация. + writeln(" - Модификация файла"); + if (e.isCloseWrite) // Если закрытие после записи. + writeln(" - Закрытие после записи"); + if (e.isCloseNoWrite) // Если закрытие без записи. + writeln(" - Закрытие без записи"); + if (e.isCreate) // Если создание. + writeln(" - Создание файла/директории"); + if (e.isDelete) // Если удаление. + writeln(" - Удаление файла/директории"); } } } diff --git a/source/fanotify_wrapper.d b/source/fanotify_wrapper.d new file mode 100644 index 0000000..067373f --- /dev/null +++ b/source/fanotify_wrapper.d @@ -0,0 +1,200 @@ +// Модуль 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 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; // Просто геттер. + } +}