From 607de2a0741d536bf9f050516b48cf247ce84dc2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 10:26:56 +0200 Subject: [PATCH] Bound recursive walker stack and report exact copy failure phases --- src/media_async/media_async.c | 54 +++++++++++++++++++++-------------- src/media_async/media_async.h | 3 +- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/media_async/media_async.c b/src/media_async/media_async.c index 2ec0a276..f8b9e8ff 100644 --- a/src/media_async/media_async.c +++ b/src/media_async/media_async.c @@ -158,24 +158,29 @@ int ma_copy_file(const char* source, const char* destination) { FILE* input = file_io_open_read(source); if (!input) return -1; struct file_io_temp output = {0}; + const char* phase = "validate paths"; int status = file_io_paths(source, destination); uint64_t total = 0; + if (status == 0) phase = "create temporary"; if (status == 0 && file_io_temp_open(&output, destination)) status = -1; if (status == 0) { uint8_t buffer[65536]; size_t count; + phase = "read/write"; while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) { if (fwrite(buffer, 1, count, output.file) != count) { status = -1; break; } total += count; } if (ferror(input)) status = -1; } - if (fclose(input)) status = -1; + if (fclose(input)) { phase = "close source"; status = -1; } + if (status == 0) phase = "flush/close temporary"; if (status == 0 && file_io_temp_finish(&output)) status = -1; + if (status == 0) phase = "publish"; if (status == 0 && file_io_temp_publish(&output, source, destination)) status = -1; file_io_temp_discard(&output); if (status < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "copy failed source=%s destination=%s bytes=%llu errno=%d", - source, destination ? destination : "(null)", (unsigned long long)total, errno); + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "copy failed phase=%s source=%s destination=%s bytes=%llu", + phase, source, destination ? destination : "(null)", (unsigned long long)total); return -1; } DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "copy complete source=%s destination=%s bytes=%llu same_file=%d", @@ -210,18 +215,21 @@ static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, uns /* Одна запись: ссылки/устройства пропускаются, ошибки делают обход неполным. */ static int ma_walk_entry(const char* directory, const char* name, struct ma_walk_ctx* ctx, unsigned depth) { if (!strcmp(name, ".") || !strcmp(name, "..")) return 0; - char full[FILE_IO_PATH_MAX]; - int length = snprintf(full, sizeof(full), "%s/%s", directory, name); - if (length < 0 || (size_t)length >= sizeof(full)) { + size_t length = strlen(directory) + strlen(name) + 2; + if (length > FILE_IO_PATH_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry path too long directory=%s name=%s", directory, name); return -1; } + char* full = u_malloc((uint32_t)length); + if (!full) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry allocation failed bytes=%zu", length); return -1; } + snprintf(full, length, "%s/%s", directory, name); struct file_io_info info; if (file_io_inspect(full, 0, &info)) { - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry inspection failed path=%s", full); return -1; + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry inspection failed path=%s", full); u_free(full); return -1; } - if (info.type == FILE_IO_DIRECTORY) return ma_walk_recursive(full, ctx, depth + 1); + int status = 0; + if (info.type == FILE_IO_DIRECTORY) status = ma_walk_recursive(full, ctx, depth + 1); if (info.type == FILE_IO_REGULAR) ctx->cb(ctx->arg, full, info.size); - return 0; + u_free(full); return status; } /* Ограниченный по глубине обход без перехода по конечным ссылкам. */ @@ -239,27 +247,30 @@ static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, uns } int status = 0; #ifdef _WIN32 - char pattern[FILE_IO_PATH_MAX]; wchar_t wide[FILE_IO_PATH_MAX]; - int length = snprintf(pattern, sizeof(pattern), "%s/*", directory); - if (length < 0 || (size_t)length >= sizeof(pattern)) { - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory search path too long path=%s", directory); return -1; + struct ma_search { wchar_t pattern[FILE_IO_PATH_MAX]; char name[FILE_IO_PATH_MAX]; WIN32_FIND_DATAW data; }; + struct ma_search* search = u_calloc(1, sizeof(*search)); + if (!search) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory search allocation failed"); return -1; } + if (file_io_wide(directory, search->pattern)) { u_free(search); return -1; } + size_t length = wcslen(search->pattern); + if (length + 3 > FILE_IO_PATH_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory search path too long path=%s", directory); u_free(search); return -1; } - if (file_io_wide(pattern, wide)) return -1; - WIN32_FIND_DATAW data; - HANDLE find = FindFirstFileW(wide, &data); + memcpy(search->pattern + length, L"/*", 3 * sizeof(wchar_t)); + HANDLE find = FindFirstFileW(search->pattern, &search->data); if (find == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); + u_free(search); if (error == ERROR_FILE_NOT_FOUND) return 0; // Открытый выше каталог пуст. DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory open failed path=%s winerr=%lu", directory, error); return -1; } do { - char name[FILE_IO_PATH_MAX]; - if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, data.cFileName, -1, name, sizeof(name), NULL, NULL)) { + if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, search->data.cFileName, -1, + search->name, sizeof(search->name), NULL, NULL)) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry UTF-8 conversion failed path=%s winerr=%lu", directory, GetLastError()); status = -1; break; } - if (ma_walk_entry(directory, name, ctx, depth)) { status = -1; break; } - if (!FindNextFileW(find, &data)) { + if (ma_walk_entry(directory, search->name, ctx, depth)) { status = -1; break; } + if (!FindNextFileW(find, &search->data)) { DWORD error = GetLastError(); if (error != ERROR_NO_MORE_FILES) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory read failed path=%s winerr=%lu", directory, error); status = -1; @@ -270,6 +281,7 @@ static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, uns if (!FindClose(find)) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory close failed path=%s winerr=%lu", directory, GetLastError()); status = -1; } + u_free(search); #else DIR* dir = opendir(directory); if (!dir) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory open failed path=%s errno=%d", directory, errno); return -1; } @@ -288,7 +300,7 @@ static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, uns } int ma_dir_walk(const char* directory, ma_dir_walk_cb callback, void* arg) { - if (!directory || !*directory || !callback) { + if (!directory || !*directory || strlen(directory) >= FILE_IO_PATH_MAX || !callback) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory walk invalid path/callback"); return -1; } struct ma_walk_ctx ctx = {.cb = callback, .arg = arg}; diff --git a/src/media_async/media_async.h b/src/media_async/media_async.h index 98b03614..54aeeb0c 100644 --- a/src/media_async/media_async.h +++ b/src/media_async/media_async.h @@ -44,7 +44,8 @@ int ma_sha256_file(const char* path, uint8_t hash_out[32]); * data=NULL допустим только при len=0. 0/-1. */ int ma_sign_block(const uint8_t* ed25519_privkey, const uint8_t* data, size_t len, uint8_t sig_out[64]); -/* Копировать regular src в отсутствующий/regular dst через соседний temp; 0/-1. Пути UTF-8, <4096 байт. +/* Копировать regular src в отсутствующий/regular dst через соседний temp; 0/-1. Пути UTF-8, <4096 байт, + * причём dst вместе с суффиксом .tmp.<32 hex> и NUL должен помещаться в 4096 байт. * Один существующий файл (включая ./ и hardlink) — успешный no-op. Конечные ссылки назначения отклоняются. * Ошибка сохраняет src и прежний dst; src/каталоги не менять во время вызова. POSIX результат имеет права 0600. * Публикация происходит внутри вызова; последующая отмена media_async не откатывает копирование. */