268 lines
6.9 KiB
D
268 lines
6.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.sys.posix.unistd : readlink;
|
||
|
||
import core.stdc.stdint;
|
||
import std.exception;
|
||
|
||
import std.path : baseName;
|
||
|
||
/// Вернуть путь для fd из события PRE_CONTENT.
|
||
/// Может вернуть "(deleted)" или пустую строку, если объект без имени.
|
||
string pathFromEventFd(int fd)
|
||
{
|
||
// Конструируем путь в /proc/self/fd
|
||
auto linkPath = "/proc/self/fd/" ~ fd.to!string;
|
||
char[4096] buf;
|
||
|
||
// readlink не добавляет '\0'
|
||
auto n = readlink(linkPath.toStringz, buf.ptr, buf.length);
|
||
if (n < 0)
|
||
{
|
||
return "";
|
||
}
|
||
return cast(string) buf[0 .. n].idup;
|
||
}
|
||
|
||
// Удобная печать маски (для живых логов)
|
||
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)
|
||
{
|
||
if (tag == TAG_PRE && (ev.isOpenPerm || ev.isAccessPerm))
|
||
{
|
||
string path = pathFromEventFd(ev.eventFd);
|
||
writeln("Попытка доступа к: ", path.length ? path
|
||
: "<неизвестно>");
|
||
// ev.respond(FAN_ALLOW); // или FAN_DENY по своей политике
|
||
baseName(path) == "test" ? ev.respond(FAN_DENY) : ev.respond(FAN_ALLOW);
|
||
}
|
||
|
||
// Лог
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
}
|