From 80480f5a6c4067d91aa5f11a84fc7ff64704b9e4 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 24 Aug 2025 02:22:00 +0300 Subject: [PATCH] 0.1.0 --- .gitignore | 16 +++ .vscode/settings.json | 5 + LICENSE | 23 ++++ README.md | 35 ++++++ dub.json | 10 ++ dub.selections.json | 5 + dub.settings.json | 4 + source/fanotify.d | 272 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 370 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 dub.json create mode 100644 dub.selections.json create mode 100644 dub.settings.json create mode 100644 source/fanotify.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..462ed2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/fanotify +fanotify.so +fanotify.dylib +fanotify.dll +fanotify.a +fanotify.lib +fanotify-test-* +*.exe +*.pdb +*.o +*.obj +*.lst diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d1c022f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..380f1f7 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# fanotify + +Low-level wrapper over the C API fanotify for D. All values are taken from uapi ``. + +## Installation + +Add to `dub.json`: + +```json +{ + "dependencies": { + "fanotify": "~>0.1.0" + } +} +``` + +Or to `dub.sdl`: + +``` +dependency "fanotify" version="~>0.1.0" +``` + +Run `dub build`. + +## Usage + +Import the module: `import fanotify;` + +## Documentation + +- Linux man pages: `fanotify(7)`, `fanotify_init(2)`, `fanotify_mark(2)`. + +## License + +Boost Software License 1.0. See [LICENSE](LICENSE). diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..369db68 --- /dev/null +++ b/dub.json @@ -0,0 +1,10 @@ +{ + "authors": [ + "Alexander Zhirov" + ], + "copyright": "Copyright © 2025, Alexander Zhirov", + "description": "The fanotify API provides notification and interception of filesystem events.", + "license": "BSL-1.0", + "name": "fanotify", + "targetType": "library" +} \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..322586b --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,5 @@ +{ + "fileVersion": 1, + "versions": { + } +} diff --git a/dub.settings.json b/dub.settings.json new file mode 100644 index 0000000..9da886c --- /dev/null +++ b/dub.settings.json @@ -0,0 +1,4 @@ +{ + "defaultArchitecture": "x86_64", + "defaultCompiler": "ldc2" +} diff --git a/source/fanotify.d b/source/fanotify.d new file mode 100644 index 0000000..3c75ea3 --- /dev/null +++ b/source/fanotify.d @@ -0,0 +1,272 @@ +// Низкоуровневая обёртка над C API fanotify для D. +// Все значения взяты из uapi +module fanotify; + +import core.stdc.stdint; // uint64_t и т.п. + +// ------------------------------------------------------------ +// ФЛАГИ ДЛЯ fanotify_init(flags, event_f_flags) +// ------------------------------------------------------------ +// Поведение файлового дескриптора группы +enum FAN_CLOEXEC = 0x0000_0001; // закрывать fd при exec() +enum FAN_NONBLOCK = 0x0000_0002; // неблокирующие read() +// Класс группы (определяет тип выдаваемых событий) +enum FAN_CLASS_NOTIF = 0x0000_0000; // только уведомления (не PERM) +enum FAN_CLASS_CONTENT = 0x0000_0004; // разрешения на доступ к СОДЕРЖИМОМУ (CONTENT) +enum FAN_CLASS_PRE_CONTENT = 0x0000_0008; // разрешения ДО доступа к содержимому (PRE_CONTENT) +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_CLASS_BITS = (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | FAN_CLASS_PRE_CONTENT); +// Лимиты очереди/меток +enum FAN_UNLIMITED_QUEUE = 0x0000_0010; // неограниченная очередь событий +enum FAN_UNLIMITED_MARKS = 0x0000_0020; // неограниченное число меток +enum FAN_ENABLE_AUDIT = 0x0000_0040; // включить аудит +// Формат отчётности в метаданных события +enum FAN_REPORT_PIDFD = 0x0000_0080; // добавлять PIDFD (если поддерживается ядром) +enum FAN_REPORT_TID = 0x0000_0100; // сообщать TID вместо PID +enum FAN_REPORT_FID = 0x0000_0200; // сообщать file-id (handle) вместо fd +enum FAN_REPORT_DIR_FID = 0x0000_0400; // сообщать dir file-id +enum FAN_REPORT_NAME = 0x0000_0800; // сообщать имя (ранее FAN_REPORT_DFID_NAME) +enum FAN_REPORT_TARGET_FID = 0x0000_1000; // сообщать target file-id +enum FAN_REPORT_FD_ERROR = 0x0000_2000; // event->fd может сообщать ошибки +enum FAN_REPORT_MNT = 0x0000_4000; // сообщать события монтирования +// Удобные сводные макросы +enum FAN_REPORT_DFID_NAME = (FAN_REPORT_DIR_FID | FAN_REPORT_NAME); +enum FAN_REPORT_DFID_NAME_TARGET = (FAN_REPORT_DFID_NAME | FAN_REPORT_FID | FAN_REPORT_TARGET_FID); +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_INIT_FLAGS = + (FAN_CLOEXEC | FAN_NONBLOCK | FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE | + FAN_UNLIMITED_MARKS); +// Параметр event_f_flags для fanotify_init() — это флаги open(2): +// O_RDONLY / O_WRONLY / O_RDWR | O_LARGEFILE и т.п. (из ). + +// ------------------------------------------------------------ +// ФЛАГИ ДЛЯ fanotify_mark(fanfd, flags, mask, dirfd, path) +// ------------------------------------------------------------ +enum FAN_MARK_ADD = 0x0000_0001; // добавить/обновить метку +enum FAN_MARK_REMOVE = 0x0000_0002; // удалить метку +enum FAN_MARK_DONT_FOLLOW = 0x0000_0004; // не переходить по симлинкам (на path) +enum FAN_MARK_ONLYDIR = 0x0000_0008; // path должен быть каталогом +enum FAN_MARK_MOUNT = 0x0000_0010; // навесить на точку монтирования +enum FAN_MARK_IGNORED_MASK = 0x0000_0020; // задать игнорируемые события +enum FAN_MARK_IGNORED_SURV_MODIFY = 0x0000_0040; // игнор сохраняется при модификации +enum FAN_MARK_FLUSH = 0x0000_0080; // снять все метки у группы +enum FAN_MARK_FILESYSTEM = 0x0000_0100; // навесить на всю ФС +enum FAN_MARK_EVICTABLE = 0x0000_0200; // метка может быть evicted +enum FAN_MARK_IGNORE = 0x0000_0400; // игнорировать (взаимоисключающе с IGNORED_MASK, новое) +// These are NOT bitwise flags. Both bits can be used together. +enum FAN_MARK_INODE = 0x0000_0000; // на inode +enum FAN_MARK_MNTNS = 0x0000_0110; // на mount namespace +// Удобный макрос +enum FAN_MARK_IGNORE_SURV = (FAN_MARK_IGNORE | FAN_MARK_IGNORED_SURV_MODIFY); +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_MARK_FLAGS = + (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_DONT_FOLLOW | FAN_MARK_ONLYDIR | + FAN_MARK_MOUNT | FAN_MARK_IGNORED_MASK | FAN_MARK_IGNORED_SURV_MODIFY | + FAN_MARK_FLUSH); + +// ------------------------------------------------------------ +// БИТЫ МАСКИ СОБЫТИЙ (mask) ДЛЯ fanotify_mark() +// ------------------------------------------------------------ +// «Обычные» события +enum FAN_ACCESS = 0x0000_0001; // чтение (read) +enum FAN_MODIFY = 0x0000_0002; // запись/изменение +enum FAN_ATTRIB = 0x0000_0004; // метаданные (chmod/chown/utime) +enum FAN_CLOSE_WRITE = 0x0000_0008; // закрыт после записи +enum FAN_CLOSE_NOWRITE = 0x0000_0010; // закрыт без записи +enum FAN_OPEN = 0x0000_0020; // открыт +enum FAN_MOVED_FROM = 0x0000_0040; // перемещён «из» +enum FAN_MOVED_TO = 0x0000_0080; // перемещён «в» +enum FAN_CREATE = 0x0000_0100; // создан +enum FAN_DELETE = 0x0000_0200; // удалён +enum FAN_DELETE_SELF = 0x0000_0400; // удалён сам наблюдаемый объект +enum FAN_MOVE_SELF = 0x0000_0800; // перемещён сам наблюдаемый объект +enum FAN_OPEN_EXEC = 0x0000_1000; // открыт для исполнения +enum FAN_Q_OVERFLOW = 0x0000_4000; // переполнение очереди +enum FAN_FS_ERROR = 0x0000_8000; // ошибки ФС +// deprecated #define FAN_DIR_MODIFY 0x0008_0000 /* Deprecated (reserved) */ +enum FAN_PRE_ACCESS = 0x0010_0000; // pre-content access hook +enum FAN_MNT_ATTACH = 0x0100_0000; // mount attached +enum FAN_MNT_DETACH = 0x0200_0000; // mount detached +enum FAN_RENAME = 0x1000_0000; // file renamed +// События «разрешения» (требуют CLASS_CONTENT/PRE_CONTENT) +enum FAN_OPEN_PERM = 0x0001_0000; // запрос на open() (исправлено имя/значение) +enum FAN_ACCESS_PERM = 0x0002_0000; // запрос на доступ к содержимому (исправлено имя/значение) +enum FAN_OPEN_EXEC_PERM = 0x0004_0000; // запрос на open(O_EXEC) +// Модификаторы области действия маски +enum FAN_EVENT_ON_CHILD = 0x0800_0000; // события на дочерние объекты каталога +enum FAN_ONDIR = 0x4000_0000; // событие относится к каталогу +// Составные удобные маски +enum FAN_CLOSE = (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE); +enum FAN_MOVE = (FAN_MOVED_FROM | FAN_MOVED_TO); +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_EVENTS = + (FAN_ACCESS | FAN_MODIFY | FAN_CLOSE | FAN_OPEN); +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_PERM_EVENTS = + (FAN_OPEN_PERM | FAN_ACCESS_PERM); +// Deprecated - do not use this in programs and do not add new flags here! +deprecated enum FAN_ALL_OUTGOING_EVENTS = + (FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_Q_OVERFLOW); + +// ------------------------------------------------------------ +// МЕТАДАННЫЕ СОБЫТИЯ +// ------------------------------------------------------------ +struct fanotify_event_metadata +{ + uint event_len; // общая длина записи + ubyte vers; // версия (FANOTIFY_METADATA_VERSION) + ubyte reserved; // зарезервировано + ushort metadata_len; // длина этой структуры + uint64_t mask; // __aligned_u64: биты события/модификаторов + int fd; // файловый дескриптор (или FAN_NOFD) + int pid; // PID или TID (если FAN_REPORT_TID) +} + +enum FANOTIFY_METADATA_VERSION = 3; +enum FAN_NOFD = -1; +enum FAN_NOPIDFD = FAN_NOFD; +enum FAN_EPIDFD = -2; + +// ------------------------------------------------------------ +// ДОП. ИНФОРМАЦИЯ (FID / PIDFD / ERROR / etc.) В ХВОСТЕ СОБЫТИЯ +// ------------------------------------------------------------ +struct fanotify_event_info_header +{ + ubyte info_type; // тип записей ниже + ubyte pad; // выравнивание + ushort len; // длина этой записи +} +// Типы info-записей (идут после metadata) +enum FAN_EVENT_INFO_TYPE_FID = 1; // только file handle +enum FAN_EVENT_INFO_TYPE_DFID_NAME = 2; // dir handle + имя объекта +enum FAN_EVENT_INFO_TYPE_DFID = 3; // dir handle +enum FAN_EVENT_INFO_TYPE_PIDFD = 4; // PIDFD (если FAN_REPORT_PIDFD) +enum FAN_EVENT_INFO_TYPE_ERROR = 5; // error info +enum FAN_EVENT_INFO_TYPE_RANGE = 6; // range info +enum FAN_EVENT_INFO_TYPE_MNT = 7; // mount info +// Special info types for FAN_RENAME +enum FAN_EVENT_INFO_TYPE_OLD_DFID_NAME = 10; +enum FAN_EVENT_INFO_TYPE_NEW_DFID_NAME = 12; +// (Reserved: 11 и 13 для OLD_DFID и NEW_DFID) + +struct __kernel_fsid_t +{ + int[2] val; +} + +// Общий заголовок для FID-записей +struct fanotify_event_info_fid +{ + fanotify_event_info_header hdr; + __kernel_fsid_t fsid; // файловая система + // далее переменной длины: unsigned char handle[]; (opaque struct file_handle) +} + +// Совместимо с open_by_handle_at() +struct file_handle +{ + uint handle_bytes; // длина f_handle + int handle_type; // тип handle + ubyte[0] f_handle; // гибкий хвост (реальные байты handle идут сразу за структурой) +} + +// Структура для PIDFD +struct fanotify_event_info_pidfd +{ + fanotify_event_info_header hdr; + int pidfd; // __s32 +} + +// Структура для ERROR +struct fanotify_event_info_error +{ + fanotify_event_info_header hdr; + int error; // __s32 + uint error_count; // __u32 +} + +// Структура для RANGE +struct fanotify_event_info_range +{ + fanotify_event_info_header hdr; + uint pad; // __u32 + uint64_t offset; // __u64 + uint64_t count; // __u64 +} + +// Структура для MNT +struct fanotify_event_info_mnt +{ + fanotify_event_info_header hdr; + uint64_t mnt_id; // __u64 +} + +// ------------------------------------------------------------ +// СТРУКТУРЫ ДЛЯ RESPONSE (для PERM-событий) +// ------------------------------------------------------------ +struct fanotify_response +{ + int fd; // __s32 + uint response; // __u32 +} + +// Дополнительная информация в response +enum FAN_RESPONSE_INFO_NONE = 0; +enum FAN_RESPONSE_INFO_AUDIT_RULE = 1; + +struct fanotify_response_info_header +{ + ubyte type; + ubyte pad; + ushort len; +} + +struct fanotify_response_info_audit_rule +{ + fanotify_response_info_header hdr; + uint rule_number; // __u32 + uint subj_trust; // __u32 + uint obj_trust; // __u32 +} + +// Legit responses +enum FAN_ALLOW = 0x01; +enum FAN_DENY = 0x02; +enum FAN_AUDIT = 0x10; // Bitmask to create audit record +enum FAN_INFO = 0x20; // Bitmask to indicate additional information + +// Для DENY с errno +enum FAN_ERRNO_BITS = 8; +enum FAN_ERRNO_SHIFT = (32 - FAN_ERRNO_BITS); +enum FAN_ERRNO_MASK = ((1 << FAN_ERRNO_BITS) - 1); +// Макрос: FAN_DENY_ERRNO(err) = (FAN_DENY | ((((__u32)(err)) & FAN_ERRNO_MASK) << FAN_ERRNO_SHIFT)) + +// ------------------------------------------------------------ +// ПРОТОТИПЫ ФУНКЦИЙ C API +// ------------------------------------------------------------ +// Инициализирует новую группу fanotify и возвращает файловый дескриптор для очереди событий +extern (C) int fanotify_init( + uint flags, // Битовая маска, определяющая класс уведомлений и другие поведения + uint event_f_flags // Флаги состояния файлов, применяемые к дескрипторам файлов, возвращаемым в событиях + +); +// Добавляет, удаляет или модифицирует метку fanotify на объекте файловой системы +extern (C) int fanotify_mark( + int fanotify_fd, // Файловый дескриптор fanotify, возвращенный fanotify_init() + uint flags, // Битовая маска, описывающая выполняемую модификацию + uint64_t mask, // Битовая маска, определяющая события для прослушивания (или игнорирования) + int dirfd, // AT_FDCWD или fd каталога + const(char)* pathname // путь или null при INODE/FILESYSTEM/MOUNT + +); +// Для работы с FID: открыть объект по handle (требует CAP_DAC_READ_SEARCH) +extern (C) int open_by_handle_at(int mount_fd, const(file_handle)* handle, int flags); + +// ------------------------------------------------------------ +// HELPER FUNCTIONS (макросы для работы с буфером metadata) +// ------------------------------------------------------------ +enum FAN_EVENT_METADATA_LEN = fanotify_event_metadata.sizeof; + +// Макросы в D-стиле (как функции или шаблоны, но для простоты как enum/комментарии) +// FAN_EVENT_NEXT(meta, len): len -= meta.event_len; return cast(fanotify_event_metadata*) (cast(char*) meta + meta.event_len); +// FAN_EVENT_OK(meta, len): (len >= FAN_EVENT_METADATA_LEN) && (meta.event_len >= FAN_EVENT_METADATA_LEN) && (meta.event_len <= len)