Обычный режим работы
This commit is contained in:
		
							parent
							
								
									aa214dcf96
								
							
						
					
					
						commit
						62be8600c6
					
				
					 4 changed files with 308 additions and 636 deletions
				
			
		
							
								
								
									
										123
									
								
								c/main.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								c/main.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,123 @@
 | 
			
		|||
// gcc -O2 -Wall -Wextra -o main main.c
 | 
			
		||||
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#include <sys/fanotify.h>      // <-- прототипы fanotify_*
 | 
			
		||||
#include <sys/epoll.h>
 | 
			
		||||
#include <sys/uio.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <limits.h>            // PATH_MAX
 | 
			
		||||
 | 
			
		||||
#ifndef AT_FDCWD
 | 
			
		||||
#  include <fcntl.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/* Настройки из твоего D-кода */
 | 
			
		||||
#define INIT_FLAGS  (FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK)
 | 
			
		||||
#define EVENT_FLAGS (O_RDONLY | O_LARGEFILE | O_CLOEXEC)
 | 
			
		||||
#define BASE_FLAGS  (FAN_MARK_ADD)
 | 
			
		||||
#define PERM_MASK   (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM | FAN_OPEN | FAN_CLOSE_WRITE)
 | 
			
		||||
#define BUF_SZ      (64 * 1024)
 | 
			
		||||
 | 
			
		||||
/* Простой лог */
 | 
			
		||||
static void log_line(const char *s) {
 | 
			
		||||
    struct iovec v[2];
 | 
			
		||||
    static const char nl = '\n';
 | 
			
		||||
    v[0].iov_base = (void*)s; v[0].iov_len = strlen(s);
 | 
			
		||||
    v[1].iov_base = (void*)&nl; v[1].iov_len = 1;
 | 
			
		||||
    writev(STDERR_FILENO, v, 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ssize_t fd_path(int fd, char *buf, size_t cap) {
 | 
			
		||||
    char linkpath[64];
 | 
			
		||||
    int m = snprintf(linkpath, sizeof(linkpath), "/proc/self/fd/%d", fd);
 | 
			
		||||
    if (m <= 0 || (size_t)m >= sizeof(linkpath)) return -1;
 | 
			
		||||
    ssize_t r = readlink(linkpath, buf, cap - 1);
 | 
			
		||||
    if (r >= 0) buf[r] = '\0';
 | 
			
		||||
    return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(void) {
 | 
			
		||||
    int fan = fanotify_init(INIT_FLAGS, EVENT_FLAGS);
 | 
			
		||||
    if (fan == -1) { perror("fanotify_init"); return 1; }
 | 
			
		||||
 | 
			
		||||
    /* метим /tmp и /home как файловые системы */
 | 
			
		||||
    if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/tmp") == -1) {
 | 
			
		||||
        perror("fanotify_mark(/tmp)"); return 1;
 | 
			
		||||
    }
 | 
			
		||||
    if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/home") == -1) {
 | 
			
		||||
        perror("fanotify_mark(/home)"); return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int ep = epoll_create1(EPOLL_CLOEXEC);
 | 
			
		||||
    if (ep == -1) { perror("epoll_create1"); return 1; }
 | 
			
		||||
 | 
			
		||||
    struct epoll_event ev = { .events = EPOLLIN | EPOLLONESHOT };
 | 
			
		||||
    ev.data.u64 = 1; /* тег */
 | 
			
		||||
    if (epoll_ctl(ep, EPOLL_CTL_ADD, fan, &ev) == -1) { perror("epoll_ctl ADD"); return 1; }
 | 
			
		||||
 | 
			
		||||
    char *buf = malloc(BUF_SZ);
 | 
			
		||||
    if (!buf) { perror("malloc"); return 1; }
 | 
			
		||||
 | 
			
		||||
    log_line("fanomon: started (watching /tmp and /home)");
 | 
			
		||||
 | 
			
		||||
    for (;;) {
 | 
			
		||||
        struct epoll_event out;
 | 
			
		||||
        int n = epoll_wait(ep, &out, 1, -1);
 | 
			
		||||
        if (n == -1) { if (errno == EINTR) continue; perror("epoll_wait"); break; }
 | 
			
		||||
 | 
			
		||||
        if ((out.events & EPOLLIN) && out.data.u64 == 1) {
 | 
			
		||||
            for (;;) {
 | 
			
		||||
                ssize_t len = read(fan, buf, BUF_SZ);
 | 
			
		||||
                if (len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) break;
 | 
			
		||||
                if (len <= 0) break;
 | 
			
		||||
 | 
			
		||||
                struct fanotify_event_metadata *meta;
 | 
			
		||||
                for (meta = (struct fanotify_event_metadata*)buf;
 | 
			
		||||
                     FAN_EVENT_OK(meta, len);
 | 
			
		||||
                     meta = FAN_EVENT_NEXT(meta, len)) {
 | 
			
		||||
 | 
			
		||||
                    if (meta->vers != FANOTIFY_METADATA_VERSION) { log_line("metadata version mismatch"); continue; }
 | 
			
		||||
                    if (meta->mask & FAN_Q_OVERFLOW)             { log_line("queue overflow"); continue; }
 | 
			
		||||
 | 
			
		||||
                    /* Быстро отвечаем на PERM-события */
 | 
			
		||||
                    if (meta->mask & (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM)) {
 | 
			
		||||
                        struct fanotify_response resp = { .fd = meta->fd, .response = FAN_ALLOW };
 | 
			
		||||
                        if (write(fan, &resp, sizeof(resp)) == -1) perror("fanotify_response");
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    /* Лёгкий лог пути (после ответа) */
 | 
			
		||||
                    if (meta->fd >= 0) {
 | 
			
		||||
                        char path[PATH_MAX];
 | 
			
		||||
                        if (fd_path(meta->fd, path, sizeof(path)) >= 0) {
 | 
			
		||||
                            char line[PATH_MAX + 16];
 | 
			
		||||
                            const char *tag =
 | 
			
		||||
                                (meta->mask & FAN_OPEN)             ? "OPEN  " :
 | 
			
		||||
                                (meta->mask & FAN_CLOSE_WRITE)      ? "CLOSEW" :
 | 
			
		||||
                                (meta->mask & FAN_ACCESS_PERM)      ? "APERM " :
 | 
			
		||||
                                (meta->mask & FAN_OPEN_PERM)        ? "OPERM " :
 | 
			
		||||
                                (meta->mask & FAN_OPEN_EXEC_PERM)   ? "XPERM " : "EVENT ";
 | 
			
		||||
                            snprintf(line, sizeof(line), "%s %s", tag, path);
 | 
			
		||||
                            log_line(line);
 | 
			
		||||
                        }
 | 
			
		||||
                        close(meta->fd);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* Реарм ONE_SHOT */
 | 
			
		||||
            ev.events = EPOLLIN | EPOLLONESHOT;
 | 
			
		||||
            ev.data.u64 = 1;
 | 
			
		||||
            if (epoll_ctl(ep, EPOLL_CTL_MOD, fan, &ev) == -1) { perror("epoll_ctl MOD"); break; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    free(buf);
 | 
			
		||||
    close(ep);
 | 
			
		||||
    close(fan);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										483
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										483
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,182 +1,200 @@
 | 
			
		|||
module app;
 | 
			
		||||
public import fanotify;
 | 
			
		||||
 | 
			
		||||
import dfanotify;
 | 
			
		||||
import fanotify;
 | 
			
		||||
import depoll;
 | 
			
		||||
 | 
			
		||||
import core.sys.posix.fcntl : O_RDONLY, O_LARGEFILE, O_CLOEXEC, AT_FDCWD;
 | 
			
		||||
import core.sys.posix.unistd : read, close;
 | 
			
		||||
import core.sys.posix.sys.stat : fstat, stat_t;
 | 
			
		||||
import core.sys.posix.unistd : read, write, close, ssize_t;
 | 
			
		||||
import core.sys.posix.fcntl : O_RDONLY, O_RDWR, O_LARGEFILE, AT_FDCWD, O_CLOEXEC;
 | 
			
		||||
import std.exception : enforce, collectException;
 | 
			
		||||
import std.string : toStringz, fromStringz;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
import core.stdc.errno : errno, EINTR;
 | 
			
		||||
import core.stdc.string : strerror;
 | 
			
		||||
import std.stdio : writeln, writefln, stderr;
 | 
			
		||||
import std.file : isDir, readText, exists;
 | 
			
		||||
import std.string : strip, splitLines, startsWith, toStringz, fromStringz, split;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
import std.exception : enforce;
 | 
			
		||||
import std.getopt : getopt, defaultGetoptPrinter;
 | 
			
		||||
import core.stdc.stdint;
 | 
			
		||||
import std.stdio : writeln;
 | 
			
		||||
import std.format : format;
 | 
			
		||||
import core.sys.linux.epoll : EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLRDHUP, EPOLL_CLOEXEC;
 | 
			
		||||
import std.file : readLink, FileException;
 | 
			
		||||
 | 
			
		||||
// Переписать
 | 
			
		||||
import core.sys.posix.sys.types : uid_t;
 | 
			
		||||
import core.sys.posix.pwd : passwd, getpwuid_r;
 | 
			
		||||
import core.stdc.stdlib : malloc, free;
 | 
			
		||||
import core.stdc.string : strlen;
 | 
			
		||||
 | 
			
		||||
/// ---- syscall + pidfd_open ----
 | 
			
		||||
extern (C) long syscall(long number, ...);
 | 
			
		||||
version (X86_64) enum __NR_pidfd_open = 434;
 | 
			
		||||
extern (C) int pidfd_open(int pid, uint flags)
 | 
			
		||||
{
 | 
			
		||||
	return cast(int) syscall(__NR_pidfd_open, pid, flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- readlink по fd ----
 | 
			
		||||
extern (C) long readlink(const char* path, char* buf, size_t bufsiz);
 | 
			
		||||
string readlinkFdPath(int fd)
 | 
			
		||||
{
 | 
			
		||||
	char[4_096] buf;
 | 
			
		||||
	auto link = format("/proc/self/fd/%d", fd);
 | 
			
		||||
	auto n = readlink(link.toStringz, buf.ptr, buf.length.to!int - 1);
 | 
			
		||||
	if (n <= 0)
 | 
			
		||||
		return "";
 | 
			
		||||
	buf[n] = 0;
 | 
			
		||||
	return buf.ptr.fromStringz.idup.strip;
 | 
			
		||||
	string link = "/proc/self/fd/%d".format(fd);
 | 
			
		||||
	string path;
 | 
			
		||||
	// Заглушка для FileException
 | 
			
		||||
	collectException!FileException(path = link.readLink());
 | 
			
		||||
	return path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- имя процесса и UIDы (ruid из status, uid из loginuid) ----
 | 
			
		||||
struct ProcIds
 | 
			
		||||
{
 | 
			
		||||
	uint ruid;
 | 
			
		||||
	uint uid;
 | 
			
		||||
} // uid = loginuid
 | 
			
		||||
struct FanotifyMetadataRange {
 | 
			
		||||
    private fanotify_event_metadata* current; // Current position in buffer.
 | 
			
		||||
    private size_t remainingLen;               // Remaining bytes in buffer from current.
 | 
			
		||||
 | 
			
		||||
string readProcComm(int pid)
 | 
			
		||||
{
 | 
			
		||||
	auto p = format("/proc/%s/comm", pid);
 | 
			
		||||
	string s;
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		s = readText(p);
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		return "";
 | 
			
		||||
	}
 | 
			
		||||
	return s.strip;
 | 
			
		||||
    // Private helper functions (now non-static const methods inside the class):
 | 
			
		||||
    private @nogc nothrow @trusted
 | 
			
		||||
    bool FAN_EVENT_OK(const(fanotify_event_metadata)* meta, size_t len) const {
 | 
			
		||||
        enum long BASE = cast(long) FAN_EVENT_METADATA_LEN;
 | 
			
		||||
        const long L = cast(long) len;
 | 
			
		||||
        const long EL = cast(long) meta.event_len;
 | 
			
		||||
        return (L >= BASE) && (EL >= BASE) && (EL <= L);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private @nogc nothrow @trusted
 | 
			
		||||
    fanotify_event_metadata* FAN_EVENT_NEXT(fanotify_event_metadata* meta, ref size_t len) const {
 | 
			
		||||
        const uint step = meta.event_len;
 | 
			
		||||
        len -= step;
 | 
			
		||||
        return cast(fanotify_event_metadata*)((cast(ubyte*) meta) + step);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Constructor: Takes the buffer slice and read length.
 | 
			
		||||
    this(ubyte[] buffer, size_t len) @nogc nothrow @trusted {
 | 
			
		||||
        if (len < FAN_EVENT_METADATA_LEN) {
 | 
			
		||||
            remainingLen = 0; // Empty if too small.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        current = cast(fanotify_event_metadata*) buffer.ptr;
 | 
			
		||||
        remainingLen = len;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Range primitives:
 | 
			
		||||
    @nogc nothrow @trusted
 | 
			
		||||
    bool empty() const {
 | 
			
		||||
        return !FAN_EVENT_OK(current, remainingLen);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @nogc nothrow @trusted
 | 
			
		||||
    ref const(fanotify_event_metadata) front() const {
 | 
			
		||||
        assert(!empty, "Range is empty");
 | 
			
		||||
        return *current; // Returns by const ref to avoid copies.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @nogc nothrow @trusted
 | 
			
		||||
    void popFront() {
 | 
			
		||||
        assert(!empty, "Range is empty");
 | 
			
		||||
        current = FAN_EVENT_NEXT(current, remainingLen);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint readLoginUid(int pid)
 | 
			
		||||
// Класс для представления события fanotify (ООП-стиль, с методами для проверки и обработки)
 | 
			
		||||
struct FanotifyEvent
 | 
			
		||||
{
 | 
			
		||||
	auto p = format("/proc/%s/loginuid", pid);
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		auto s = readText(p).strip;
 | 
			
		||||
		// loginuid может быть "-1" (unset). В этом случае вернём 4294967295 или 0 — выбери политику.
 | 
			
		||||
		if (s.length && s[0] == '-')
 | 
			
		||||
			return uint.max; // помечаем как "нет"
 | 
			
		||||
		return s.to!uint;
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		return uint.max; // нет файла или отказ — считаем не установленным
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
	private fanotify_event_metadata _meta;
 | 
			
		||||
	private int _fanFd;
 | 
			
		||||
 | 
			
		||||
ProcIds readProcIds(int pid)
 | 
			
		||||
{
 | 
			
		||||
	uint ruid = 0;
 | 
			
		||||
	auto p = format("/proc/%s/status", pid);
 | 
			
		||||
	string s;
 | 
			
		||||
	try
 | 
			
		||||
	// Деструктор: автоматически закрывает fd события, если он валиден (RAII)
 | 
			
		||||
	// ~this()
 | 
			
		||||
	// {
 | 
			
		||||
	// 	if (_meta.fd >= 0 && _meta.fd != FAN_NOFD)
 | 
			
		||||
	// 	{
 | 
			
		||||
	// 		close(_meta.fd);
 | 
			
		||||
	// 	}
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
	// Геттеры (value types)
 | 
			
		||||
	@property uint64_t mask() const { return _meta.mask; }
 | 
			
		||||
	@property int eventFd() const { return _meta.fd; }
 | 
			
		||||
	@property int pid() const { return _meta.pid; }
 | 
			
		||||
 | 
			
		||||
	// Методы проверки событий
 | 
			
		||||
	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; }
 | 
			
		||||
 | 
			
		||||
	// Метод для отправки response (для permission-событий), закрывает fd автоматически после
 | 
			
		||||
	void respond(uint response)
 | 
			
		||||
	{
 | 
			
		||||
		s = readText(p);
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		return ProcIds(0, readLoginUid(pid));
 | 
			
		||||
	}
 | 
			
		||||
	foreach (line; s.splitLines)
 | 
			
		||||
	{
 | 
			
		||||
		if (line.startsWith("Uid:"))
 | 
			
		||||
		if (eventFd < 0 || eventFd == FAN_NOFD)
 | 
			
		||||
		{
 | 
			
		||||
			// Uid:  RUID  EUID  SUID  FSUID
 | 
			
		||||
			auto parts = line.split;
 | 
			
		||||
			if (parts.length >= 5)
 | 
			
		||||
			{
 | 
			
		||||
				ruid = parts[1].to!uint;
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
			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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ProcIds(ruid, readLoginUid(pid));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- ключ карты соответствий: (pid, dev, ino) ----
 | 
			
		||||
struct DevIno
 | 
			
		||||
{
 | 
			
		||||
	ulong dev;
 | 
			
		||||
	ulong ino;
 | 
			
		||||
	int pid;
 | 
			
		||||
	bool opEquals(const DevIno rhs) const @safe nothrow
 | 
			
		||||
	// Метод для добавления/удаления/модификации меток (управление событиями)
 | 
			
		||||
	void mark(uint markFlags, uint64_t eventMask, int dirFd = AT_FDCWD, string path = null)
 | 
			
		||||
	{
 | 
			
		||||
		return dev == rhs.dev && ino == rhs.ino && pid == rhs.pid;
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t toHash() const @safe nothrow
 | 
			
		||||
	// Метод для чтения событий (возвращает массив объектов событий)
 | 
			
		||||
	FanotifyEvent[] readEvents(size_t bufferSize = 4096)
 | 
			
		||||
	{
 | 
			
		||||
		return (dev * 1_315_423_911UL) ^ (ino * 2_654_435_761UL) ^ (cast(size_t) pid);
 | 
			
		||||
		size_t sz = bufferSize;
 | 
			
		||||
		if (sz < FAN_EVENT_METADATA_LEN) sz = FAN_EVENT_METADATA_LEN;
 | 
			
		||||
 | 
			
		||||
		ubyte[] buffer = new ubyte[sz];
 | 
			
		||||
 | 
			
		||||
		ssize_t len;
 | 
			
		||||
		while (true)
 | 
			
		||||
		{
 | 
			
		||||
			len = read(_fd, buffer.ptr, buffer.length);
 | 
			
		||||
			if (len < 0 && errno == EINTR) continue;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		if (len <= 0) return [];
 | 
			
		||||
 | 
			
		||||
		auto range = FanotifyMetadataRange(buffer, cast(size_t) len);
 | 
			
		||||
 | 
			
		||||
		import std.array : Appender;
 | 
			
		||||
		auto events = Appender!(FanotifyEvent[])();
 | 
			
		||||
		// FanotifyEvent[] events;
 | 
			
		||||
		foreach (ref const meta; range) {
 | 
			
		||||
			events ~= FanotifyEvent(meta, _fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return events.data;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Геттер для fd (если нужно внешне, но лучше использовать методы)
 | 
			
		||||
	@property int handle() const
 | 
			
		||||
	{
 | 
			
		||||
		return _fd;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool getDevIno(int fd, out DevIno di, int pid)
 | 
			
		||||
{
 | 
			
		||||
	stat_t st;
 | 
			
		||||
	if (fstat(fd, &st) != 0)
 | 
			
		||||
		return false;
 | 
			
		||||
	di = DevIno(cast(ulong) st.st_dev, cast(ulong) st.st_ino, pid);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- сессия файла для конкретного PID ----
 | 
			
		||||
struct Session
 | 
			
		||||
{
 | 
			
		||||
	string procName;
 | 
			
		||||
	uint ruid; // из /proc/PID/status (Uid:)
 | 
			
		||||
	uint uid; // LOGINUID из /proc/PID/loginuid
 | 
			
		||||
	string filePathOpen; // путь, снятый на PERM
 | 
			
		||||
	bool changed; // был CLOSE_WRITE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__gshared Session[DevIno] gMap; // (pid,dev,ino) -> Session
 | 
			
		||||
__gshared int[int] gPidfdByPid; // pid -> pidfd
 | 
			
		||||
 | 
			
		||||
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 "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- журнал: proc, file, uid(loginuid), ruid ----
 | 
			
		||||
void logChange(string procName, string filePath, uint uid, uint ruid)
 | 
			
		||||
{
 | 
			
		||||
	writefln(`proc="%s" file="%s" user=%s(%s) realUser=%s(%s)`,
 | 
			
		||||
		procName, filePath, userName(uid), uid, userName(ruid), ruid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- маска на КАТАЛОГ ----
 | 
			
		||||
ulong dirMask()
 | 
			
		||||
{
 | 
			
		||||
	return FAN_EVENT_ON_CHILD |
 | 
			
		||||
| 
						 | 
				
			
			@ -184,163 +202,32 @@ ulong dirMask()
 | 
			
		|||
		FAN_OPEN | FAN_CLOSE_WRITE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- обработчики ----
 | 
			
		||||
void onOpenPerm(FanotifyEvent ev, Epoll ep)
 | 
			
		||||
{
 | 
			
		||||
	const pid = ev.pid;
 | 
			
		||||
	auto procName = readProcComm(pid);
 | 
			
		||||
	auto ids = readProcIds(pid); // <-- ruid + loginuid
 | 
			
		||||
 | 
			
		||||
	DevIno key;
 | 
			
		||||
	if (!getDevIno(ev.eventFd, key, pid))
 | 
			
		||||
	{
 | 
			
		||||
		ev.respond(FAN_ALLOW);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto path = readlinkFdPath(ev.eventFd);
 | 
			
		||||
 | 
			
		||||
	if (auto ps = key in gMap)
 | 
			
		||||
	{
 | 
			
		||||
		// уже есть
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		gMap[key] = Session(procName, ids.ruid, ids.uid, path, false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(pid in gPidfdByPid))
 | 
			
		||||
	{
 | 
			
		||||
		int pfd = -1;
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			pfd = pidfd_open(pid, 0); // на твоей системе так ок
 | 
			
		||||
			if (pfd < 0)
 | 
			
		||||
				writeln("pidfd_open failed: ", strerror(errno).fromStringz);
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception e)
 | 
			
		||||
		{
 | 
			
		||||
			pfd = -1;
 | 
			
		||||
		}
 | 
			
		||||
		if (pfd >= 0)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				ep.add(pfd, cast(uint) pid, EPOLLIN | EPOLLHUP | EPOLLRDHUP | EPOLLERR);
 | 
			
		||||
				gPidfdByPid[pid] = pfd;
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception e)
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ev.respond(FAN_ALLOW);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void onCloseWrite(FanotifyEvent ev)
 | 
			
		||||
{
 | 
			
		||||
	DevIno key;
 | 
			
		||||
	if (!getDevIno(ev.eventFd, key, ev.pid))
 | 
			
		||||
		return;
 | 
			
		||||
	if (auto ps = key in gMap)
 | 
			
		||||
		ps.changed = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void flushAndClearForPid(int pid, Epoll ep)
 | 
			
		||||
{
 | 
			
		||||
	auto keys = gMap.keys.dup;
 | 
			
		||||
	foreach (k; keys)
 | 
			
		||||
	{
 | 
			
		||||
		if (k.pid != pid)
 | 
			
		||||
			continue;
 | 
			
		||||
		auto s = gMap[k];
 | 
			
		||||
		if (s.changed)
 | 
			
		||||
			logChange(s.procName, s.filePathOpen, s.uid, s.ruid);
 | 
			
		||||
		gMap.remove(k);
 | 
			
		||||
	}
 | 
			
		||||
	if (auto p = pid in gPidfdByPid)
 | 
			
		||||
	{
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			ep.remove(*p);
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception e)
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
		close(*p);
 | 
			
		||||
		gPidfdByPid.remove(pid);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ---- main ----
 | 
			
		||||
enum uint FAN_TAG = 0;
 | 
			
		||||
 | 
			
		||||
void main(string[] args)
 | 
			
		||||
{
 | 
			
		||||
	string watchDir;
 | 
			
		||||
 | 
			
		||||
	auto help = getopt(args, "dir|d", &watchDir);
 | 
			
		||||
	if (help.helpWanted || watchDir.length == 0)
 | 
			
		||||
	{
 | 
			
		||||
		defaultGetoptPrinter("Usage: app -d <dir-to-watch>", help.options);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	enforce(isDir(watchDir), "Ожидается путь к КАТАЛОГУ (-d)");
 | 
			
		||||
 | 
			
		||||
	enum initFlags = FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK;
 | 
			
		||||
	auto fan = new Fanotify(initFlags, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
 | 
			
		||||
 | 
			
		||||
	try
 | 
			
		||||
	{
 | 
			
		||||
		fan.mark(FAN_MARK_ADD | FAN_MARK_ONLYDIR, dirMask(), AT_FDCWD, watchDir);
 | 
			
		||||
	}
 | 
			
		||||
	catch (Exception e)
 | 
			
		||||
	{
 | 
			
		||||
		stderr.writefln("fanotify_mark failed: %s", e.msg);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto ep = new Epoll(EPOLL_CLOEXEC);
 | 
			
		||||
	ep.add(fan.handle, FAN_TAG, EPOLLIN | EPOLLERR | EPOLLHUP);
 | 
			
		||||
 | 
			
		||||
	writeln("watching dir: ", watchDir);
 | 
			
		||||
	// fan.mark(FAN_MARK_ADD | FAN_MARK_FILESYSTEM, dirMask(), AT_FDCWD, "/home");
 | 
			
		||||
	fan.mark(FAN_MARK_ADD | FAN_MARK_FILESYSTEM, dirMask(), AT_FDCWD, "/tmp");
 | 
			
		||||
 | 
			
		||||
	for (;;)
 | 
			
		||||
	{
 | 
			
		||||
		auto evs = ep.wait(256, -1);
 | 
			
		||||
		if (evs.length == 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		foreach (e; evs)
 | 
			
		||||
		auto list = fan.readEvents(8_192);
 | 
			
		||||
		foreach (fev; list)
 | 
			
		||||
		{
 | 
			
		||||
			if (e.tag == FAN_TAG)
 | 
			
		||||
			if (fev.isOverflow)
 | 
			
		||||
			{
 | 
			
		||||
				auto list = fan.readEvents(8_192);
 | 
			
		||||
				foreach (fev; list)
 | 
			
		||||
				{
 | 
			
		||||
					if (fev.isOverflow)
 | 
			
		||||
					{
 | 
			
		||||
						writeln(
 | 
			
		||||
							"FAN_Q_OVERFLOW — возможна потеря корреляции");
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
					if (fev.isOpenPerm || fev.isAccessPerm || fev.isOpenExecPerm)
 | 
			
		||||
					{
 | 
			
		||||
						onOpenPerm(fev, ep);
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
					if (fev.isCloseWrite)
 | 
			
		||||
					{
 | 
			
		||||
						onCloseWrite(fev);
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			if (fev.isOpenPerm || fev.isAccessPerm || fev.isOpenExecPerm)
 | 
			
		||||
			{
 | 
			
		||||
				auto pid = cast(int) e.tag;
 | 
			
		||||
				flushAndClearForPid(pid, ep);
 | 
			
		||||
				writeln(fev.eventFd.readlinkFdPath);
 | 
			
		||||
				fev.respond(FAN_ALLOW);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			if (fev.isCloseWrite)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,86 +0,0 @@
 | 
			
		|||
module depoll;
 | 
			
		||||
 | 
			
		||||
import core.sys.linux.epoll;
 | 
			
		||||
import core.sys.posix.unistd : close;
 | 
			
		||||
import core.stdc.errno : errno, EINTR, EEXIST;
 | 
			
		||||
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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Добавление дескриптора с произвольным tag (например, tag=0 для fanotify, tag=pid для pidfd)
 | 
			
		||||
	void add(int fd, uint tag, uint events = EPOLLIN | EPOLLERR | EPOLLHUP)
 | 
			
		||||
	{
 | 
			
		||||
		epoll_event ev;
 | 
			
		||||
		ev.events = events;
 | 
			
		||||
		ev.data.u32 = tag;
 | 
			
		||||
		auto rc = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
 | 
			
		||||
		if (rc != 0 && errno == EEXIST)
 | 
			
		||||
		{
 | 
			
		||||
			// Если уже добавлен — делаем MOD
 | 
			
		||||
			rc = epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev);
 | 
			
		||||
		}
 | 
			
		||||
		enforce(rc == 0, "epoll_ctl ADD/MOD: " ~ strerror(errno).fromStringz.idup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Опционально: явное удаление
 | 
			
		||||
	void remove(int fd)
 | 
			
		||||
	{
 | 
			
		||||
		auto rc = epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, null);
 | 
			
		||||
		enforce(rc == 0, "epoll_ctl DEL: " ~ strerror(errno).fromStringz.idup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct Event
 | 
			
		||||
	{
 | 
			
		||||
		uint tag; // то самое, что передавали в add()
 | 
			
		||||
		uint events; // EPOLL* биты
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Возвращает события; пустой массив — если таймаут/прерывание
 | 
			
		||||
	Event[] wait(int maxevents = 16, int timeout = -1)
 | 
			
		||||
	{
 | 
			
		||||
		epoll_event[] evs = new epoll_event[maxevents];
 | 
			
		||||
		int n;
 | 
			
		||||
		// Терпим EINTR и повторяем
 | 
			
		||||
		while (true)
 | 
			
		||||
		{
 | 
			
		||||
			n = epoll_wait(epfd_, evs.ptr, maxevents, timeout);
 | 
			
		||||
			if (n < 0 && errno == EINTR)
 | 
			
		||||
				continue;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		if (n <= 0)
 | 
			
		||||
			return [];
 | 
			
		||||
 | 
			
		||||
		Event[] res;
 | 
			
		||||
		res.reserve(n);
 | 
			
		||||
		foreach (i; 0 .. n)
 | 
			
		||||
		{
 | 
			
		||||
			res ~= Event(evs[i].data.u32, evs[i].events);
 | 
			
		||||
		}
 | 
			
		||||
		return res;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@property int handle() const
 | 
			
		||||
	{
 | 
			
		||||
		return epfd_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,252 +0,0 @@
 | 
			
		|||
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_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue