From 0e418c7fa906f866381576138e49885aeaf91d1d Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 10:20:02 +0200 Subject: [PATCH] Propagate RNG errors and preserve 64-bit media sizes through callers --- src/chat/chat_msg.c | 30 +++++--- src/media_delivery/media_index.c | 117 ++++++++++++++++++------------- src/media_delivery/media_index.h | 2 +- 3 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/chat/chat_msg.c b/src/chat/chat_msg.c index ee44ce71..6f87034b 100644 --- a/src/chat/chat_msg.c +++ b/src/chat/chat_msg.c @@ -23,6 +23,7 @@ #include "../../lib/mem.h" #include "../../lib/u_async.h" #include "../../lib/platform_compat.h" +#include "../../lib/file_io.h" #include #include @@ -358,7 +359,11 @@ void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_ /* лимит одного медиафайла применяется и к локальным отправкам */ uint64_t unit = inst->config->global.chatserver_storage_unit_size; if (unit > 0) { - int64_t sz = (int64_t)ma_file_size(req->media_src); + int64_t sz = ma_file_size(req->media_src); + if (sz < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media submit source size unavailable path=%s", req->media_src); + chat_event_submitted(inst, req->request_id, 0); return; + } if (sz > 0 && (uint64_t)sz > unit) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: media too large for local send (%lld > %llu bytes), rejected ch=%s", CC_ID, (long long)sz, (unsigned long long)unit, req->channel_id); @@ -1153,8 +1158,8 @@ void chat_media_backfill(struct UTUN_INSTANCE* inst) { char path[1536]; snprintf(path, sizeof(path), "%s/media/%s/%s", media_base, ch_id, final_name); - int fsz = ma_file_size(path); - if (fsz <= 0 || (int64_t)fsz != result.file_size) { + int64_t fsz = ma_file_size(path); + if (fsz <= 0 || fsz != result.file_size) { u_free(body); media_index_result_free(&result); continue; } @@ -1290,7 +1295,7 @@ static void bf_collect(struct chat_core_ctx* cc, const char* media_base, const c char full_path[1536]; snprintf(full_path, sizeof(full_path), "%s/media/%s/%s", media_base, ch_id, final_name); - int64_t actual = (int64_t)ma_file_size(full_path); + int64_t actual = ma_file_size(full_path); if (actual < 0) actual = 0; if (l->count == l->cap) { @@ -1588,8 +1593,8 @@ static void cache_rel_collect_cb(const struct media_msg_file* f, void* arg) { } static void cache_file_collect_cb(const struct media_msg_file* f, void* arg) { - int fsz = ma_file_size(f->full_path); - if (fsz > 0) cache_file_add((struct cache_file_list*)arg, f->full_path, f->ts_ms, (int64_t)fsz); + int64_t fsz = ma_file_size(f->full_path); + if (fsz > 0) cache_file_add((struct cache_file_list*)arg, f->full_path, f->ts_ms, fsz); } /* Фактическая сумма размеров файлов, соответствующих сообщениям. */ @@ -1654,6 +1659,11 @@ static void chat_media_cache_cleanup_orphans(struct UTUN_INSTANCE* inst) { char media_base[512]; chat_media_base(cc, media_base, sizeof(media_base)); char media_dir[600]; snprintf(media_dir, sizeof(media_dir), "%s/media", media_base); + struct file_io_info directory; + int directory_status = file_io_inspect(media_dir, 0, &directory); + if (directory_status == 1) return; // До первого вложения каталога может не быть. + if (directory_status < 0) return; + struct cache_rel_list rels = {0}; chat_media_foreach_media_message(cc, media_base, cache_rel_collect_cb, &rels); /* Ещё не опубликованные контейнеры и их временные файлы принадлежат crypto worker. */ @@ -1680,10 +1690,12 @@ static void chat_media_cache_cleanup_orphans(struct UTUN_INSTANCE* inst) { ow.media_base = media_base; ow.mb_len = strlen(media_base); ow.rels = rels.items; ow.rel_count = rels.count; ow.removed = 0; ow.removed_bytes = 0; - ma_dir_walk(media_dir, orphan_walk_cb, &ow); + int walk_result = ma_dir_walk(media_dir, orphan_walk_cb, &ow); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: cache orphan cleanup done removed=%d bytes=%llu", - CC_ID, ow.removed, (unsigned long long)ow.removed_bytes); + if (walk_result) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "%s: cache orphan cleanup incomplete removed=%d bytes=%llu", + CC_ID, ow.removed, (unsigned long long)ow.removed_bytes); + else DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: cache orphan cleanup done removed=%d bytes=%llu", + CC_ID, ow.removed, (unsigned long long)ow.removed_bytes); for (int i = 0; i < rels.count; i++) u_free(rels.items[i]); u_free(rels.items); diff --git a/src/media_delivery/media_index.c b/src/media_delivery/media_index.c index bdc592a3..3658d6a3 100644 --- a/src/media_delivery/media_index.c +++ b/src/media_delivery/media_index.c @@ -4,13 +4,16 @@ #include "../../lib/u_async.h" #include "../../lib/mem.h" #include "../../lib/debug_config.h" +#include "../../lib/file_io.h" +#include "../../lib/platform_compat.h" #include #include #include #include #include -#include +#include +#include #define MEDIA_BLOCK_MIN (10 * 1024 * 1024) #define MEDIA_BLOCK_MAX (25 * 1024 * 1024) @@ -105,7 +108,7 @@ int media_index_init(sqlite3* db) { int media_calc_block_size(uint64_t file_size) { if (file_size == 0) return 0; - uint64_t target = (file_size + MEDIA_BLOCK_TARGET - 1) / MEDIA_BLOCK_TARGET; + uint64_t target = (file_size - 1) / MEDIA_BLOCK_TARGET + 1; if (target < MEDIA_BLOCK_MIN) target = MEDIA_BLOCK_MIN; if (target > MEDIA_BLOCK_MAX) target = MEDIA_BLOCK_MAX; return (int)target; @@ -151,8 +154,7 @@ int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* med } int media_index_generate_uuid(uint8_t uuid_out[16]) { - ma_uuid(uuid_out); - return 0; + return ma_uuid(uuid_out); } void media_index_result_free(struct media_index_result* result) { @@ -243,54 +245,63 @@ struct mi_reg_ctx { int err; }; +/* Только полная подготовка допускает commit; любая ошибка оставляет result для обычной очистки done. */ static void mi_reg_work(void* data) { - struct mi_reg_ctx* ctx = (struct mi_reg_ctx*)data; - - if (ctx->copy_file && ma_copy_file(ctx->src_path, ctx->dest_path) != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: copy failed %s -> %s", ctx->src_path, ctx->dest_path); - ctx->err = -10; return; + struct mi_reg_ctx* ctx = data; + FILE* file = NULL; uint8_t* buffer = NULL; + const char* phase = "copy"; + ctx->err = -10; + if (ctx->copy_file && ma_copy_file(ctx->src_path, ctx->dest_path)) goto done; + phase = "size"; ctx->err = -20; + int64_t size = ma_file_size(ctx->dest_path); + if (size <= 0) goto done; + int64_t block = media_calc_block_size((uint64_t)size); + uint64_t count = (uint64_t)(size / block + (size % block != 0)); + phase = "block count"; ctx->err = -21; + if (count > INT_MAX || count > UINT32_MAX / 64) goto done; + ctx->result.file_size = size; ctx->result.block_size = block; ctx->result.num_blocks = (int)count; + phase = "hash"; ctx->err = -30; + if (ma_sha256_file(ctx->dest_path, ctx->result.content_hash)) goto done; + phase = "media UUID"; ctx->err = -31; + if (ma_uuid(ctx->result.media_id)) goto done; + phase = "block arrays"; ctx->err = -40; + ctx->result.block_ids = u_malloc((uint32_t)count * 16); + ctx->result.block_sigs = u_malloc((uint32_t)count * 64); + if (!ctx->result.block_ids || !ctx->result.block_sigs) goto done; + phase = "open"; ctx->err = -50; + file = file_io_open_read(ctx->dest_path); + if (!file) goto done; + for (int n = 0; n < ctx->result.num_blocks; n++) { + int64_t offset = (int64_t)n * block; + size_t length = (size_t)(n == ctx->result.num_blocks - 1 ? size - offset : block); + phase = "block allocation"; ctx->err = -60; + buffer = u_malloc((uint32_t)length); + if (!buffer) goto done; + phase = "seek"; ctx->err = -70; +#ifdef _WIN32 + if (_fseeki64(file, offset, SEEK_SET)) goto done; +#else + if ((int64_t)(off_t)offset != offset || fseeko(file, (off_t)offset, SEEK_SET)) goto done; +#endif + phase = "read"; + if (fread(buffer, 1, length, file) != length) goto done; + phase = "block UUID"; ctx->err = -81; + if (ma_uuid(ctx->result.block_ids + (size_t)n * 16)) goto done; + phase = "block signature"; ctx->err = -80; + if (ma_sign_block(ctx->ed25519_privkey, buffer, length, ctx->result.block_sigs + (size_t)n * 64)) goto done; + u_free(buffer); buffer = NULL; } - - int64_t fsize = (int64_t)ma_file_size(ctx->dest_path); - if (fsize <= 0) { ctx->err = -20; return; } - ctx->result.file_size = fsize; - - int64_t bs = (int64_t)media_calc_block_size((uint64_t)fsize); - int nb = (int)((fsize + bs - 1) / bs); - ctx->result.block_size = bs; - ctx->result.num_blocks = nb; - - if (ma_sha256_file(ctx->dest_path, ctx->result.content_hash) != 0) { - ctx->err = -30; return; - } - - ma_uuid(ctx->result.media_id); - - ctx->result.block_ids = u_malloc((size_t)nb * 16); - ctx->result.block_sigs = u_malloc((size_t)nb * 64); - if (!ctx->result.block_ids || !ctx->result.block_sigs) { ctx->err = -40; return; } - - FILE* f = fopen(ctx->dest_path, "rb"); - if (!f) { ctx->err = -50; return; } - - for (int n = 0; n < nb; n++) { - int64_t off = n * bs; - int64_t sz = (n == nb - 1) ? fsize - off : bs; - uint8_t* buf = u_malloc((size_t)sz); - if (!buf) { fclose(f); ctx->err = -60; return; } - fseeko(f, (off_t)off, SEEK_SET); - size_t rd = fread(buf, 1, (size_t)sz, f); - if (rd != (size_t)sz) { u_free(buf); fclose(f); ctx->err = -70; return; } - - ma_uuid(ctx->result.block_ids + n * 16); - if (ma_sign_block(ctx->ed25519_privkey, buf, (size_t)sz, - ctx->result.block_sigs + n * 64) != 0) { - u_free(buf); fclose(f); ctx->err = -80; return; - } - u_free(buf); - } - fclose(f); + phase = "close"; ctx->err = -71; + int close_error = fclose(file); file = NULL; + if (close_error) goto done; ctx->err = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "media_index prepared path=%s bytes=%lld blocks=%d", + ctx->dest_path, (long long)size, ctx->result.num_blocks); +done: + if (ctx->err) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index preparation failed phase=%s path=%s error=%d errno=%d", + phase, ctx->dest_path, ctx->err, errno); + u_free(buffer); + if (file && fclose(file)) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media_index cleanup close failed path=%s errno=%d", ctx->dest_path, errno); } static void mi_reg_done(void* arg, int err) { @@ -325,8 +336,14 @@ void media_index_register_async( return; } + if (!*src_path || !*dest_path || strlen(src_path) >= sizeof(((struct mi_reg_ctx*)0)->src_path) + || strlen(dest_path) >= sizeof(((struct mi_reg_ctx*)0)->dest_path) + || strlen(chat_id) >= sizeof(((struct mi_reg_ctx*)0)->chat_id) + || strlen(media_base) >= sizeof(((struct mi_reg_ctx*)0)->media_base)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index register oversized/empty path"); cb(cb_arg, -1, NULL); return; + } struct mi_reg_ctx* ctx = u_calloc(1, sizeof(*ctx)); - if (!ctx) { cb(cb_arg, -1, NULL); return; } + if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index context allocation failed"); cb(cb_arg, -1, NULL); return; } ctx->ua = ua; ctx->db = db; diff --git a/src/media_delivery/media_index.h b/src/media_delivery/media_index.h index 73c1c941..2c7519a6 100644 --- a/src/media_delivery/media_index.h +++ b/src/media_delivery/media_index.h @@ -67,7 +67,7 @@ void media_index_register_async( // ── Вспомогательные операции ── -// Заполнить 16 байт идентификатора; возвращает 0. +// Заполнить 16 байт идентификатора; 0/-1. При ошибке RNG выход обнулён, использовать его нельзя. int media_index_generate_uuid(uint8_t uuid_out[16]); // Освободить только массивы собственного result и обнулить их указатели; callback-result освобождает сам модуль.