Мониторинг с несколькими дескрипторами + новый модуль depoll
This commit is contained in:
parent
cb3f40deee
commit
7858416df9
2 changed files with 285 additions and 50 deletions
258
source/app.d
258
source/app.d
|
@ -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
|
||||
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";
|
||||
}
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
fan = new Fanotify(
|
||||
FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME
|
||||
string watchPath = "/tmp/scripts";
|
||||
bool markMount = false;
|
||||
|
||||
getopt(args,
|
||||
"path|p", &watchPath,
|
||||
"mount", &markMount,
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
writeln(e.msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto eventMask = FAN_OPEN | FAN_MODIFY | FAN_CLOSE | FAN_CREATE | FAN_DELETE | FAN_EVENT_ON_CHILD | FAN_ACCESS;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
source/depoll.d
Normal file
73
source/depoll.d
Normal file
|
@ -0,0 +1,73 @@
|
|||
module depoll;
|
||||
|
||||
import core.sys.linux.epoll;
|
||||
import core.sys.posix.unistd : close;
|
||||
import core.stdc.errno : errno, EINTR;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void add(int fd, uint tag, uint events = EPOLLIN | EPOLLERR | EPOLLHUP)
|
||||
{
|
||||
epoll_event ev;
|
||||
ev.events = events;
|
||||
ev.data.u32 = tag;
|
||||
enforce(epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev) == 0,
|
||||
"epoll_ctl ADD: " ~ strerror(errno).fromStringz.idup);
|
||||
}
|
||||
|
||||
// Опционально: метод для удаления fd
|
||||
void remove(int fd)
|
||||
{
|
||||
enforce(epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, null) == 0,
|
||||
"epoll_ctl DEL: " ~ strerror(errno).fromStringz.idup);
|
||||
}
|
||||
|
||||
struct Event
|
||||
{
|
||||
uint tag;
|
||||
uint events;
|
||||
}
|
||||
|
||||
Event[] wait(int maxevents = 16, int timeout = -1)
|
||||
{
|
||||
epoll_event[] evs = new epoll_event[maxevents];
|
||||
int n = epoll_wait(epfd_, evs.ptr, maxevents, timeout);
|
||||
if (n <= 0)
|
||||
{
|
||||
// Игнорируем EINTR и другие (как в оригинале: continue)
|
||||
return [];
|
||||
}
|
||||
|
||||
Event[] res;
|
||||
foreach (i; 0 .. n)
|
||||
{
|
||||
res ~= Event(evs[i].data.u32, evs[i].events);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@property int handle() const
|
||||
{
|
||||
return epfd_;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue