Compare commits
	
		
			8 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 377d5c1f94 | |||
| 2876a45a85 | |||
| bc02ac8518 | |||
| 1f78d8eb83 | |||
| 7858416df9 | |||
| cb3f40deee | |||
| ed0d2780c3 | |||
| 582ae5a294 | 
					 7 changed files with 806 additions and 257 deletions
				
			
		
							
								
								
									
										5
									
								
								dub.json
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								dub.json
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -9,9 +9,6 @@
 | 
			
		|||
	"targetPath": "bin",
 | 
			
		||||
	"targetType": "executable",
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"fanotify": {
 | 
			
		||||
			"repository": "git+https://git.zhirov.kz/dlang/fanotify.git",
 | 
			
		||||
			"version": "97edc0d795c93ef773ff60d260951e5ff6ae6215"
 | 
			
		||||
		}
 | 
			
		||||
		"fanotify": "~>0.1.0"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
	"fileVersion": 1,
 | 
			
		||||
	"versions": {
 | 
			
		||||
		"fanotify": "0.1.0"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										393
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										393
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,71 +1,358 @@
 | 
			
		|||
// Модуль app.d
 | 
			
		||||
// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper.
 | 
			
		||||
// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.),
 | 
			
		||||
// затем в бесконечном цикле читает события и выводит информацию о них в консоль.
 | 
			
		||||
// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима),
 | 
			
		||||
// std.format для форматирования строк, core.sys.posix для констант.
 | 
			
		||||
module app;
 | 
			
		||||
 | 
			
		||||
import fanotify_wrapper; // Импорт обёртки для fanotify.
 | 
			
		||||
import dfanotify; // твой модуль сверху
 | 
			
		||||
import depoll; // новый модуль для epoll
 | 
			
		||||
import event_actor;
 | 
			
		||||
 | 
			
		||||
import std.stdio; // Импорт для writeln, writefln (вывод в консоль).
 | 
			
		||||
import std.file : readLink; // Импорт readLink для чтения симлинков (не используется здесь, но оставлено для возможного расширения).
 | 
			
		||||
import std.format : format; // Импорт format для форматирования строк (хотя здесь используется writefln напрямую).
 | 
			
		||||
import core.sys.posix.fcntl : AT_FDCWD; // Импорт AT_FDCWD для текущей директории.
 | 
			
		||||
import core.sys.posix.unistd : close; // Импорт close (не используется здесь, но для возможного расширения с fd).
 | 
			
		||||
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, strip;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
import std.stdio : writeln, writefln;
 | 
			
		||||
import std.getopt;
 | 
			
		||||
import std.algorithm : canFind;
 | 
			
		||||
import core.sys.posix.unistd : readlink;
 | 
			
		||||
 | 
			
		||||
// Функция main: точка входа приложения.
 | 
			
		||||
void main()
 | 
			
		||||
import core.stdc.stdint;
 | 
			
		||||
import std.exception;
 | 
			
		||||
 | 
			
		||||
import std.path : baseName;
 | 
			
		||||
 | 
			
		||||
import std.file : readText, exists, read;
 | 
			
		||||
 | 
			
		||||
/// Вернуть путь для fd из события PRE_CONTENT.
 | 
			
		||||
/// Может вернуть "(deleted)" или пустую строку, если объект без имени.
 | 
			
		||||
string pathFromEventFd(int fd)
 | 
			
		||||
{
 | 
			
		||||
	// Инициализация объекта Fanotify с флагами:
 | 
			
		||||
	// FAN_CLASS_NOTIF - режим уведомлений (без контроля доступа),
 | 
			
		||||
	// FAN_CLOEXEC - закрытие дескриптора при exec,
 | 
			
		||||
	// FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME - отчёт с FID (идентификатор файла) вместо fd, плюс имя файла.
 | 
			
		||||
	// Это позволяет получать имя без реального fd (fd будет FAN_NOFD).
 | 
			
		||||
	auto fan = new Fanotify(
 | 
			
		||||
		FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME);
 | 
			
		||||
	// Конструируем путь в /proc/self/fd
 | 
			
		||||
	auto linkPath = "/proc/self/fd/" ~ fd.to!string;
 | 
			
		||||
	char[4096] buf;
 | 
			
		||||
 | 
			
		||||
	// Определение маски событий: битовая OR флагов для мониторинга.
 | 
			
		||||
	// FAN_OPEN - открытие, FAN_MODIFY - модификация, FAN_CLOSE - закрытие (включает WRITE и NOWRITE),
 | 
			
		||||
	// FAN_CREATE - создание, FAN_DELETE - удаление, FAN_EVENT_ON_CHILD - события в поддиректориях.
 | 
			
		||||
	auto eventMask = FAN_OPEN | FAN_MODIFY | FAN_CLOSE | FAN_CREATE | FAN_DELETE | FAN_EVENT_ON_CHILD;
 | 
			
		||||
	// readlink не добавляет '\0'
 | 
			
		||||
	auto n = readlink(linkPath.toStringz, buf.ptr, buf.length);
 | 
			
		||||
	if (n < 0)
 | 
			
		||||
	{
 | 
			
		||||
		return "";
 | 
			
		||||
	}
 | 
			
		||||
	return cast(string) buf[0 .. n].idup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// Маркировка директории /tmp/scripts:
 | 
			
		||||
	// FAN_MARK_ADD - добавить марку, FAN_MARK_ONLYDIR - только для директории (ошибка, если не директория).
 | 
			
		||||
	// AT_FDCWD - базовая директория текущая, путь "/tmp/scripts".
 | 
			
		||||
	fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, eventMask, AT_FDCWD, "/tmp/scripts");
 | 
			
		||||
/// Возвращает "имя процесса" для события fanotify.
 | 
			
		||||
/// Приоритет: /proc/<pid>/comm → имя ссылки /proc/<pid>/exe → первый токен из cmdline.
 | 
			
		||||
/// Если всё сломалось — "pid:<pid>".
 | 
			
		||||
string eventProcessName(int pid)
 | 
			
		||||
{
 | 
			
		||||
	// 1) /proc/<pid>/comm — самое точное короткое имя
 | 
			
		||||
	const commPath = "/proc/" ~ pid.to!string ~ "/comm";
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		if (exists(commPath))
 | 
			
		||||
		{
 | 
			
		||||
			auto s = readText(commPath).strip; // там обычно одна строка с \n
 | 
			
		||||
			if (s.length)
 | 
			
		||||
				return s;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга.
 | 
			
		||||
	// 2) /proc/<pid>/exe — полный путь к исполняемому файлу
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		char[4096] buf;
 | 
			
		||||
		auto link = "/proc/" ~ pid.to!string ~ "/exe";
 | 
			
		||||
		auto n = readlink(link.toStringz, buf.ptr, buf.length);
 | 
			
		||||
		if (n > 0)
 | 
			
		||||
		{
 | 
			
		||||
			auto exePath = cast(string) buf[0 .. n].idup;
 | 
			
		||||
			auto name = baseName(exePath);
 | 
			
		||||
			if (name.length)
 | 
			
		||||
				return name;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3) /proc/<pid>/cmdline — первый NUL-разделённый аргумент
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		const cmdPath = "/proc/" ~ pid.to!string ~ "/cmdline";
 | 
			
		||||
		if (exists(cmdPath))
 | 
			
		||||
		{
 | 
			
		||||
			auto bytes = cast(const(ubyte)[]) read(cmdPath);
 | 
			
		||||
			if (bytes.length)
 | 
			
		||||
			{
 | 
			
		||||
				// до первого \0
 | 
			
		||||
				size_t i = 0;
 | 
			
		||||
				for (; i < bytes.length && bytes[i] != 0; ++i)
 | 
			
		||||
				{
 | 
			
		||||
				}
 | 
			
		||||
				auto first = cast(string) bytes[0 .. i].idup;
 | 
			
		||||
				if (first.length)
 | 
			
		||||
				{
 | 
			
		||||
					auto name = baseName(first);
 | 
			
		||||
					if (name.length)
 | 
			
		||||
						return name;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// fallback
 | 
			
		||||
	return "pid:" ~ pid.to!string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Удобная печать маски (для живых логов)
 | 
			
		||||
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 events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий).
 | 
			
		||||
		auto epEvents = ep.wait();
 | 
			
		||||
 | 
			
		||||
		foreach (ref e; events) // Цикл по каждому событию в массиве.
 | 
			
		||||
		foreach (epEv; epEvents)
 | 
			
		||||
		{
 | 
			
		||||
			// Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown".
 | 
			
		||||
			// name - относительное имя файла/директории относительно маркированной.
 | 
			
		||||
			string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное)
 | 
			
		||||
			auto tag = epEv.tag;
 | 
			
		||||
 | 
			
		||||
			// Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути.
 | 
			
		||||
			// fd теперь FAN_NOFD, так что пропускаем readLink/close
 | 
			
		||||
			// Считываем пачку событий с соответствующего fd
 | 
			
		||||
			FanotifyEvent[] events;
 | 
			
		||||
			if (tag == TAG_PRE)
 | 
			
		||||
			{
 | 
			
		||||
				events = pre.readEvents(64 * 1024);
 | 
			
		||||
			}
 | 
			
		||||
			else if (tag == TAG_NOTIF)
 | 
			
		||||
			{
 | 
			
		||||
				events = notif.readEvents(64 * 1024);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Вывод общей информации о событии: маска в hex, PID, путь/имя.
 | 
			
		||||
			writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path);
 | 
			
		||||
			foreach (ev; events)
 | 
			
		||||
			{
 | 
			
		||||
				auto pname = eventProcessName(ev.pid);
 | 
			
		||||
				// writeln("pid = ", ev.pid, "\tproc = ", pname, "\tmask = ", maskToStr(ev.mask));
 | 
			
		||||
				writefln("[%s] pid = %s proc = %s mask = %s name = %s",
 | 
			
		||||
					(tag == TAG_PRE) ? "PRE" : "NOTIF",
 | 
			
		||||
					ev.pid, pname,
 | 
			
		||||
					maskToStr(ev.mask),
 | 
			
		||||
					ev.name.length ? ev.name : "<неизвестно>"
 | 
			
		||||
				);
 | 
			
		||||
				auto actor = eventActorFromPid(ev.pid);
 | 
			
		||||
				writeln("user=", actor.user, " uid=", actor.uid,
 | 
			
		||||
						" ruid=", actor.ruid, "(", actor.ruser, ")",
 | 
			
		||||
						" euid=", actor.euid, "(", actor.euser, ")");
 | 
			
		||||
 | 
			
		||||
			// Проверки и вывод конкретных типов событий с использованием методов структуры.
 | 
			
		||||
			if (e.isOpen) // Если открытие.
 | 
			
		||||
				writeln(" - Открытие файла");
 | 
			
		||||
			if (e.isModify) // Если модификация.
 | 
			
		||||
				writeln(" - Модификация файла");
 | 
			
		||||
			if (e.isCloseWrite) // Если закрытие после записи.
 | 
			
		||||
				writeln(" - Закрытие после записи");
 | 
			
		||||
			if (e.isCloseNoWrite) // Если закрытие без записи.
 | 
			
		||||
				writeln(" - Закрытие без записи");
 | 
			
		||||
			if (e.isCreate) // Если создание.
 | 
			
		||||
				writeln(" - Создание файла/директории");
 | 
			
		||||
			if (e.isDelete) // Если удаление.
 | 
			
		||||
				writeln(" - Удаление файла/директории");
 | 
			
		||||
				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.isMoved)
 | 
			
		||||
					{
 | 
			
		||||
						writefln("   -> HANDLE MOVED 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_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										252
									
								
								source/dfanotify.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								source/dfanotify.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,252 @@
 | 
			
		|||
module dfanotify;
 | 
			
		||||
 | 
			
		||||
public import fanotify;
 | 
			
		||||
 | 
			
		||||
import core.sys.posix.unistd : read, write, close, ssize_t;
 | 
			
		||||
import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD;
 | 
			
		||||
import std.exception : enforce;
 | 
			
		||||
import std.string : toStringz, fromStringz;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
import core.stdc.errno : errno;
 | 
			
		||||
import core.stdc.string : strerror;
 | 
			
		||||
import core.stdc.stdint;
 | 
			
		||||
 | 
			
		||||
// Класс для представления события fanotify (ООП-стиль, с методами для проверки и обработки)
 | 
			
		||||
class FanotifyEvent
 | 
			
		||||
{
 | 
			
		||||
	private fanotify_event_metadata meta_;
 | 
			
		||||
	private string name_;
 | 
			
		||||
	private int fanFd_; // Ссылка на fanotify fd для отправки response (копируется при создании)
 | 
			
		||||
 | 
			
		||||
	// Конструктор (value semantics, копирует данные)
 | 
			
		||||
	this(fanotify_event_metadata meta, string name, int fanFd)
 | 
			
		||||
	{
 | 
			
		||||
		meta_ = meta;
 | 
			
		||||
		name_ = name;
 | 
			
		||||
		fanFd_ = fanFd;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Деструктор: автоматически закрывает fd события, если он валиден (RAII)
 | 
			
		||||
	~this()
 | 
			
		||||
	{
 | 
			
		||||
		if (meta_.fd >= 0 && meta_.fd != FAN_NOFD)
 | 
			
		||||
		{
 | 
			
		||||
			close(meta_.fd);
 | 
			
		||||
			meta_.fd = -1; // Избегаем повторного закрытия
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Геттеры (value types)
 | 
			
		||||
	@property uint64_t mask() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta_.mask;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@property int eventFd() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta_.fd;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@property int pid() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta_.pid;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@property string name() const
 | 
			
		||||
	{
 | 
			
		||||
		return name_;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Методы проверки событий (без ref)
 | 
			
		||||
	bool isOpen() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_OPEN) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isModify() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_MODIFY) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isCloseWrite() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CLOSE_WRITE) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isCloseNoWrite() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CLOSE_NOWRITE) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isAccess() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_ACCESS) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isMoved() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_MOVED_FROM) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isCreate() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CREATE) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isDelete() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_DELETE) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isOpenPerm() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_OPEN_PERM) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isAccessPerm() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_ACCESS_PERM) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isOpenExecPerm() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_OPEN_EXEC_PERM) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isOverflow() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_Q_OVERFLOW) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool isFsError() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_FS_ERROR) != 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод для постобработки события (виртуальный, можно override для кастомной логики)
 | 
			
		||||
	void postProcess()
 | 
			
		||||
	{
 | 
			
		||||
		// По умолчанию ничего, но можно добавить логику, например, логирование
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод для отправки response (для permission-событий), закрывает fd автоматически после
 | 
			
		||||
	void respond(uint response)
 | 
			
		||||
	{
 | 
			
		||||
		if (eventFd < 0 || eventFd == FAN_NOFD)
 | 
			
		||||
		{
 | 
			
		||||
			return; // Нет fd для response
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fanotify_response resp;
 | 
			
		||||
		resp.fd = eventFd;
 | 
			
		||||
		resp.response = response;
 | 
			
		||||
 | 
			
		||||
		ssize_t res = write(fanFd_, &resp, fanotify_response.sizeof);
 | 
			
		||||
		enforce(res == fanotify_response.sizeof, "Ошибка записи response: " ~ strerror(errno)
 | 
			
		||||
				.fromStringz.to!string);
 | 
			
		||||
 | 
			
		||||
		// Закрываем fd сразу после response (не ждем деструктора, но деструктор на всякий случай)
 | 
			
		||||
		close(meta_.fd);
 | 
			
		||||
		meta_.fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Основной ООП-класс для управления fanotify
 | 
			
		||||
class Fanotify
 | 
			
		||||
{
 | 
			
		||||
	private int fd_ = -1;
 | 
			
		||||
 | 
			
		||||
	// Конструктор: инициализация с флагами
 | 
			
		||||
	this(uint initFlags, uint eventFFlags = O_RDONLY | O_LARGEFILE)
 | 
			
		||||
	{
 | 
			
		||||
		fd_ = fanotify_init(initFlags, eventFFlags);
 | 
			
		||||
		enforce(fd_ >= 0, "Ошибка инициализации fanotify: " ~ strerror(errno)
 | 
			
		||||
				.fromStringz.to!string);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Деструктор: автоматически закрывает fanotify fd
 | 
			
		||||
	~this()
 | 
			
		||||
	{
 | 
			
		||||
		if (fd_ >= 0)
 | 
			
		||||
		{
 | 
			
		||||
			close(fd_);
 | 
			
		||||
			fd_ = -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод для добавления/удаления/модификации меток (управление событиями)
 | 
			
		||||
	void mark(uint markFlags, uint64_t eventMask, int dirFd = AT_FDCWD, string path = null)
 | 
			
		||||
	{
 | 
			
		||||
		const(char)* cPath = path ? path.toStringz() : null;
 | 
			
		||||
		int res = fanotify_mark(fd_, markFlags, eventMask, dirFd, cPath);
 | 
			
		||||
		enforce(res == 0, "Ошибка маркировки fanotify: " ~ strerror(errno)
 | 
			
		||||
				.fromStringz.to!string);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод для чтения событий (возвращает массив объектов событий)
 | 
			
		||||
	FanotifyEvent[] readEvents(size_t bufferSize = 4096)
 | 
			
		||||
	{
 | 
			
		||||
		ubyte[] buffer = new ubyte[bufferSize];
 | 
			
		||||
		ssize_t len = read(fd_, buffer.ptr, buffer.length);
 | 
			
		||||
		if (len <= 0)
 | 
			
		||||
		{
 | 
			
		||||
			return [];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		FanotifyEvent[] events;
 | 
			
		||||
		size_t offset = 0;
 | 
			
		||||
		while (offset + FAN_EVENT_METADATA_LEN <= len)
 | 
			
		||||
		{
 | 
			
		||||
			auto meta = *(cast(fanotify_event_metadata*)(buffer.ptr + offset));
 | 
			
		||||
			if (meta.event_len < FAN_EVENT_METADATA_LEN || offset + meta.event_len > len)
 | 
			
		||||
			{
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string name;
 | 
			
		||||
			size_t infoOffset = offset + fanotify_event_metadata.sizeof;
 | 
			
		||||
			while (infoOffset < offset + meta.event_len)
 | 
			
		||||
			{
 | 
			
		||||
				auto hdr = *(cast(fanotify_event_info_header*)(buffer.ptr + infoOffset));
 | 
			
		||||
				if (hdr.len == 0 || infoOffset + hdr.len > offset + meta.event_len)
 | 
			
		||||
				{
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME ||
 | 
			
		||||
					hdr.info_type == FAN_EVENT_INFO_TYPE_OLD_DFID_NAME ||
 | 
			
		||||
					hdr.info_type == FAN_EVENT_INFO_TYPE_NEW_DFID_NAME)
 | 
			
		||||
				{
 | 
			
		||||
					size_t fidOffset = infoOffset + fanotify_event_info_header.sizeof + __kernel_fsid_t.sizeof;
 | 
			
		||||
					auto handle = *(cast(file_handle*)(buffer.ptr + fidOffset));
 | 
			
		||||
					size_t handleEnd = fidOffset + file_handle.sizeof + handle.handle_bytes;
 | 
			
		||||
					if (handleEnd < offset + meta.event_len)
 | 
			
		||||
					{
 | 
			
		||||
						name = (cast(char*)(buffer.ptr + handleEnd)).fromStringz.to!string;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				infoOffset += hdr.len;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto ev = new FanotifyEvent(meta, name, fd_);
 | 
			
		||||
			events ~= ev;
 | 
			
		||||
			offset += meta.event_len;
 | 
			
		||||
		}
 | 
			
		||||
		return events;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод для постобработки всех событий (вызывает postProcess на каждом)
 | 
			
		||||
	void postProcessEvents(FanotifyEvent[] events)
 | 
			
		||||
	{
 | 
			
		||||
		foreach (ev; events)
 | 
			
		||||
		{
 | 
			
		||||
			ev.postProcess();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Геттер для fd (если нужно внешне, но лучше использовать методы)
 | 
			
		||||
	@property int handle() const
 | 
			
		||||
	{
 | 
			
		||||
		return fd_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								source/event_actor.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								source/event_actor.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,139 @@
 | 
			
		|||
module event_actor;
 | 
			
		||||
 | 
			
		||||
import core.sys.posix.pwd : passwd, getpwuid_r;
 | 
			
		||||
import core.sys.posix.sys.types : uid_t;
 | 
			
		||||
import core.stdc.stdlib : malloc, free;
 | 
			
		||||
import core.stdc.string : strlen;
 | 
			
		||||
import std.file : readText, exists;
 | 
			
		||||
import std.string : splitLines, strip;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
 | 
			
		||||
// для токенизации по пробельным
 | 
			
		||||
import std.ascii : isWhite;
 | 
			
		||||
import std.algorithm.iteration : splitter;
 | 
			
		||||
import std.array : array;
 | 
			
		||||
 | 
			
		||||
struct EventActor
 | 
			
		||||
{
 | 
			
		||||
	uid_t uid; // выбранный UID (loginuid если есть, иначе ruid)
 | 
			
		||||
	string user; // имя пользователя для uid (может быть пустым)
 | 
			
		||||
	uid_t ruid, euid, suid, fsuid;
 | 
			
		||||
	string ruser, euser, suser, fsuser;
 | 
			
		||||
	bool hasLoginuid;
 | 
			
		||||
	uid_t loginuid;
 | 
			
		||||
	string loginuser;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private string userName(uid_t uid)
 | 
			
		||||
{
 | 
			
		||||
	enum BUF = 4096;
 | 
			
		||||
	auto buf = cast(char*) malloc(BUF);
 | 
			
		||||
	scope (exit)
 | 
			
		||||
		if (buf)
 | 
			
		||||
			free(buf);
 | 
			
		||||
	passwd pwd;
 | 
			
		||||
	passwd* outp;
 | 
			
		||||
	auto rc = getpwuid_r(uid, &pwd, buf, BUF, &outp);
 | 
			
		||||
	if (rc == 0 && outp !is null)
 | 
			
		||||
	{
 | 
			
		||||
		return pwd.pw_name[0 .. strlen(pwd.pw_name)].idup;
 | 
			
		||||
	}
 | 
			
		||||
	return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Парсит строку вида: "Uid:\t<ruid>\t<euid>\t<suid>\t<fsuid>"
 | 
			
		||||
private bool parseUidLine(string line, out uid_t r, out uid_t e, out uid_t s, out uid_t f)
 | 
			
		||||
{
 | 
			
		||||
	// разобьём по любым пробельным символам
 | 
			
		||||
	auto toks = line.splitter!isWhite.array; // ["Uid:", "1000", "1000", "1000", "1000"]
 | 
			
		||||
	if (toks.length >= 5 && toks[0] == "Uid:")
 | 
			
		||||
	{
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			r = toks[1].to!uid_t;
 | 
			
		||||
			e = toks[2].to!uid_t;
 | 
			
		||||
			s = toks[3].to!uid_t;
 | 
			
		||||
			f = toks[4].to!uid_t;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception e)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Главная функция: кто инициировал событие (по PID из fanotify_event_metadata)
 | 
			
		||||
EventActor eventActorFromPid(int pid)
 | 
			
		||||
{
 | 
			
		||||
	EventActor a;
 | 
			
		||||
	if (pid <= 0)
 | 
			
		||||
		return a;
 | 
			
		||||
 | 
			
		||||
	const base = "/proc/" ~ pid.to!string ~ "/";
 | 
			
		||||
	const statusPath = base ~ "status";
 | 
			
		||||
 | 
			
		||||
	if (exists(statusPath))
 | 
			
		||||
	{
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			auto txt = readText(statusPath);
 | 
			
		||||
			uid_t r = 0, e = 0, s = 0, f = 0;
 | 
			
		||||
			foreach (line; txt.splitLines())
 | 
			
		||||
			{
 | 
			
		||||
				// ищем строку "Uid:"
 | 
			
		||||
				if (line.length >= 4 && line[0 .. 4] == "Uid:")
 | 
			
		||||
				{
 | 
			
		||||
					if (parseUidLine(line, r, e, s, f))
 | 
			
		||||
					{
 | 
			
		||||
						a.ruid = r;
 | 
			
		||||
						a.euid = e;
 | 
			
		||||
						a.suid = s;
 | 
			
		||||
						a.fsuid = f;
 | 
			
		||||
						a.ruser = userName(r);
 | 
			
		||||
						a.euser = userName(e);
 | 
			
		||||
						a.suser = userName(s);
 | 
			
		||||
						a.fsuser = userName(f);
 | 
			
		||||
					}
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception e)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// loginuid — владелец сессии (если audit включён)
 | 
			
		||||
	const loginPath = base ~ "loginuid";
 | 
			
		||||
	if (exists(loginPath))
 | 
			
		||||
	{
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			auto s = readText(loginPath).strip;
 | 
			
		||||
			if (s.length)
 | 
			
		||||
			{
 | 
			
		||||
				a.loginuid = s.to!uid_t;
 | 
			
		||||
				a.loginuser = userName(a.loginuid);
 | 
			
		||||
				a.hasLoginuid = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception e)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// выбираем «итогового» пользователя
 | 
			
		||||
	if (a.hasLoginuid)
 | 
			
		||||
	{
 | 
			
		||||
		a.uid = a.loginuid;
 | 
			
		||||
		a.user = a.loginuser.length ? a.loginuser : ("uid:" ~ a.loginuid.to!string);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		a.uid = a.ruid;
 | 
			
		||||
		a.user = a.ruser.length ? a.ruser : ("uid:" ~ a.ruid.to!string);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,200 +0,0 @@
 | 
			
		|||
// Модуль fanotify_wrapper.d
 | 
			
		||||
// Этот модуль предоставляет обёртку вокруг API fanotify для упрощения мониторинга событий файловой системы в Linux.
 | 
			
		||||
// Fanotify позволяет получать уведомления о действиях с файлами, такими как открытие, модификация, создание и удаление.
 | 
			
		||||
// Обёртка включает структуру для событий, класс для управления дескриптором и методы для инициализации, маркировки и чтения событий.
 | 
			
		||||
// Импорты: public import fanotify - предполагается, что это низкоуровневый модуль с определениями из <linux/fanotify.h>.
 | 
			
		||||
// Другие импорты из core.sys.posix для системных вызовов (read, close и т.д.), std.exception для обработки ошибок,
 | 
			
		||||
// std.string и std.conv для работы со строками, core.stdc.errno для errno и strerror для детальных сообщений об ошибках,
 | 
			
		||||
// core.stdc.stdint для типов вроде uint64_t.
 | 
			
		||||
 | 
			
		||||
module fanotify_wrapper;
 | 
			
		||||
 | 
			
		||||
public import fanotify; // Импорт низкоуровневых определений fanotify (структуры, константы, функции вроде fanotify_init, fanotify_mark).
 | 
			
		||||
 | 
			
		||||
import core.sys.posix.unistd : read, close, ssize_t; // Импорт функций для чтения (read), закрытия (close) дескрипторов и типа ssize_t для возвращаемых значений.
 | 
			
		||||
import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD; // Импорт флагов для open (O_RDONLY - только чтение, O_LARGEFILE - поддержка больших файлов) и AT_FDCWD для текущей директории.
 | 
			
		||||
import std.exception : enforce; // Импорт enforce для проверки условий и бросания исключений при ошибках.
 | 
			
		||||
import std.string : toStringz, fromStringz; // Импорт функций для конвертации строк D в C-строки (toStringz) и обратно (fromStringz).
 | 
			
		||||
import std.conv : to; // Импорт to для конвертации типов (например, int в string).
 | 
			
		||||
import core.stdc.errno : errno; // Импорт errno для получения кода последней ошибки.
 | 
			
		||||
import core.stdc.string : strerror; // Импорт strerror для получения строкового описания ошибки по errno.
 | 
			
		||||
import core.stdc.stdint; // Импорт стандартных целочисленных типов (uint64_t и т.п.).
 | 
			
		||||
 | 
			
		||||
// Структура FanotifyEvent: представляет одно событие fanotify.
 | 
			
		||||
// Расширена по сравнению с базовой fanotify_event_metadata: добавлено поле name для извлечённого имени файла (если используются флаги FAN_REPORT_NAME).
 | 
			
		||||
// Также добавлены свойства для доступа к ключевым полям и методы для проверки конкретных типов событий.
 | 
			
		||||
// Это упрощает работу с событиями, делая код более читаемым (вместо прямого доступа к meta.mask и т.д.).
 | 
			
		||||
struct FanotifyEvent
 | 
			
		||||
{
 | 
			
		||||
	fanotify_event_metadata meta; // Базовая структура метаданных события из fanotify (содержит mask, fd, pid, event_len и т.д.).
 | 
			
		||||
	string name; // Извлечённое имя файла или директории (относительное имя, если FAN_REPORT_NAME включено; парсится из дополнительной информации в буфере).
 | 
			
		||||
 | 
			
		||||
	// Свойство mask: возвращает маску событий (битовая маска, где каждый бит соответствует типу события, например FAN_OPEN).
 | 
			
		||||
	@property uint64_t mask() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta.mask; // Просто возвращает значение из meta; const гарантирует, что структура не модифицируется.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Свойство eventFd: возвращает дескриптор файла события (fd). В режиме FAN_REPORT_FID это FAN_NOFD (-1), иначе реальный fd.
 | 
			
		||||
	@property int eventFd() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta.fd; // Доступ к fd из meta; полезно, если нужно работать с файлом напрямую (но в этом коде используется режим без fd).
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Свойство pid: возвращает PID процесса, который вызвал событие.
 | 
			
		||||
	@property int pid() const
 | 
			
		||||
	{
 | 
			
		||||
		return meta.pid; // Доступ к pid из meta; помогает идентифицировать, какой процесс взаимодействовал с файлом.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isOpen: проверяет, включает ли маска событие открытия файла (FAN_OPEN).
 | 
			
		||||
	// Использует битовую операцию & для проверки наличия бита FAN_OPEN в mask.
 | 
			
		||||
	bool isOpen() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_OPEN) != 0; // Если бит установлен, возвращает true; это стандартный способ работы с битoвыми масками.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isModify: проверяет событие модификации файла (FAN_MODIFY, например, запись в файл).
 | 
			
		||||
	bool isModify() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_MODIFY) != 0; // Аналогично, проверка бита FAN_MODIFY.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isCloseWrite: проверяет закрытие файла после записи (FAN_CLOSE_WRITE).
 | 
			
		||||
	bool isCloseWrite() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CLOSE_WRITE) != 0; // Проверка бита для закрытия с модификацией.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isCloseNoWrite: проверяет закрытие файла без записи (FAN_CLOSE_NOWRITE, например, после чтения).
 | 
			
		||||
	bool isCloseNoWrite() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CLOSE_NOWRITE) != 0; // Проверка бита для закрытия без модификации.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isAccess: проверяет событие доступа (FAN_ACCESS, например, чтение).
 | 
			
		||||
	bool isAccess() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_ACCESS) != 0; // Проверка бита FAN_ACCESS.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isCreate: проверяет создание файла или директории (FAN_CREATE).
 | 
			
		||||
	bool isCreate() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_CREATE) != 0; // Проверка бита для создания.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод isDelete: проверяет удаление файла или директории (FAN_DELETE).
 | 
			
		||||
	bool isDelete() const
 | 
			
		||||
	{
 | 
			
		||||
		return (mask & FAN_DELETE) != 0; // Проверка бита для удаления.
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Класс Fanotify: основной класс для работы с fanotify.
 | 
			
		||||
// Управляет дескриптором (fd), инициализирует его, маркирует пути для мониторинга, читает события и закрывает дескриптор.
 | 
			
		||||
// Расширен для парсинга дополнительной информации (FID, NAME) в методе readEvents.
 | 
			
		||||
// Использует RAII: дескриптор закрывается в деструкторе автоматически.
 | 
			
		||||
class Fanotify
 | 
			
		||||
{
 | 
			
		||||
	private int fd = -1; // Приватный дескриптор fanotify; инициализирован -1 (недействительный), чтобы избежать использования до инициализации.
 | 
			
		||||
 | 
			
		||||
	// Конструктор: инициализирует fanotify с заданными флагами.
 | 
			
		||||
	// initFlags: флаги для fanotify_init (например, FAN_CLASS_NOTIF для уведомлений без контроля доступа).
 | 
			
		||||
	// eventFFlags: флаги для событий (по умолчанию O_RDONLY | O_LARGEFILE для чтения больших файлов).
 | 
			
		||||
	this(uint initFlags, uint eventFFlags = O_RDONLY | O_LARGEFILE)
 | 
			
		||||
	{
 | 
			
		||||
		fd = fanotify_init(initFlags, eventFFlags); // Вызов системной функции fanotify_init для создания дескриптора.
 | 
			
		||||
		enforce(fd >= 0, "Ошибка инициализации fanotify: " ~ to!string(fd)); // Проверка: если fd < 0, бросить исключение с сообщением; enforce упрощает обработку ошибок.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Деструктор: автоматически вызывается при уничтожении объекта.
 | 
			
		||||
	~this()
 | 
			
		||||
	{
 | 
			
		||||
		if (fd >= 0) // Проверка, валиден ли fd (чтобы избежать закрытия -1).
 | 
			
		||||
		{
 | 
			
		||||
			close(fd); // Закрытие дескриптора через системный вызов close; освобождает ресурсы.
 | 
			
		||||
			fd = -1; // Установка в -1 для безопасности (хотя объект уничтожается).
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод mark: маркирует путь (файл или директорию) для мониторинга.
 | 
			
		||||
	// markFlags: флаги маркировки (например, FAN_MARK_ADD для добавления, FAN_MARK_ONLYDIR для только директорий).
 | 
			
		||||
	// eventMask: маска событий, которые нужно мониторить (битовая маска, например FAN_OPEN | FAN_MODIFY).
 | 
			
		||||
	// dirFd: дескриптор директории (по умолчанию AT_FDCWD - текущая).
 | 
			
		||||
	// path: путь к маркируемому объекту (null для текущей директории).
 | 
			
		||||
	void mark(uint markFlags, uint64_t eventMask, int dirFd = AT_FDCWD, string path = null)
 | 
			
		||||
	{
 | 
			
		||||
		const(char)* cPath = path ? path.toStringz() : null; // Конвертация пути в C-строку (toStringz добавляет null-терминатор); если path null, то null.
 | 
			
		||||
		int res = fanotify_mark(fd, markFlags, eventMask, dirFd, cPath); // Вызов системной функции fanotify_mark для добавления марки.
 | 
			
		||||
		if (res == -1) // Проверка на ошибку (-1 означает неудачу).
 | 
			
		||||
		{
 | 
			
		||||
			// Сборка детального сообщения об ошибке: включает res, errno и описание от strerror.
 | 
			
		||||
			string errMsg = "Ошибка маркировки fanotify: " ~ to!string(
 | 
			
		||||
				res) ~ " (errno: " ~ to!string(errno) ~ ", " ~ strerror(errno)
 | 
			
		||||
				.fromStringz.to!string ~ ")";
 | 
			
		||||
			throw new Exception(errMsg); // Бросить исключение для прерывания выполнения при ошибке.
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Метод readEvents: читает события из дескриптора fanotify.
 | 
			
		||||
	// bufferSize: размер буфера для чтения (по умолчанию 4096 байт - размер страницы памяти, достаточно для нескольких событий).
 | 
			
		||||
	// Возвращает массив FanotifyEvent; расширен для парсинга дополнительной информации (FID, DFID, NAME).
 | 
			
		||||
	FanotifyEvent[] readEvents(size_t bufferSize = 4096)
 | 
			
		||||
	{
 | 
			
		||||
		ubyte[] buffer = new ubyte[bufferSize]; // Выделение буфера unsigned byte[] для сырых данных (fanotify возвращает байты).
 | 
			
		||||
		ssize_t len = read(fd, buffer.ptr, buffer.length); // Чтение данных из fd через системный вызов read; len - количество прочитанных байт (блокирующий вызов, ждёт событий).
 | 
			
		||||
		if (len <= 0) // Если ничего не прочитано или ошибка, вернуть пустой массив.
 | 
			
		||||
		{
 | 
			
		||||
			return []; // Нет событий или ошибка (не бросаем исключение, чтобы цикл мог продолжаться).
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		FanotifyEvent[] events; // Массив для собранных событий.
 | 
			
		||||
		size_t offset = 0; // Смещение в буфере для парсинга (события идут подряд).
 | 
			
		||||
		while (offset + FAN_EVENT_METADATA_LEN <= len) // Цикл по буферу: пока хватает места для минимальной структуры metadata.
 | 
			
		||||
		{
 | 
			
		||||
			auto meta = cast(fanotify_event_metadata*)(buffer.ptr + offset); // Кастинг байтов в структуру metadata (unsafe, но стандартно для C-API).
 | 
			
		||||
			if (meta.event_len < FAN_EVENT_METADATA_LEN || offset + meta.event_len > len) // Проверка валидности: длина события должна быть >= минимальной и не выходить за буфер.
 | 
			
		||||
			{
 | 
			
		||||
				break; // Если некорректно, прервать цикл (защита от повреждённых данных).
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			FanotifyEvent ev = FanotifyEvent(*meta); // Создание структуры события на основе meta (копирует данные).
 | 
			
		||||
 | 
			
		||||
			// Парсинг дополнительной информации (info blocks): если флаги включают FAN_REPORT_FID/NAME, в буфере после meta идут блоки с FID, NAME и т.д.
 | 
			
		||||
			size_t infoOffset = offset + fanotify_event_metadata.sizeof; // Смещение после meta.
 | 
			
		||||
			while (infoOffset < offset + meta.event_len) // Цикл по блокам info внутри события.
 | 
			
		||||
			{
 | 
			
		||||
				auto hdr = cast(fanotify_event_info_header*)(buffer.ptr + infoOffset); // Кастинг в заголовок info (содержит type и len).
 | 
			
		||||
				if (hdr.len == 0 || infoOffset + hdr.len > offset + meta.event_len) // Проверка валидности блока.
 | 
			
		||||
				{
 | 
			
		||||
					break; // Если некорректно, прервать.
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) // Если тип - DFID + NAME (директория FID + имя).
 | 
			
		||||
				{
 | 
			
		||||
					// Расчёт смещения: пропускаем hdr, fsid (filesystem ID), затем file_handle.
 | 
			
		||||
					size_t fidOffset = infoOffset + fanotify_event_info_header.sizeof + __kernel_fsid_t
 | 
			
		||||
						.sizeof;
 | 
			
		||||
					auto handle = cast(file_handle*)(buffer.ptr + fidOffset); // Кастинг в структуру file_handle (содержит handle_bytes - размер handle).
 | 
			
		||||
					size_t handleEnd = fidOffset + file_handle.sizeof + handle.handle_bytes; // Конец handle в буфере.
 | 
			
		||||
					if (handleEnd < offset + meta.event_len) // Проверка, что за handle есть место для имени.
 | 
			
		||||
					{
 | 
			
		||||
						// Извлечение имени: null-terminated C-строка после handle; конвертируем в string D.
 | 
			
		||||
						ev.name = (cast(char*)(buffer.ptr + handleEnd)).fromStringz.to!string;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				infoOffset += hdr.len; // Переход к следующему блоку info.
 | 
			
		||||
			}
 | 
			
		||||
			events ~= ev; // Добавление parsed события в массив.
 | 
			
		||||
			offset += meta.event_len; // Переход к следующему событию в буфере.
 | 
			
		||||
		}
 | 
			
		||||
		return events; // Возврат массива событий.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Свойство handle: возвращает дескриптор fd для низкоуровневого доступа (если нужно, например, для select или других вызовов).
 | 
			
		||||
	@property int handle() const
 | 
			
		||||
	{
 | 
			
		||||
		return fd; // Просто геттер.
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue