234 lines
7.3 KiB
D
234 lines
7.3 KiB
D
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;
|
||
}
|
||
}
|
||
}
|
||
}
|