Compare commits
	
		
			10 commits
		
	
	
		
			master
			...
			dev-scheme
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 62be8600c6 | |||
| aa214dcf96 | |||
| 377d5c1f94 | |||
| 2876a45a85 | |||
| bc02ac8518 | |||
| 1f78d8eb83 | |||
| 7858416df9 | |||
| cb3f40deee | |||
| ed0d2780c3 | |||
| 582ae5a294 | 
					 7 changed files with 419 additions and 261 deletions
				
			
		
							
								
								
									
										2
									
								
								.vscode/launch.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -20,7 +20,7 @@
 | 
			
		|||
			"type": "cppdbg",
 | 
			
		||||
			"request": "launch",
 | 
			
		||||
			"program": "${workspaceFolder}/bin/dwatch",
 | 
			
		||||
			"args": [], // Аргументы командной строки для программы, если нужны
 | 
			
		||||
			"args": ["-d", "/tmp/scripts"], // Аргументы командной строки для программы, если нужны
 | 
			
		||||
			"stopAtEntry": false, // Остановить на входе в main
 | 
			
		||||
			"cwd": "${workspaceFolder}",
 | 
			
		||||
			"environment": [],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										74
									
								
								scheme.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								scheme.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
```mermaid
 | 
			
		||||
sequenceDiagram
 | 
			
		||||
%%{init: { 'theme': 'default', 'themeVariables': {
 | 
			
		||||
    'primaryColor': '#ff0000',
 | 
			
		||||
    'nodeTextColor': '#ffffff',
 | 
			
		||||
    'edgeLabelBackground': '#f0f0f0'
 | 
			
		||||
}}}%%
 | 
			
		||||
    autonumber
 | 
			
		||||
    participant Watcher as Демон-наблюдатель
 | 
			
		||||
    participant Kernel as Ядро (fanotify)
 | 
			
		||||
    participant Editor as Процесс-редактор (PID X)
 | 
			
		||||
    participant FS as Файловая система (целевой каталог/файл)
 | 
			
		||||
    participant Ep as epoll
 | 
			
		||||
    participant Pfd as pidfd(PID X)
 | 
			
		||||
    participant Map as Карта соответствий (в памяти)
 | 
			
		||||
    participant ChLog as Журнал изменений
 | 
			
		||||
 | 
			
		||||
    Note over Watcher: Инициализация
 | 
			
		||||
    Watcher->>Kernel: fanotify_init(PRE_CONTENT|NONBLOCK|CLOEXEC, O_RDONLY|O_CLOEXEC|O_LARGEFILE)
 | 
			
		||||
    Watcher->>Kernel: fanotify_mark(ADD|ONLYDIR, EVENT_ON_CHILD | OPEN[_EXEC]_PERM | OPEN | CLOSE_WRITE, watchDir)
 | 
			
		||||
    Watcher->>Ep: add(fanotify_fd, tag=FAN)
 | 
			
		||||
 | 
			
		||||
    Note over Editor: Запрос доступа к файлу
 | 
			
		||||
    Editor->>FS: open(target, O_RDONLY/O_RDWR/EXEC)
 | 
			
		||||
    FS-->>Kernel: системный вызов доступа
 | 
			
		||||
    Kernel-->>Watcher: PERM-событие (+fd, mask, pid)
 | 
			
		||||
 | 
			
		||||
    Note over Watcher: Сбор контекста
 | 
			
		||||
    Watcher->>FS: fstat(fd) → (inode, dev)
 | 
			
		||||
    FS-->>Watcher: inode, dev
 | 
			
		||||
    Watcher->>Watcher: readlink(/proc/self/fd/FD) → file_path_open
 | 
			
		||||
    Watcher->>Watcher: read /proc/PID/comm → proc_name
 | 
			
		||||
    Watcher->>Watcher: read /proc/PID/status(Uid:) → RUID
 | 
			
		||||
    Watcher->>Watcher: read /proc/PID/loginuid → UID(loginuid)
 | 
			
		||||
 | 
			
		||||
    %% Кладём связку
 | 
			
		||||
    Watcher->>Map: PUT key=(PID,inode,dev), val={proc_name, RUID, UID, file_path_open, changed=false}
 | 
			
		||||
    Note over Map: Добавлена связка key=(PID,inode,dev)
 | 
			
		||||
 | 
			
		||||
    %% pidfd и epoll
 | 
			
		||||
    Watcher->>Pfd: pidfd_open(PID, flags=0)
 | 
			
		||||
    Pfd-->>Watcher: pidfd
 | 
			
		||||
    Watcher->>Ep: add(pidfd, tag=PID, events=IN|HUP|RDHUP|ERR)
 | 
			
		||||
 | 
			
		||||
    %% Разрешаем доступ
 | 
			
		||||
    Watcher->>Kernel: fanotify_response{fd, FAN_ALLOW}
 | 
			
		||||
    Watcher->>FS: close(fd)
 | 
			
		||||
 | 
			
		||||
    Note over Editor: Работа с файлом
 | 
			
		||||
    Editor->>FS: write(...)
 | 
			
		||||
    Editor->>FS: close()
 | 
			
		||||
    Kernel-->>Watcher: CLOSE_WRITE (+fd)
 | 
			
		||||
 | 
			
		||||
    Note over Watcher: Фиксация изменения
 | 
			
		||||
    Watcher->>FS: fstat(fd) → (inode, dev)
 | 
			
		||||
    FS-->>Watcher: inode, dev
 | 
			
		||||
    Watcher->>Map: GET key=(PID,inode,dev)
 | 
			
		||||
    Map-->>Watcher: {…}
 | 
			
		||||
    Watcher->>Map: SET changed=true
 | 
			
		||||
    Watcher->>FS: close(fd)
 | 
			
		||||
 | 
			
		||||
    Note over Editor: Завершение процесса
 | 
			
		||||
    Editor->>Editor: exit()
 | 
			
		||||
    Pfd-->>Ep: EPOLLIN/HUP (процесс завершён)
 | 
			
		||||
    Ep-->>Watcher: событие(tag=PID)
 | 
			
		||||
 | 
			
		||||
    %% Пишем журнал и чистим
 | 
			
		||||
    Watcher->>Map: GET_ALL where pid=PID
 | 
			
		||||
    Map-->>Watcher: список записей
 | 
			
		||||
    Watcher->>ChLog: APPEND для changed=true → {proc=proc_name, file=file_path_open, uid=UID(loginuid), ruid=RUID}
 | 
			
		||||
    Watcher->>Ep: remove(pidfd); Watcher->>Pfd: close(pidfd)
 | 
			
		||||
    Watcher->>Map: DELETE_ALL where pid=PID
 | 
			
		||||
    Note over Map: Удалены все связки для PID
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										275
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										275
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,71 +1,234 @@
 | 
			
		|||
// Модуль app.d
 | 
			
		||||
// Это основное приложение, демонстрирующее использование обёртки fanotify_wrapper.
 | 
			
		||||
// Инициализирует fanotify, маркирует директорию /tmp/scripts для мониторинга событий (открытие, модификация и т.д.),
 | 
			
		||||
// затем в бесконечном цикле читает события и выводит информацию о них в консоль.
 | 
			
		||||
// Импорты: fanotify_wrapper - обёртка, std.stdio для вывода, std.file для readLink (хотя здесь не используется из-за режима),
 | 
			
		||||
// std.format для форматирования строк, core.sys.posix для констант.
 | 
			
		||||
public import fanotify;
 | 
			
		||||
 | 
			
		||||
import fanotify_wrapper; // Импорт обёртки для fanotify.
 | 
			
		||||
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 core.stdc.stdint;
 | 
			
		||||
import std.stdio : writeln;
 | 
			
		||||
import std.format : format;
 | 
			
		||||
import std.file : readLink, FileException;
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
// Функция main: точка входа приложения.
 | 
			
		||||
void main()
 | 
			
		||||
string readlinkFdPath(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);
 | 
			
		||||
	string link = "/proc/self/fd/%d".format(fd);
 | 
			
		||||
	string path;
 | 
			
		||||
	// Заглушка для FileException
 | 
			
		||||
	collectException!FileException(path = link.readLink());
 | 
			
		||||
	return path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// Определение маски событий: битовая 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;
 | 
			
		||||
struct FanotifyMetadataRange {
 | 
			
		||||
    private fanotify_event_metadata* current; // Current position in buffer.
 | 
			
		||||
    private size_t remainingLen;               // Remaining bytes in buffer from current.
 | 
			
		||||
 | 
			
		||||
	// Маркировка директории /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");
 | 
			
		||||
    // 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	writeln("Мониторинг запущен для /tmp/scripts..."); // Вывод сообщения о старте мониторинга.
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	// Бесконечный цикл: постоянно читает события и обрабатывает их.
 | 
			
		||||
	while (true)
 | 
			
		||||
    // 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Класс для представления события fanotify (ООП-стиль, с методами для проверки и обработки)
 | 
			
		||||
struct FanotifyEvent
 | 
			
		||||
{
 | 
			
		||||
	private fanotify_event_metadata _meta;
 | 
			
		||||
	private int _fanFd;
 | 
			
		||||
 | 
			
		||||
	// Деструктор: автоматически закрывает 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)
 | 
			
		||||
	{
 | 
			
		||||
		auto events = fan.readEvents(); // Чтение событий (блокирующее, ждёт до появления событий).
 | 
			
		||||
 | 
			
		||||
		foreach (ref e; events) // Цикл по каждому событию в массиве.
 | 
			
		||||
		if (eventFd < 0 || eventFd == FAN_NOFD)
 | 
			
		||||
		{
 | 
			
		||||
			// Определение пути: если name извлечено (из FAN_REPORT_NAME), использовать его; иначе "unknown".
 | 
			
		||||
			// name - относительное имя файла/директории относительно маркированной.
 | 
			
		||||
			string path = e.name.length ? e.name : "unknown"; // Используем извлечённое имя (относительное)
 | 
			
		||||
			return; // Нет fd для response
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			// Комментарий: в режиме с FAN_REPORT_FID fd = FAN_NOFD, так что нельзя использовать readLink("/proc/self/fd/" ~ to!string(e.eventFd)) для полного пути.
 | 
			
		||||
			// fd теперь FAN_NOFD, так что пропускаем readLink/close
 | 
			
		||||
		fanotify_response resp;
 | 
			
		||||
		resp.fd = eventFd;
 | 
			
		||||
		resp.response = response;
 | 
			
		||||
 | 
			
		||||
			// Вывод общей информации о событии: маска в hex, PID, путь/имя.
 | 
			
		||||
			writefln("Событие: mask=0x%x, pid=%d, name/path=%s", e.mask, e.pid, path);
 | 
			
		||||
		ssize_t res = write(_fanFd, &resp, fanotify_response.sizeof);
 | 
			
		||||
		enforce(res == fanotify_response.sizeof, "Ошибка записи response: " ~ strerror(errno)
 | 
			
		||||
				.fromStringz.to!string);
 | 
			
		||||
 | 
			
		||||
			// Проверки и вывод конкретных типов событий с использованием методов структуры.
 | 
			
		||||
			if (e.isOpen) // Если открытие.
 | 
			
		||||
				writeln(" - Открытие файла");
 | 
			
		||||
			if (e.isModify) // Если модификация.
 | 
			
		||||
				writeln(" - Модификация файла");
 | 
			
		||||
			if (e.isCloseWrite) // Если закрытие после записи.
 | 
			
		||||
				writeln(" - Закрытие после записи");
 | 
			
		||||
			if (e.isCloseNoWrite) // Если закрытие без записи.
 | 
			
		||||
				writeln(" - Закрытие без записи");
 | 
			
		||||
			if (e.isCreate) // Если создание.
 | 
			
		||||
				writeln(" - Создание файла/директории");
 | 
			
		||||
			if (e.isDelete) // Если удаление.
 | 
			
		||||
				writeln(" - Удаление файла/директории");
 | 
			
		||||
		// Закрываем 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)
 | 
			
		||||
	{
 | 
			
		||||
		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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ulong dirMask()
 | 
			
		||||
{
 | 
			
		||||
	return FAN_EVENT_ON_CHILD |
 | 
			
		||||
		FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM | FAN_ACCESS_PERM |
 | 
			
		||||
		FAN_OPEN | FAN_CLOSE_WRITE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void main(string[] args)
 | 
			
		||||
{
 | 
			
		||||
	enum initFlags = FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK;
 | 
			
		||||
	auto fan = new Fanotify(initFlags, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
 | 
			
		||||
 | 
			
		||||
	// 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 list = fan.readEvents(8_192);
 | 
			
		||||
		foreach (fev; list)
 | 
			
		||||
		{
 | 
			
		||||
			if (fev.isOverflow)
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			if (fev.isOpenPerm || fev.isAccessPerm || fev.isOpenExecPerm)
 | 
			
		||||
			{
 | 
			
		||||
				writeln(fev.eventFd.readlinkFdPath);
 | 
			
		||||
				fev.respond(FAN_ALLOW);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			if (fev.isCloseWrite)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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