From 41f0aa24396895da9f72e9ccac2b3593b60df600 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 18:09:11 +0200 Subject: [PATCH] fix(member-files): reject local FIFO without blocking disk worker --- src/member_files/disk.c | 13 ++++++++++--- src/member_files/disk.h | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/member_files/disk.c b/src/member_files/disk.c index 7a7d13f7..02e7e230 100644 --- a/src/member_files/disk.c +++ b/src/member_files/disk.c @@ -58,7 +58,10 @@ static int disk_win_error(const char* operation) { static int disk_handle_info(HANDLE h, struct mf_disk_info* out) { BY_HANDLE_FILE_INFORMATION i; if (!GetFileInformationByHandle(h, &i)) return disk_win_error("GetFileInformationByHandle"); - if ((i.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || i.nNumberOfLinks > 1) return MF_INVALID_PATH; + if ((i.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || i.nNumberOfLinks > 1) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: unsupported file attributes=%lu links=%lu", i.dwFileAttributes, i.nNumberOfLinks); + return MF_INVALID_PATH; + } memset(out, 0, sizeof(*out)); out->type = i.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? MF_DIRECTORY : MF_FILE; out->size = out->type == MF_FILE ? ((uint64_t)i.nFileSizeHigh << 32) | i.nFileSizeLow : 0; @@ -254,7 +257,11 @@ static int disk_parent_open(struct mf_disk_root* root, const char* path, struct } static int disk_posix_info(const struct stat* st, struct mf_disk_info* i) { - if ((!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) || (S_ISREG(st->st_mode) && st->st_nlink != 1)) return MF_INVALID_PATH; + if ((!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) || (S_ISREG(st->st_mode) && st->st_nlink != 1)) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: unsupported file mode=%lo links=%llu", + (unsigned long)st->st_mode, (unsigned long long)st->st_nlink); + return MF_INVALID_PATH; + } memset(i, 0, sizeof(*i)); i->type = S_ISDIR(st->st_mode) ? MF_DIRECTORY : MF_FILE; i->size = i->type == MF_FILE ? (uint64_t)st->st_size : 0; i->mtime = (uint64_t)st->st_mtime; i->identity[0] = st->st_dev; i->identity[1] = st->st_ino; @@ -352,7 +359,7 @@ int mf_disk_resize(int fd, uint64_t size) { return ftruncate(fd, (off_t)size) ? int mf_disk_sync(int fd) { return fsync(fd) ? disk_error("sync", NULL) : MF_OK; } int mf_disk_close(int fd) { return close(fd) ? disk_error("close", NULL) : MF_OK; } int mf_disk_local_open(const char* path, int create, int* fd) { - *fd = open(path, O_CLOEXEC | O_NOFOLLOW | (create ? O_RDWR | O_CREAT | O_EXCL : O_RDONLY), 0600); + *fd = open(path, O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK | (create ? O_RDWR | O_CREAT | O_EXCL : O_RDONLY), 0600); return *fd < 0 ? disk_error("local open", path) : MF_OK; } int mf_disk_local_publish(const char* from, const char* to) { return rename(from, to) ? disk_error("local publish", to) : MF_OK; } diff --git a/src/member_files/disk.h b/src/member_files/disk.h index bbdaa334..49dfc5a0 100644 --- a/src/member_files/disk.h +++ b/src/member_files/disk.h @@ -1,39 +1,64 @@ -/* Примитивы диска для storage и клиентского worker. На POSIX пути разрешаются +/* Синхронные примитивы диска для storage и клиентского worker; не вызывать из uasync callbacks. + * Возврат — MF_OK либо положительный mf_status, не errno/-1. Аргументы/выходы обязательны, кроме отмеченных NULL. + * Серверные path/from/to — уже проверенные относительные пути; сам слой не запрещает '.'/'..'. + * На POSIX пути разрешаются * относительно открытого каталога через *at/O_NOFOLLOW. Windows удерживает каталоги * без FILE_SHARE_DELETE и отвергает reparse points; имена преобразуются UTF-8→UTF-16. - * fd принадлежит вызывающему; read/write работают по 64-битному смещению. */ + * fd принадлежит вызывающему; read/write работают по 64-битному смещению. + * Локальные пути приложения не ограничены корнем storage; их родители/назначение остаются ответственностью caller. */ #ifndef MEMBER_FILES_DISK_H #define MEMBER_FILES_DISK_H #include "protocol.h" #include "../../lib/platform_compat.h" -struct mf_disk_info { uint64_t size, mtime, identity[6]; uint8_t type; }; +struct mf_disk_info { + uint64_t size; // Байты regular-файла; каталог — 0. + uint64_t mtime; // Unix seconds; для version используются более точные identity. + uint64_t identity[6]; // Непрозрачный снимок inode/file ID и времён; платформенно зависим, не wire. + uint8_t type; // MF_FILE/MF_DIRECTORY; другие типы и regular hardlink отклоняются. +}; struct mf_disk_root { - char path[2048]; + char path[2048]; // Заимствованный при open путь копируется; предел2047 байт + NUL. #ifdef _WIN32 - HANDLE handle; + HANDLE handle; // Собственный handle корня, закрывается после всех операций. #else - int fd; + int fd; // Собственный directory fd; операции разрешают имена относительно него. #endif }; +/* Создать корень, если отсутствует (не создаёт родителей), и закрепить его. Close нужен только после успеха. */ int mf_disk_root_open(struct mf_disk_root* root, const char* path); void mf_disk_root_close(struct mf_disk_root* root); +/* Открыть regular-файл без усечения. create!=0 — эксклюзивное создание, writable!=0 — read/write. + * fd использовать/закрывать только после MF_OK; отказ не гарантирует значение *fd. */ int mf_disk_open(struct mf_disk_root* root, const char* path, int create, int writable, int* fd); +/* Снимок без чтения содержимого: stat — по имени, info — уже открытого fd. Выход использовать только при успехе. */ int mf_disk_stat(struct mf_disk_root* root, const char* path, struct mf_disk_info* info); int mf_disk_info(int fd, struct mf_disk_info* info); -/* created, если не NULL, показывает факт создания даже при последующей ошибке sync. */ +/* Создать один каталог. created, если не NULL, показывает факт создания даже при последующей ошибке sync. */ int mf_disk_mkdir(struct mf_disk_root* root, const char* path, int* created); +/* Удалить файл либо пустой каталог. Ошибка sync на POSIX возможна уже после удаления. */ int mf_disk_remove(struct mf_disk_root* root, const char* path, int directory); +/* Заменяющий rename: запрет замены при обычном RENAME проверяет storage. На POSIX sync обоих родителей; + * ошибка sync не означает откат уже выполненного rename. */ int mf_disk_rename(struct mf_disk_root* root, const char* from, const char* to); +/* name/info заимствованы на время callback; 0 — продолжить, >0 — успешная ранняя остановка, <0 — -mf_status. */ typedef int (*mf_disk_entry_fn)(void* arg, const char* name, const struct mf_disk_info* info); +/* Перечислить записи в порядке файловой системы, без '.'/'..'. Пустой path означает корень. */ int mf_disk_list(struct mf_disk_root* root, const char* path, mf_disk_entry_fn cb, void* arg); +/* Точный диапазон: 0 байт допустимо, короткий read — ошибка. Caller гарантирует offset+size <= INT64_MAX. */ int mf_disk_read(int fd, uint64_t offset, void* data, size_t size); int mf_disk_write(int fd, uint64_t offset, const void* data, size_t size); +/* Изменить длину (size<=INT64_MAX), синхронизировать содержимое либо закрыть fd; каждый вызов независим. */ int mf_disk_resize(int fd, uint64_t size); int mf_disk_sync(int fd); int mf_disk_close(int fd); +/* Локальный source (create=0) либо эксклюзивный read/write temp (create!=0), без усечения. + * Тип проверяет caller через info; POSIX O_NONBLOCK позволяет отвергнуть FIFO до блокирующего чтения. */ int mf_disk_local_open(const char* path, int create, int* fd); +/* Заменить local назначение: POSIX rename без sync каталога; Windows MOVEFILE_WRITE_THROUGH. */ int mf_disk_local_publish(const char* from, const char* to); +/* Удалить собственный local temp; отсутствие самого файла допустимо. Владение проверяет caller. */ int mf_disk_local_remove(const char* path); +/* SHA256 ровно size байт от нуля, включая пустой файл; ошибка делает hash непригодным. fd остаётся у caller. */ int mf_disk_hash(int fd, uint64_t size, uint8_t hash[32]); #endif