Мониторинг с несколькими дескрипторами + новый модуль depoll
This commit is contained in:
		
							parent
							
								
									cb3f40deee
								
							
						
					
					
						commit
						7858416df9
					
				
					 2 changed files with 285 additions and 50 deletions
				
			
		
							
								
								
									
										262
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										262
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,77 +1,239 @@
 | 
			
		|||
import dfanotify;
 | 
			
		||||
module app;
 | 
			
		||||
 | 
			
		||||
import std.stdio;
 | 
			
		||||
import std.format : format;
 | 
			
		||||
import core.sys.posix.fcntl : AT_FDCWD;
 | 
			
		||||
import core.stdc.stdlib : exit;
 | 
			
		||||
import dfanotify; // твой модуль сверху
 | 
			
		||||
import depoll; // новый модуль для epoll
 | 
			
		||||
 | 
			
		||||
void main()
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
	Fanotify fan;
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		fan = new Fanotify(
 | 
			
		||||
			FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		writeln(e.msg);
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
	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";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	auto eventMask = FAN_OPEN | FAN_MODIFY | FAN_CLOSE | FAN_CREATE | FAN_DELETE | FAN_EVENT_ON_CHILD | FAN_ACCESS;
 | 
			
		||||
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
 | 
			
		||||
	{
 | 
			
		||||
		fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, eventMask, AT_FDCWD, "/tmp/scripts");
 | 
			
		||||
		pre = new Fanotify(preInitFlags, O_RDONLY | O_LARGEFILE);
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		writeln(e.msg);
 | 
			
		||||
		exit(1);
 | 
			
		||||
		writeln("pre: ", e.msg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeln(
 | 
			
		||||
		"Мониторинг с разрешениями запущен для /tmp/scripts...");
 | 
			
		||||
	// 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 (e; events)
 | 
			
		||||
		foreach (epEv; epEvents)
 | 
			
		||||
		{
 | 
			
		||||
			string path = e.name.length ? e.name : "unknown";
 | 
			
		||||
			auto tag = epEv.tag;
 | 
			
		||||
 | 
			
		||||
			writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path);
 | 
			
		||||
 | 
			
		||||
			// Обработка permission-события
 | 
			
		||||
			if (e.isOpenPerm)
 | 
			
		||||
			// Считываем пачку событий с соответствующего fd
 | 
			
		||||
			FanotifyEvent[] events;
 | 
			
		||||
			if (tag == TAG_PRE)
 | 
			
		||||
			{
 | 
			
		||||
				writeln(" - Запрос на открытие файла. Отклоняем (предполагаем попытку записи).");
 | 
			
		||||
				e.respond(FAN_DENY); // Отклонить (для FAN_ALLOW используйте FAN_ALLOW)
 | 
			
		||||
				events = pre.readEvents(64 * 1024);
 | 
			
		||||
			}
 | 
			
		||||
			else if (tag == TAG_NOTIF)
 | 
			
		||||
			{
 | 
			
		||||
				events = notif.readEvents(64 * 1024);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				// Обычные уведомления (не permission)
 | 
			
		||||
				if (e.isOpen)
 | 
			
		||||
					writeln(" - Открытие файла");
 | 
			
		||||
				if (e.isAccess)
 | 
			
		||||
					writeln(" - Доступ к файлу");
 | 
			
		||||
				if (e.isModify)
 | 
			
		||||
					writeln(" - Модификация файла");
 | 
			
		||||
				if (e.isCloseWrite)
 | 
			
		||||
					writeln(" - Закрытие после записи");
 | 
			
		||||
				if (e.isCloseNoWrite)
 | 
			
		||||
					writeln(" - Закрытие без записи");
 | 
			
		||||
				if (e.isCreate)
 | 
			
		||||
					writeln(" - Создание файла/директории");
 | 
			
		||||
				if (e.isDelete)
 | 
			
		||||
					writeln(" - Удаление файла/директории");
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Опционально: вызов постобработки
 | 
			
		||||
			e.postProcess();
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue