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, 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; string readlinkFdPath(int fd) { string link = "/proc/self/fd/%d".format(fd); string path; // Заглушка для FileException collectException!FileException(path = link.readLink()); return path; } struct FanotifyMetadataRange { private fanotify_event_metadata* current; // Current position in buffer. private size_t remainingLen; // Remaining bytes in buffer from current. // 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); } 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); } // 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) { 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) { 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; } } } }