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/source/app.d b/source/app.d index 6b67df9..2cc78c9 100644 --- a/source/app.d +++ b/source/app.d @@ -1,357 +1,71 @@ -module app; +// Модуль app.d +// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper. +// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.), +// затем в бесконечном цикле читает события и выводит информацию о них в консоль. +// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима), +// std.format для форматирования строк, core.sys.posix для констант. -import dfanotify; // твой модуль сверху -import depoll; // новый модуль для epoll -import event_actor; +import fanotify_wrapper; // Импорт обёртки для fanotify. -import core.sys.posix.fcntl : O_RDONLY, O_LARGEFILE; -import core.stdc.errno : errno; -import core.stdc.string : strerror; -import std.string : fromStringz, toStringz, join, strip; -import std.conv : to; -import std.stdio : writeln, writefln; -import std.getopt; -import std.algorithm : canFind; -import core.sys.posix.unistd : readlink; +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 core.stdc.stdint; -import std.exception; - -import std.path : baseName; - -import std.file : readText, exists, read; - -/// Вернуть путь для fd из события PRE_CONTENT. -/// Может вернуть "(deleted)" или пустую строку, если объект без имени. -string pathFromEventFd(int fd) +// Функция main: точка входа приложения. +void main() { - // Конструируем путь в /proc/self/fd - auto linkPath = "/proc/self/fd/" ~ fd.to!string; - char[4096] buf; + // Инициализация объекта 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); - // readlink не добавляет '\0' - auto n = readlink(linkPath.toStringz, buf.ptr, buf.length); - if (n < 0) - { - return ""; - } - return cast(string) buf[0 .. n].idup; -} + // Определение маски событий: битовая 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; -/// Возвращает "имя процесса" для события fanotify. -/// Приоритет: /proc//comm → имя ссылки /proc//exe → первый токен из cmdline. -/// Если всё сломалось — "pid:". -string eventProcessName(int pid) -{ - // 1) /proc//comm — самое точное короткое имя - const commPath = "/proc/" ~ pid.to!string ~ "/comm"; - try - { - if (exists(commPath)) - { - auto s = readText(commPath).strip; // там обычно одна строка с \n - if (s.length) - return s; - } - } - catch (Exception e) - { - } + // Маркировка директории /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"); - // 2) /proc//exe — полный путь к исполняемому файлу - try - { - char[4096] buf; - auto link = "/proc/" ~ pid.to!string ~ "/exe"; - auto n = readlink(link.toStringz, buf.ptr, buf.length); - if (n > 0) - { - auto exePath = cast(string) buf[0 .. n].idup; - auto name = baseName(exePath); - if (name.length) - return name; - } - } - catch (Exception e) - { - } - - // 3) /proc//cmdline — первый NUL-разделённый аргумент - try - { - const cmdPath = "/proc/" ~ pid.to!string ~ "/cmdline"; - if (exists(cmdPath)) - { - auto bytes = cast(const(ubyte)[]) read(cmdPath); - if (bytes.length) - { - // до первого \0 - size_t i = 0; - for (; i < bytes.length && bytes[i] != 0; ++i) - { - } - auto first = cast(string) bytes[0 .. i].idup; - if (first.length) - { - auto name = baseName(first); - if (name.length) - return name; - } - } - } - } - catch (Exception e) - { - } - - // fallback - return "pid:" ~ pid.to!string; -} - -// Удобная печать маски (для живых логов) -string maskToStr(uint64_t m) -{ - string[] parts; - if (m & FAN_OPEN) - parts ~= "OPEN"; - if (m & FAN_ACCESS) - parts ~= "ACCESS"; - if (m & FAN_MODIFY) - parts ~= "MODIFY"; - if (m & FAN_CLOSE_WRITE) - parts ~= "CLOSE_WRITE"; - if (m & FAN_CLOSE_NOWRITE) - parts ~= "CLOSE_NOWRITE"; - if (m & FAN_CREATE) - parts ~= "CREATE"; - if (m & FAN_DELETE) - parts ~= "DELETE"; - if (m & FAN_MOVED_FROM) - parts ~= "MOVED_FROM"; - if (m & FAN_MOVED_TO) - parts ~= "MOVED_TO"; - if (m & FAN_OPEN_PERM) - parts ~= "OPEN_PERM"; - if (m & FAN_ACCESS_PERM) - parts ~= "ACCESS_PERM"; - if (m & FAN_OPEN_EXEC) - parts ~= "OPEN_EXEC"; - if (m & FAN_OPEN_EXEC_PERM) - parts ~= "OPEN_EXEC_PERM"; - if (m & FAN_Q_OVERFLOW) - parts ~= "Q_OVERFLOW"; - if (m & FAN_FS_ERROR) - parts ~= "FS_ERROR"; - return parts.length ? parts.join("|") : "0"; -} - -void main(string[] args) -{ - string watchPath = "/tmp/scripts"; - bool markMount = false; - - getopt(args, - "path|p", &watchPath, - "mount", &markMount, - ); - - writefln("[*] Watch: %s (mode: %s)", watchPath, markMount ? "MOUNT" : "DIR"); - - Fanotify pre, notif; - - // --------------------------- - // 1) Создаём две группы - // --------------------------- - - // PRE_CONTENT: решаем "до доступа" - // Важно: для permission-событий обязательно включить REPORT_* для имени - // и DFID (иначе имя не извлечёшь). - uint preInitFlags = - FAN_CLASS_PRE_CONTENT - | FAN_CLOEXEC - | FAN_NONBLOCK; - - try - { - pre = new Fanotify(preInitFlags, O_RDONLY | O_LARGEFILE); - } - catch (Exception e) - { - writeln("pre: ", e.msg); - } - - // NOTIF: обычные уведомления (после факта), CLOSE_WRITE и т.п. - uint notifInitFlags = - FAN_CLASS_NOTIF - | FAN_CLOEXEC - | FAN_NONBLOCK - | FAN_REPORT_FID - | FAN_REPORT_DIR_FID - | FAN_REPORT_NAME; - - try - { - notif = new Fanotify(notifInitFlags, O_RDONLY | O_LARGEFILE); - } - catch (Exception e) - { - writeln("notif", e.msg); - } - - // --------------------------- - // 2) Ставим метки - // --------------------------- - - // Что именно хотим ловить - enum uint64_t PERM_MASK = - FAN_OPEN_PERM - | FAN_ACCESS_PERM - | FAN_OPEN_EXEC_PERM; - - enum uint64_t NOTIF_MASK = - FAN_OPEN - | FAN_OPEN_EXEC - | FAN_MODIFY - | FAN_CLOSE_WRITE - | FAN_CLOSE_NOWRITE - | FAN_CREATE - | FAN_DELETE - | FAN_MOVED_FROM - | FAN_MOVED_TO; - - // Важно: для слежения за потомками каталога добавляем FAN_EVENT_ON_CHILD. - auto preMask = PERM_MASK | FAN_EVENT_ON_CHILD; - auto notifMask = NOTIF_MASK | FAN_EVENT_ON_CHILD; - - uint baseFlags = FAN_MARK_ADD /*| FAN_MARK_ONLYDIR*/ ; - - // Если нужно «видеть всё на файловой системе/монтировании», используй MOUNT: - // (на многих сценариях это предпочтительно, иначе будет только каталог + его прямые дети) - if (markMount) - { - baseFlags |= FAN_MARK_MOUNT; - } - - // Метки на обе группы - pre.mark(baseFlags, preMask, /*dirFd*/ 0, watchPath); - notif.mark(baseFlags, notifMask, /*dirFd*/ 0, watchPath); - - // --------------------------- - // 3) Готовим epoll (теперь через ООП-класс) - // --------------------------- - - Epoll ep = new Epoll(); - - enum uint TAG_PRE = 1; - enum uint TAG_NOTIF = 2; - - ep.add(pre.handle, TAG_PRE); - ep.add(notif.handle, TAG_NOTIF); - - // --------------------------- - // 4) Главный цикл - // --------------------------- + writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга. + // Бесконечный цикл: постоянно читает события и обрабатывает их. while (true) { - auto epEvents = ep.wait(); + auto events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий). - foreach (epEv; epEvents) + foreach (ref e; events) // Цикл по каждому событию в массиве. { - auto tag = epEv.tag; + // Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown". + // name - относительное имя файла/директории относительно маркированной. + string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное) - // Считываем пачку событий с соответствующего fd - FanotifyEvent[] events; - if (tag == TAG_PRE) - { - events = pre.readEvents(64 * 1024); - } - else if (tag == TAG_NOTIF) - { - events = notif.readEvents(64 * 1024); - } - else - { - continue; - } + // Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути. + // fd теперь FAN_NOFD, так что пропускаем readLink/close - foreach (ev; events) - { - auto pname = eventProcessName(ev.pid); - // writeln("pid = ", ev.pid, "\tproc = ", pname, "\tmask = ", maskToStr(ev.mask)); - writefln("[%s] pid = %s proc = %s mask = %s", - (tag == TAG_PRE) ? "PRE" : "NOTIF", - ev.pid, pname, - maskToStr(ev.mask) - ); - auto actor = eventActorFromPid(ev.pid); - writeln("user=", actor.user, " uid=", actor.uid, - " ruid=", actor.ruid, "(", actor.ruser, ")", - " euid=", actor.euid, "(", actor.euser, ")"); + // Вывод общей информации о событии: маска в hex, PID, путь/имя. + writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path); - if (tag == TAG_PRE && (ev.isOpenPerm || ev.isAccessPerm)) - { - string path = pathFromEventFd(ev.eventFd); - writeln("Попытка доступа к: ", path.length ? path - : "<неизвестно>"); - // ev.respond(FAN_ALLOW); // или FAN_DENY по своей политике - baseName(path) == "test" ? ev.respond(FAN_DENY) : ev.respond(FAN_ALLOW); - } - - // Лог - // writefln("[%s] pid=%s mask=%s name=%s", - // (tag == TAG_PRE) ? "PRE" : "NOTIF", - // ev.pid.to!string, - // maskToStr(ev.mask), - // ev.name.length ? ev.name : "(unknown)"); - - // --- ВЕТКА РЕШЕНИЯ ДЛЯ PERM-СОБЫТИЙ --- - // if (tag == TAG_PRE && - // (ev.isOpenPerm || ev.isAccessPerm || ev.isOpenExecPerm)) - // { - // // Пример «политики»: запрещать выполнение из временных директорий - // // (поменяй на свои условия) - // bool deny = false; - - // // простейший сэмпл-правил - // auto nm = ev.name; - // if (nm.length) - // { - // if (nm.canFind("/tmp/") && ev.isOpenExecPerm) - // { - // deny = true; - // } - // } - - // // Отправляем решение ядру - // ev.respond(deny ? FAN_DENY : FAN_ALLOW); - - // writefln(" -> %s", deny ? "DENY" : "ALLOW"); - // } - - // --- ПОСТ-ФАКТУМ УВЕДОМЛЕНИЯ --- - if (tag == TAG_NOTIF) - { - // Реакции в реальном времени - if (ev.isCloseWrite) - { - // Напр., дернуть диф/бэкап/индексацию - // snag --config ... create --comment "CLOSE_WRITE - " ... - writefln(" -> HANDLE CLOSE_WRITE for: %s", ev.name); - } - else if (ev.isMoved) - { - writefln(" -> HANDLE MOVED for: %s", ev.name); - } - else if (ev.isModify) - { - writefln(" -> HANDLE MODIFY for: %s", ev.name); - } - } - - // Кастомный postProcess если надо - ev.postProcess(); - } + // Проверки и вывод конкретных типов событий с использованием методов структуры. + 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/depoll.d b/source/depoll.d deleted file mode 100644 index 8b85272..0000000 --- a/source/depoll.d +++ /dev/null @@ -1,73 +0,0 @@ -module depoll; - -import core.sys.linux.epoll; -import core.sys.posix.unistd : close; -import core.stdc.errno : errno, EINTR; -import core.stdc.string : strerror; -import std.string : fromStringz; -import std.exception : enforce; - -class Epoll -{ - private int epfd_ = -1; - - this(int flags = 0) - { - epfd_ = epoll_create1(flags); - enforce(epfd_ >= 0, "epoll_create1: " ~ strerror(errno).fromStringz.idup); - } - - ~this() - { - if (epfd_ >= 0) - { - close(epfd_); - epfd_ = -1; - } - } - - void add(int fd, uint tag, uint events = EPOLLIN | EPOLLERR | EPOLLHUP) - { - epoll_event ev; - ev.events = events; - ev.data.u32 = tag; - enforce(epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev) == 0, - "epoll_ctl ADD: " ~ strerror(errno).fromStringz.idup); - } - - // Опционально: метод для удаления fd - void remove(int fd) - { - enforce(epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, null) == 0, - "epoll_ctl DEL: " ~ strerror(errno).fromStringz.idup); - } - - struct Event - { - uint tag; - uint events; - } - - Event[] wait(int maxevents = 16, int timeout = -1) - { - epoll_event[] evs = new epoll_event[maxevents]; - int n = epoll_wait(epfd_, evs.ptr, maxevents, timeout); - if (n <= 0) - { - // Игнорируем EINTR и другие (как в оригинале: continue) - return []; - } - - Event[] res; - foreach (i; 0 .. n) - { - res ~= Event(evs[i].data.u32, evs[i].events); - } - return res; - } - - @property int handle() const - { - return epfd_; - } -} diff --git a/source/dfanotify.d b/source/dfanotify.d deleted file mode 100644 index b4d4d30..0000000 --- a/source/dfanotify.d +++ /dev/null @@ -1,252 +0,0 @@ -module dfanotify; - -public import fanotify; - -import core.sys.posix.unistd : read, write, 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; - -// Класс для представления события fanotify (ООП-стиль, с методами для проверки и обработки) -class FanotifyEvent -{ - private fanotify_event_metadata meta_; - private string name_; - private int fanFd_; // Ссылка на fanotify fd для отправки response (копируется при создании) - - // Конструктор (value semantics, копирует данные) - this(fanotify_event_metadata meta, string name, int fanFd) - { - meta_ = meta; - name_ = name; - fanFd_ = fanFd; - } - - // Деструктор: автоматически закрывает fd события, если он валиден (RAII) - ~this() - { - if (meta_.fd >= 0 && meta_.fd != FAN_NOFD) - { - close(meta_.fd); - meta_.fd = -1; // Избегаем повторного закрытия - } - } - - // Геттеры (value types) - @property uint64_t mask() const - { - return meta_.mask; - } - - @property int eventFd() const - { - return meta_.fd; - } - - @property int pid() const - { - return meta_.pid; - } - - @property string name() const - { - return name_; - } - - // Методы проверки событий (без ref) - 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; - } - - // Метод для постобработки события (виртуальный, можно override для кастомной логики) - void postProcess() - { - // По умолчанию ничего, но можно добавить логику, например, логирование - } - - // Метод для отправки response (для permission-событий), закрывает fd автоматически после - void respond(uint response) - { - if (eventFd < 0 || eventFd == FAN_NOFD) - { - return; // Нет fd для response - } - - fanotify_response resp; - resp.fd = eventFd; - resp.response = response; - - ssize_t res = write(fanFd_, &resp, fanotify_response.sizeof); - enforce(res == fanotify_response.sizeof, "Ошибка записи response: " ~ strerror(errno) - .fromStringz.to!string); - - // Закрываем 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) - { - ubyte[] buffer = new ubyte[bufferSize]; - ssize_t len = read(fd_, buffer.ptr, buffer.length); - if (len <= 0) - { - return []; - } - - FanotifyEvent[] events; - size_t offset = 0; - while (offset + FAN_EVENT_METADATA_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; - } - - string name; - size_t infoOffset = offset + fanotify_event_metadata.sizeof; - while (infoOffset < 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; - } - - if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME || - hdr.info_type == FAN_EVENT_INFO_TYPE_OLD_DFID_NAME || - hdr.info_type == FAN_EVENT_INFO_TYPE_NEW_DFID_NAME) - { - size_t fidOffset = infoOffset + fanotify_event_info_header.sizeof + __kernel_fsid_t.sizeof; - auto handle = *(cast(file_handle*)(buffer.ptr + fidOffset)); - size_t handleEnd = fidOffset + file_handle.sizeof + handle.handle_bytes; - if (handleEnd < offset + meta.event_len) - { - name = (cast(char*)(buffer.ptr + handleEnd)).fromStringz.to!string; - } - } - infoOffset += hdr.len; - } - - auto ev = new FanotifyEvent(meta, name, fd_); - events ~= ev; - offset += meta.event_len; - } - return events; - } - - // Метод для постобработки всех событий (вызывает postProcess на каждом) - void postProcessEvents(FanotifyEvent[] events) - { - foreach (ev; events) - { - ev.postProcess(); - } - } - - // Геттер для fd (если нужно внешне, но лучше использовать методы) - @property int handle() const - { - return fd_; - } -} diff --git a/source/event_actor.d b/source/event_actor.d deleted file mode 100644 index 5ac27de..0000000 --- a/source/event_actor.d +++ /dev/null @@ -1,139 +0,0 @@ -module event_actor; - -import core.sys.posix.pwd : passwd, getpwuid_r; -import core.sys.posix.sys.types : uid_t; -import core.stdc.stdlib : malloc, free; -import core.stdc.string : strlen; -import std.file : readText, exists; -import std.string : splitLines, strip; -import std.conv : to; - -// для токенизации по пробельным -import std.ascii : isWhite; -import std.algorithm.iteration : splitter; -import std.array : array; - -struct EventActor -{ - uid_t uid; // выбранный UID (loginuid если есть, иначе ruid) - string user; // имя пользователя для uid (может быть пустым) - uid_t ruid, euid, suid, fsuid; - string ruser, euser, suser, fsuser; - bool hasLoginuid; - uid_t loginuid; - string loginuser; -} - -private string userName(uid_t uid) -{ - enum BUF = 4096; - auto buf = cast(char*) malloc(BUF); - scope (exit) - if (buf) - free(buf); - passwd pwd; - passwd* outp; - auto rc = getpwuid_r(uid, &pwd, buf, BUF, &outp); - if (rc == 0 && outp !is null) - { - return pwd.pw_name[0 .. strlen(pwd.pw_name)].idup; - } - return ""; -} - -/// Парсит строку вида: "Uid:\t\t\t\t" -private bool parseUidLine(string line, out uid_t r, out uid_t e, out uid_t s, out uid_t f) -{ - // разобьём по любым пробельным символам - auto toks = line.splitter!isWhite.array; // ["Uid:", "1000", "1000", "1000", "1000"] - if (toks.length >= 5 && toks[0] == "Uid:") - { - try - { - r = toks[1].to!uid_t; - e = toks[2].to!uid_t; - s = toks[3].to!uid_t; - f = toks[4].to!uid_t; - return true; - } - catch (Exception e) - { - } - } - return false; -} - -/// Главная функция: кто инициировал событие (по PID из fanotify_event_metadata) -EventActor eventActorFromPid(int pid) -{ - EventActor a; - if (pid <= 0) - return a; - - const base = "/proc/" ~ pid.to!string ~ "/"; - const statusPath = base ~ "status"; - - if (exists(statusPath)) - { - try - { - auto txt = readText(statusPath); - uid_t r = 0, e = 0, s = 0, f = 0; - foreach (line; txt.splitLines()) - { - // ищем строку "Uid:" - if (line.length >= 4 && line[0 .. 4] == "Uid:") - { - if (parseUidLine(line, r, e, s, f)) - { - a.ruid = r; - a.euid = e; - a.suid = s; - a.fsuid = f; - a.ruser = userName(r); - a.euser = userName(e); - a.suser = userName(s); - a.fsuser = userName(f); - } - break; - } - } - } - catch (Exception e) - { - } - } - - // loginuid — владелец сессии (если audit включён) - const loginPath = base ~ "loginuid"; - if (exists(loginPath)) - { - try - { - auto s = readText(loginPath).strip; - if (s.length) - { - a.loginuid = s.to!uid_t; - a.loginuser = userName(a.loginuid); - a.hasLoginuid = true; - } - } - catch (Exception e) - { - } - } - - // выбираем «итогового» пользователя - if (a.hasLoginuid) - { - a.uid = a.loginuid; - a.user = a.loginuser.length ? a.loginuser : ("uid:" ~ a.loginuid.to!string); - } - else - { - a.uid = a.ruid; - a.user = a.ruser.length ? a.ruser : ("uid:" ~ a.ruid.to!string); - } - - return a; -} 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; // Просто геттер. + } +}