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 - " ... writefln(" -> HANDLE CLOSE_WRITE for: %s", ev.name); } else if (ev.isModify) { writefln(" -> HANDLE MODIFY for: %s", ev.name); } } // Кастомный postProcess если надо ev.postProcess(); } } } }