Мониторинг с несколькими дескрипторами + новый модуль depoll

This commit is contained in:
Alexander Zhirov 2025-08-29 00:35:45 +03:00
parent cb3f40deee
commit 7858416df9
Signed by: alexander
GPG key ID: C8D8BE544A27C511
2 changed files with 285 additions and 50 deletions

View file

@ -1,77 +1,239 @@
import dfanotify;
module app;
import std.stdio;
import std.format : format;
import core.sys.posix.fcntl : AT_FDCWD;
import core.stdc.stdlib : exit;
import dfanotify; // твой модуль сверху
import depoll; // новый модуль для epoll
void main()
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;
import std.conv : to;
import std.stdio : writeln, writefln;
import std.getopt;
import std.algorithm : canFind;
import core.stdc.stdint;
import std.exception;
// Удобная печать маски (для живых логов)
string maskToStr(uint64_t m)
{
Fanotify fan;
try
{
fan = new Fanotify(
FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME
);
}
catch (Exception e)
{
writeln(e.msg);
exit(1);
}
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";
}
auto eventMask = FAN_OPEN | FAN_MODIFY | FAN_CLOSE | FAN_CREATE | FAN_DELETE | FAN_EVENT_ON_CHILD | FAN_ACCESS;
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
{
fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, eventMask, AT_FDCWD, "/tmp/scripts");
pre = new Fanotify(preInitFlags, O_RDONLY | O_LARGEFILE);
}
catch (Exception e)
{
writeln(e.msg);
exit(1);
writeln("pre: ", e.msg);
}
writeln(
"Мониторинг с разрешениями запущен для /tmp/scripts...");
// 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) Главный цикл
// ---------------------------
while (true)
{
auto events = fan.readEvents();
auto epEvents = ep.wait();
foreach (e; events)
foreach (epEv; epEvents)
{
string path = e.name.length ? e.name : "unknown";
auto tag = epEv.tag;
writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path);
// Обработка permission-события
if (e.isOpenPerm)
// Считываем пачку событий с соответствующего fd
FanotifyEvent[] events;
if (tag == TAG_PRE)
{
writeln(" - Запрос на открытие файла. Отклоняем (предполагаем попытку записи).");
e.respond(FAN_DENY); // Отклонить (для FAN_ALLOW используйте FAN_ALLOW)
events = pre.readEvents(64 * 1024);
}
else if (tag == TAG_NOTIF)
{
events = notif.readEvents(64 * 1024);
}
else
{
// Обычные уведомления (не permission)
if (e.isOpen)
writeln(" - Открытие файла");
if (e.isAccess)
writeln(" - Доступ к файлу");
if (e.isModify)
writeln(" - Модификация файла");
if (e.isCloseWrite)
writeln(" - Закрытие после записи");
if (e.isCloseNoWrite)
writeln(" - Закрытие без записи");
if (e.isCreate)
writeln(" - Создание файла/директории");
if (e.isDelete)
writeln(" - Удаление файла/директории");
continue;
}
// Опционально: вызов постобработки
e.postProcess();
foreach (ev; events)
{
// Лог
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 - <name>" ...
writefln(" -> HANDLE CLOSE_WRITE for: %s", ev.name);
}
else if (ev.isModify)
{
writefln(" -> HANDLE MODIFY for: %s", ev.name);
}
}
// Кастомный postProcess если надо
ev.postProcess();
}
}
}
}