Document preparation ownership and log exact media index failures

This commit is contained in:
evgeny committed 2026-10-22 15:06:00 +02:00
1 parent 3bb8f76b7d
commit f123a1bdeb
2 files changed
+33 -15

No files matched your search

+24 -10
View File
@@ -67,7 +67,8 @@ static int mi_location_valid(const char* location) {
int media_index_path(const char* base, const char* location, char path[MEDIA_PATH_MAX]) {
char clean[MEDIA_BASE_MAX];
if (!path || mi_clean_path(base, clean, sizeof(clean)) || mi_location_valid(location)) return -1;
if (!path) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index path without output"); return -1; }
if (mi_clean_path(base, clean, sizeof(clean)) || mi_location_valid(location)) return -1;
int size = snprintf(path, MEDIA_PATH_MAX, "%s%s%s", clean, clean[strlen(clean) - 1] == '/' ? "" : "/", location);
if (size >= MEDIA_PATH_MAX || size < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index joined path too long base=%s location=%s", clean, location); return -1;
@@ -77,7 +78,8 @@ int media_index_path(const char* base, const char* location, char path[MEDIA_PAT
int media_index_location(const char* path, const char* base, char location[MEDIA_PATH_MAX]) {
char full[MEDIA_PATH_MAX], root[MEDIA_BASE_MAX];
if (!location || mi_clean_path(path, full, sizeof(full)) || mi_clean_path(base, root, sizeof(root))) return -1;
if (!location) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index location without output"); return -1; }
if (mi_clean_path(path, full, sizeof(full)) || mi_clean_path(base, root, sizeof(root))) return -1;
size_t length = strlen(root);
const char* relative = NULL;
if (!strcmp(root, ".") && full[0] != '/' && !strchr(full, ':')) relative = full;
@@ -113,16 +115,23 @@ int media_index_get_base(sqlite3* db, char base[MEDIA_BASE_MAX]) {
int media_index_set_base(sqlite3* db, const char* base) {
char clean[MEDIA_BASE_MAX]; sqlite3_stmt* st = NULL;
if (!db || mi_clean_path(base, clean, sizeof(clean))) return -1;
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index set base without DB"); return -1; }
if (mi_clean_path(base, clean, sizeof(clean))) return -1;
if (mi_exec(db, "CREATE TABLE IF NOT EXISTS ui_state(key TEXT PRIMARY KEY,value TEXT)")) return -1;
int rc = sqlite3_prepare_v2(db, "INSERT INTO ui_state(key,value) VALUES('media_base',?) ON CONFLICT(key) DO NOTHING", -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, clean, -1, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
int finish = st ? sqlite3_finalize(st) : SQLITE_OK;
if (rc != SQLITE_DONE || finish != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index set base SQL failure requested=%s rc=%d: %s", clean, rc, sqlite3_errmsg(db));
return -1;
}
char stored[MEDIA_BASE_MAX];
if (rc == SQLITE_DONE && finish == SQLITE_OK && !media_index_get_base(db, stored) && !strcmp(stored, clean)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index conflicting/invalid media_base requested=%s rc=%d: %s", clean, rc, sqlite3_errmsg(db));
return -1;
if (media_index_get_base(db, stored)) return -1;
if (strcmp(stored, clean)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index base change rejected requested=%s stored=%s", clean, stored); return -1;
}
return 0;
}
int media_index_track_preparation(sqlite3* db, const char* chat_id, const char* path, const char* base) {
@@ -182,8 +191,9 @@ static int mi_insert(sqlite3* db,
int changed = sqlite3_changes(db), finish = sqlite3_finalize(stmt);
if (rc != SQLITE_DONE || finish != SQLITE_OK || changed != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index insert/conflict chat=%s chunk=%d rc=%d changed=%d: %s",
chat_id, chunk, rc, changed, sqlite3_errmsg(db));
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index %s chat=%s block=%02x%02x%02x%02x chunk=%d path=%s rc=%d: %s",
rc == SQLITE_DONE && finish == SQLITE_OK && !changed ? "conflicting block metadata" : "insert failed",
chat_id, block_id[0], block_id[1], block_id[2], block_id[3], chunk, location, rc, sqlite3_errmsg(db));
return -1;
}
return 0;
@@ -341,7 +351,9 @@ static int mi_result_valid(const struct media_index_result* result) {
if (result && result->file_size > 0 && result->block_size > 0 && result->num_blocks > 0 && result->block_ids
&& (uint64_t)result->num_blocks <= UINT32_MAX / 64
&& (result->file_size - 1) / result->block_size + 1 == result->num_blocks) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index invalid block geometry/arrays"); return -1;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index invalid geometry bytes=%lld block_size=%lld count=%d ids=%p",
(long long)(result ? result->file_size : 0), (long long)(result ? result->block_size : 0),
result ? result->num_blocks : 0, (void*)(result ? result->block_ids : NULL)); return -1;
}
/* Общий атомарный путь для автора и держателя скачанного файла. */
@@ -369,7 +381,9 @@ static int mi_register_file(sqlite3* db, const struct media_index_result* result
int32_t chunk = n; memcpy(message + used, &chunk, 4); used += 4;
memcpy(message + used, &offset, 8); used += 8;
memcpy(message + used, &node_id, 8); used += 8;
if (ma_sign_block(key, message, used, signature)) { failed = 1; break; }
if (ma_sign_block(key, message, used, signature)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index signing failed chat=%s chunk=%d", chat_id, n); failed = 1; break;
}
}
if (mi_insert(db, result->media_id, result->block_ids + (size_t)n * 16, result->content_hash,
chat_id, location, node_id, signature, ts, result->file_size, size, n, offset)) { failed = 1; break; }
+9 -5
View File
@@ -2,6 +2,8 @@
* Порядок: init → register_async → callback с UUID, хешем и подписями блоков; затем можно анонсировать файл.
* Register_async копирует/хеширует/подписывает файл в worker, затем commit и callback выполняются в uasync.
* db/ma/ua заимствованы и должны жить до завершения; доступ к БД выполняется в потоке ядра.
* Назначения учитываются в media_preparations до запуска worker; неуспешная подготовка оставляет их для удаления канала.
* Перед delete_channel вызывающий должен дождаться всех своих workers/completions; функция их сама не отменяет.
* Результат callback заимствован только до возврата. result_free освобождает block_ids/block_sigs, но не саму структуру. */
#ifndef MEDIA_INDEX_H
#define MEDIA_INDEX_H
@@ -36,8 +38,8 @@ int media_index_get_base(sqlite3* db, char base[MEDIA_BASE_MAX]);
int media_index_track_preparation(sqlite3* db, const char* chat_id, const char* path, const char* base);
struct media_index_result {
uint8_t media_id[16];
uint8_t content_hash[32];
uint8_t media_id[16]; // случайный идентификатор целого файла
uint8_t content_hash[32]; // SHA256 полного содержимого
int64_t file_size; // полный размер файла, байты
int64_t block_size; // размер обычного блока, байты; последний может быть короче
int num_blocks; // число блоков и элементов block_ids/block_sigs
@@ -47,7 +49,7 @@ struct media_index_result {
// ── Инициализация таблицы ──
// Создать таблицу media_files и её индексы, сохранив существующие записи; 0/-1.
// Создать media_files, media_preparations и индексы, сохранив записи; ошибка любого DDL даёт -1, успех 0.
int media_index_init(sqlite3* db);
// ── Регистрация файлов и блоков ──
@@ -63,8 +65,10 @@ int media_index_commit(
uint64_t node_id, const uint8_t* ed25519_privkey,
const char* chat_id, const char* dest_path, const char* media_base);
/* Зарегистрировать скачанный блок в media_files (location — путь к собранному файлу),
* чтобы узел мог отдавать его как держатель. node_sign = нули (при отдаче не проверяется). */
/* Зарегистрировать один скачанный блок: location — канонический путь относительно общей базы; 0/-1.
* file_size/chunk_size >0, chunk/offset >=0, блок целиком помещается в файл.
* node_sign новой записи — нули (при отдаче не проверяется); существующая подпись при совпадающем повторе сохраняется.
* Для целого файла использовать register_downloaded_file; одиночные вызовы не образуют общую транзакцию. */
int media_index_register_downloaded(sqlite3* db, const uint8_t* media_id, const uint8_t* block_id,
const uint8_t* content_hash, const char* chat_id,
const char* location, uint64_t node_id,