dwatch/source/app.d

268 lines
6.9 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}
}