Compare commits
11 commits
master
...
dev-scheme
Author | SHA1 | Date | |
---|---|---|---|
de0518f674 | |||
7f54a56ef5 | |||
aa214dcf96 | |||
377d5c1f94 | |||
2876a45a85 | |||
bc02ac8518 | |||
1f78d8eb83 | |||
7858416df9 | |||
cb3f40deee | |||
ed0d2780c3 | |||
582ae5a294 |
8 changed files with 808 additions and 259 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -20,7 +20,7 @@
|
|||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/dwatch",
|
||||
"args": [], // Аргументы командной строки для программы, если нужны
|
||||
"args": ["-d", "/tmp/scripts"], // Аргументы командной строки для программы, если нужны
|
||||
"stopAtEntry": false, // Остановить на входе в main
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
|
|
10
dub.json
10
dub.json
|
@ -8,10 +8,14 @@
|
|||
"name": "dwatch",
|
||||
"targetPath": "bin",
|
||||
"targetType": "executable",
|
||||
"libs": [
|
||||
"xdiff"
|
||||
],
|
||||
"dependencies": {
|
||||
"fanotify": {
|
||||
"repository": "git+https://git.zhirov.kz/dlang/fanotify.git",
|
||||
"version": "97edc0d795c93ef773ff60d260951e5ff6ae6215"
|
||||
"fanotify": "~>0.1.0",
|
||||
"sdiff": {
|
||||
"repository": "git+https://git.zhirov.kz/dlang/sdiff.git",
|
||||
"version": "8c388b112360a9f5bf89b9aa12b1977b8967b219"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"fanotify": "0.1.0",
|
||||
"sdiff": {"version":"8c388b112360a9f5bf89b9aa12b1977b8967b219","repository":"git+https://git.zhirov.kz/dlang/sdiff.git"},
|
||||
"xdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"}
|
||||
}
|
||||
}
|
||||
|
|
74
scheme.md
Normal file
74
scheme.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
```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
|
||||
```
|
442
source/app.d
442
source/app.d
|
@ -1,71 +1,401 @@
|
|||
// Модуль app.d
|
||||
// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper.
|
||||
// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.),
|
||||
// затем в бесконечном цикле читает события и выводит информацию о них в консоль.
|
||||
// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима),
|
||||
// std.format для форматирования строк, core.sys.posix для констант.
|
||||
module app;
|
||||
|
||||
import fanotify_wrapper; // Импорт обёртки для fanotify.
|
||||
import dfanotify;
|
||||
import fanotify;
|
||||
import depoll;
|
||||
|
||||
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.sys.posix.fcntl : O_RDONLY, O_LARGEFILE, O_CLOEXEC, AT_FDCWD;
|
||||
import core.sys.posix.unistd : read, close;
|
||||
import core.sys.posix.sys.stat : fstat, stat_t;
|
||||
import core.stdc.errno : errno, EINTR;
|
||||
import core.stdc.string : strerror;
|
||||
import std.stdio : writeln, writefln, stderr;
|
||||
import std.file : isDir, readText, exists, read;
|
||||
import std.string : strip, splitLines, startsWith, toStringz, fromStringz, split;
|
||||
import std.conv : to;
|
||||
import std.exception : enforce;
|
||||
import std.getopt : getopt, defaultGetoptPrinter;
|
||||
import std.format : format;
|
||||
import core.sys.linux.epoll : EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLRDHUP, EPOLL_CLOEXEC;
|
||||
|
||||
// Функция main: точка входа приложения.
|
||||
void main()
|
||||
// Переписать
|
||||
import core.sys.posix.sys.types : uid_t;
|
||||
import core.sys.posix.pwd : passwd, getpwuid_r;
|
||||
import core.stdc.stdlib : malloc, free;
|
||||
import core.stdc.string : strlen;
|
||||
|
||||
/// ---- syscall + pidfd_open ----
|
||||
extern (C) long syscall(long number, ...);
|
||||
version (X86_64) enum __NR_pidfd_open = 434;
|
||||
extern (C) int pidfd_open(int pid, uint flags)
|
||||
{
|
||||
// Инициализация объекта 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);
|
||||
return cast(int) syscall(__NR_pidfd_open, pid, flags);
|
||||
}
|
||||
|
||||
// Определение маски событий: битовая 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;
|
||||
/// ---- readlink по fd ----
|
||||
extern (C) long readlink(const char* path, char* buf, size_t bufsiz);
|
||||
string readlinkFdPath(int fd)
|
||||
{
|
||||
char[4_096] buf;
|
||||
auto link = format("/proc/self/fd/%d", fd);
|
||||
auto n = readlink(link.toStringz, buf.ptr, buf.length.to!int - 1);
|
||||
if (n <= 0)
|
||||
return "";
|
||||
buf[n] = 0;
|
||||
return buf.ptr.fromStringz.idup.strip;
|
||||
}
|
||||
|
||||
// Маркировка директории /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");
|
||||
/// ---- имя процесса и UIDы (ruid из status, uid из loginuid) ----
|
||||
struct ProcIds
|
||||
{
|
||||
uint ruid;
|
||||
uint uid;
|
||||
} // uid = loginuid
|
||||
|
||||
writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга.
|
||||
|
||||
// Бесконечный цикл: постоянно читает события и обрабатывает их.
|
||||
while (true)
|
||||
string readProcComm(int pid)
|
||||
{
|
||||
auto p = format("/proc/%s/comm", pid);
|
||||
string s;
|
||||
try
|
||||
{
|
||||
auto events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий).
|
||||
|
||||
foreach (ref e; events) // Цикл по каждому событию в массиве.
|
||||
s = readText(p);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown".
|
||||
// name - относительное имя файла/директории относительно маркированной.
|
||||
string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное)
|
||||
return "";
|
||||
}
|
||||
return s.strip;
|
||||
}
|
||||
|
||||
// Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути.
|
||||
// fd теперь FAN_NOFD, так что пропускаем readLink/close
|
||||
uint readLoginUid(int pid)
|
||||
{
|
||||
auto p = format("/proc/%s/loginuid", pid);
|
||||
try
|
||||
{
|
||||
auto s = readText(p).strip;
|
||||
// loginuid может быть "-1" (unset). В этом случае вернём 4294967295 или 0 — выбери политику.
|
||||
if (s.length && s[0] == '-')
|
||||
return uint.max; // помечаем как "нет"
|
||||
return s.to!uint;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return uint.max; // нет файла или отказ — считаем не установленным
|
||||
}
|
||||
}
|
||||
|
||||
// Вывод общей информации о событии: маска в hex, PID, путь/имя.
|
||||
writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path);
|
||||
ProcIds readProcIds(int pid)
|
||||
{
|
||||
uint ruid = 0;
|
||||
auto p = format("/proc/%s/status", pid);
|
||||
string s;
|
||||
try
|
||||
{
|
||||
s = readText(p);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return ProcIds(0, readLoginUid(pid));
|
||||
}
|
||||
foreach (line; s.splitLines)
|
||||
{
|
||||
if (line.startsWith("Uid:"))
|
||||
{
|
||||
// Uid: RUID EUID SUID FSUID
|
||||
auto parts = line.split;
|
||||
if (parts.length >= 5)
|
||||
{
|
||||
ruid = parts[1].to!uint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ProcIds(ruid, readLoginUid(pid));
|
||||
}
|
||||
|
||||
// Проверки и вывод конкретных типов событий с использованием методов структуры.
|
||||
if (e.isOpen) // Если открытие.
|
||||
writeln(" - Открытие файла");
|
||||
if (e.isModify) // Если модификация.
|
||||
writeln(" - Модификация файла");
|
||||
if (e.isCloseWrite) // Если закрытие после записи.
|
||||
writeln(" - Закрытие после записи");
|
||||
if (e.isCloseNoWrite) // Если закрытие без записи.
|
||||
writeln(" - Закрытие без записи");
|
||||
if (e.isCreate) // Если создание.
|
||||
writeln(" - Создание файла/директории");
|
||||
if (e.isDelete) // Если удаление.
|
||||
writeln(" - Удаление файла/директории");
|
||||
/// ---- ключ карты соответствий: (pid, dev, ino) ----
|
||||
struct DevIno
|
||||
{
|
||||
ulong dev;
|
||||
ulong ino;
|
||||
int pid;
|
||||
bool opEquals(const DevIno rhs) const @safe nothrow
|
||||
{
|
||||
return dev == rhs.dev && ino == rhs.ino && pid == rhs.pid;
|
||||
}
|
||||
|
||||
size_t toHash() const @safe nothrow
|
||||
{
|
||||
return (dev * 1_315_423_911UL) ^ (ino * 2_654_435_761UL) ^ (cast(size_t) pid);
|
||||
}
|
||||
}
|
||||
|
||||
bool getDevIno(int fd, out DevIno di, int pid)
|
||||
{
|
||||
stat_t st;
|
||||
if (fstat(fd, &st) != 0)
|
||||
return false;
|
||||
di = DevIno(cast(ulong) st.st_dev, cast(ulong) st.st_ino, pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// ---- сессия файла для конкретного PID ----
|
||||
struct Session
|
||||
{
|
||||
string procName;
|
||||
uint ruid; // из /proc/PID/status (Uid:)
|
||||
uint uid; // LOGINUID из /proc/PID/loginuid
|
||||
string filePathOpen; // путь, снятый на PERM
|
||||
bool changed; // был CLOSE_WRITE
|
||||
const(ubyte)[] fileBytesSource;
|
||||
const(ubyte)[] fileBytesDiff;
|
||||
}
|
||||
|
||||
__gshared Session[DevIno] gMap; // (pid,dev,ino) -> Session
|
||||
__gshared int[int] gPidfdByPid; // pid -> pidfd
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
/// ---- журнал: proc, file, uid(loginuid), ruid ----
|
||||
void logChange(string procName, string filePath, uint uid, uint ruid)
|
||||
{
|
||||
writefln(`proc="%s" file="%s" user=%s(%s) realUser=%s(%s)`,
|
||||
procName, filePath, userName(uid), uid, userName(ruid), ruid);
|
||||
}
|
||||
|
||||
/// ---- маска на КАТАЛОГ ----
|
||||
ulong dirMask()
|
||||
{
|
||||
return FAN_EVENT_ON_CHILD |
|
||||
FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM | FAN_ACCESS_PERM |
|
||||
FAN_OPEN | FAN_CLOSE_WRITE;
|
||||
}
|
||||
|
||||
ubyte[] readAllFromFd(int fd)
|
||||
{
|
||||
import std.array : appender;
|
||||
|
||||
ubyte[16_384] buf; // 16 KiB буфер
|
||||
auto output = appender!(ubyte[]);
|
||||
for (;;)
|
||||
{
|
||||
auto n = read(fd, buf.ptr, buf.length);
|
||||
if (n <= 0)
|
||||
break;
|
||||
output.put(buf[0 .. n]);
|
||||
}
|
||||
return output.data;
|
||||
}
|
||||
|
||||
/// ---- обработчики ----
|
||||
void onOpenPerm(FanotifyEvent ev, Epoll ep)
|
||||
{
|
||||
const pid = ev.pid;
|
||||
auto procName = readProcComm(pid);
|
||||
auto ids = readProcIds(pid);
|
||||
|
||||
DevIno key;
|
||||
if (!getDevIno(ev.eventFd, key, pid))
|
||||
{
|
||||
ev.respond(FAN_ALLOW);
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = readlinkFdPath(ev.eventFd);
|
||||
|
||||
const(ubyte)[] content;
|
||||
try
|
||||
{
|
||||
if (path.length)
|
||||
{
|
||||
content = readAllFromFd(ev.eventFd);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
content = [];
|
||||
}
|
||||
|
||||
if (auto ps = key in gMap)
|
||||
{
|
||||
// уже есть
|
||||
}
|
||||
else
|
||||
{
|
||||
gMap[key] = Session(procName, ids.ruid, ids.uid, path, false, content);
|
||||
}
|
||||
|
||||
if (!(pid in gPidfdByPid))
|
||||
{
|
||||
int pfd = -1;
|
||||
try
|
||||
{
|
||||
pfd = pidfd_open(pid, 0);
|
||||
if (pfd < 0)
|
||||
writeln("pidfd_open failed: ", strerror(errno).fromStringz);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
pfd = -1;
|
||||
}
|
||||
if (pfd >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
ep.add(pfd, cast(uint) pid, EPOLLIN | EPOLLHUP | EPOLLRDHUP | EPOLLERR);
|
||||
gPidfdByPid[pid] = pfd;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ev.respond(FAN_ALLOW);
|
||||
}
|
||||
|
||||
void onCloseWrite(FanotifyEvent ev)
|
||||
{
|
||||
DevIno key;
|
||||
if (!getDevIno(ev.eventFd, key, ev.pid))
|
||||
return;
|
||||
if (auto ps = key in gMap)
|
||||
{
|
||||
ps.changed = true;
|
||||
|
||||
import sdiff;
|
||||
|
||||
const(ubyte)[] content;
|
||||
try
|
||||
{
|
||||
if (ps.filePathOpen.length)
|
||||
content = readAllFromFd(ev.eventFd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
content = [];
|
||||
}
|
||||
|
||||
auto dataSource = new MMFile(ps.fileBytesSource);
|
||||
auto dataChange = new MMFile(content);
|
||||
|
||||
ps.fileBytesDiff = dataSource.diff(dataChange).slice();
|
||||
}
|
||||
}
|
||||
|
||||
void flushAndClearForPid(int pid, Epoll ep)
|
||||
{
|
||||
auto keys = gMap.keys.dup;
|
||||
foreach (k; keys)
|
||||
{
|
||||
if (k.pid != pid)
|
||||
continue;
|
||||
auto s = gMap[k];
|
||||
if (s.changed)
|
||||
{
|
||||
logChange(s.procName, s.filePathOpen, s.uid, s.ruid);
|
||||
writeln(cast(string)s.fileBytesDiff);
|
||||
}
|
||||
gMap.remove(k);
|
||||
}
|
||||
if (auto p = pid in gPidfdByPid)
|
||||
{
|
||||
try
|
||||
{
|
||||
ep.remove(*p);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
close(*p);
|
||||
gPidfdByPid.remove(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/// ---- main ----
|
||||
enum uint FAN_TAG = 0;
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
string watchDir;
|
||||
|
||||
auto help = getopt(args, "dir|d", &watchDir);
|
||||
if (help.helpWanted || watchDir.length == 0)
|
||||
{
|
||||
defaultGetoptPrinter("Usage: app -d <dir-to-watch>", help.options);
|
||||
return;
|
||||
}
|
||||
enforce(isDir(watchDir), "Ожидается путь к КАТАЛОГУ (-d)");
|
||||
|
||||
enum initFlags = FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK;
|
||||
auto fan = new Fanotify(initFlags, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
|
||||
|
||||
try
|
||||
{
|
||||
fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, dirMask(), AT_FDCWD, watchDir);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
stderr.writefln("fanotify_mark failed: %s", e.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
auto ep = new Epoll(EPOLL_CLOEXEC);
|
||||
ep.add(fan.handle, FAN_TAG, EPOLLIN | EPOLLERR | EPOLLHUP);
|
||||
|
||||
writeln("watching dir: ", watchDir);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto evs = ep.wait(256, -1);
|
||||
if (evs.length == 0)
|
||||
continue;
|
||||
|
||||
foreach (e; evs)
|
||||
{
|
||||
if (e.tag == FAN_TAG)
|
||||
{
|
||||
auto list = fan.readEvents(8_192);
|
||||
foreach (fev; list)
|
||||
{
|
||||
if (fev.isOverflow)
|
||||
{
|
||||
writeln(
|
||||
"FAN_Q_OVERFLOW — возможна потеря корреляции");
|
||||
continue;
|
||||
}
|
||||
if (fev.isOpenPerm || fev.isAccessPerm || fev.isOpenExecPerm)
|
||||
{
|
||||
onOpenPerm(fev, ep);
|
||||
continue;
|
||||
}
|
||||
if (fev.isCloseWrite)
|
||||
{
|
||||
onCloseWrite(fev);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pid = cast(int) e.tag;
|
||||
flushAndClearForPid(pid, ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
86
source/depoll.d
Normal file
86
source/depoll.d
Normal file
|
@ -0,0 +1,86 @@
|
|||
module depoll;
|
||||
|
||||
import core.sys.linux.epoll;
|
||||
import core.sys.posix.unistd : close;
|
||||
import core.stdc.errno : errno, EINTR, EEXIST;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Добавление дескриптора с произвольным tag (например, tag=0 для fanotify, tag=pid для pidfd)
|
||||
void add(int fd, uint tag, uint events = EPOLLIN | EPOLLERR | EPOLLHUP)
|
||||
{
|
||||
epoll_event ev;
|
||||
ev.events = events;
|
||||
ev.data.u32 = tag;
|
||||
auto rc = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
|
||||
if (rc != 0 && errno == EEXIST)
|
||||
{
|
||||
// Если уже добавлен — делаем MOD
|
||||
rc = epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev);
|
||||
}
|
||||
enforce(rc == 0, "epoll_ctl ADD/MOD: " ~ strerror(errno).fromStringz.idup);
|
||||
}
|
||||
|
||||
/// Опционально: явное удаление
|
||||
void remove(int fd)
|
||||
{
|
||||
auto rc = epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, null);
|
||||
enforce(rc == 0, "epoll_ctl DEL: " ~ strerror(errno).fromStringz.idup);
|
||||
}
|
||||
|
||||
struct Event
|
||||
{
|
||||
uint tag; // то самое, что передавали в add()
|
||||
uint events; // EPOLL* биты
|
||||
}
|
||||
|
||||
/// Возвращает события; пустой массив — если таймаут/прерывание
|
||||
Event[] wait(int maxevents = 16, int timeout = -1)
|
||||
{
|
||||
epoll_event[] evs = new epoll_event[maxevents];
|
||||
int n;
|
||||
// Терпим EINTR и повторяем
|
||||
while (true)
|
||||
{
|
||||
n = epoll_wait(epfd_, evs.ptr, maxevents, timeout);
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (n <= 0)
|
||||
return [];
|
||||
|
||||
Event[] res;
|
||||
res.reserve(n);
|
||||
foreach (i; 0 .. n)
|
||||
{
|
||||
res ~= Event(evs[i].data.u32, evs[i].events);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@property int handle() const
|
||||
{
|
||||
return epfd_;
|
||||
}
|
||||
}
|
252
source/dfanotify.d
Normal file
252
source/dfanotify.d
Normal file
|
@ -0,0 +1,252 @@
|
|||
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_;
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
// Модуль fanotify_wrapper.d
|
||||
// Этот модуль предоставляет обёртку вокруг API fanotify для упрощения мониторинга событий файловой системы в Linux.
|
||||
// Fanotify позволяет получать уведомления о действиях с файлами, такими как открытие, модификация, создание и удаление.
|
||||
// Обёртка включает структуру для событий, класс для управления дескриптором и методы для инициализации, маркировки и чтения событий.
|
||||
// Импорты: public import fanotify - предполагается, что это низкоуровневый модуль с определениями из <linux/fanotify.h>.
|
||||
// Другие импорты из 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; // Просто геттер.
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue