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 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_; } }