239 lines
5.9 KiB
D
239 lines
5.9 KiB
D
module app;
|
||
|
||
import dfanotify; // твой модуль сверху
|
||
import depoll; // новый модуль для epoll
|
||
|
||
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)
|
||
{
|
||
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)
|
||
{
|
||
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
|
||
{
|
||
pre = new Fanotify(preInitFlags, O_RDONLY | O_LARGEFILE);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
writeln("pre: ", e.msg);
|
||
}
|
||
|
||
// 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 epEvents = ep.wait();
|
||
|
||
foreach (epEv; epEvents)
|
||
{
|
||
auto tag = epEv.tag;
|
||
|
||
// Считываем пачку событий с соответствующего fd
|
||
FanotifyEvent[] events;
|
||
if (tag == TAG_PRE)
|
||
{
|
||
events = pre.readEvents(64 * 1024);
|
||
}
|
||
else if (tag == TAG_NOTIF)
|
||
{
|
||
events = notif.readEvents(64 * 1024);
|
||
}
|
||
else
|
||
{
|
||
continue;
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
}
|