diff --git a/src/chat/chat_channel.c b/src/chat/chat_channel.c index a764047b..f66d6358 100644 --- a/src/chat/chat_channel.c +++ b/src/chat/chat_channel.c @@ -330,6 +330,9 @@ void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id) { return; } if (!ch_id || !ch_id[0]) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel — empty ch_id", CC_ID); return; } + if (!sqlite3_get_autocommit(cc->db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel rejected inside external transaction ch=%s", CC_ID, ch_id); return; + } if (chat_media_defer_channel_delete(inst, ch_id)) return; DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel BEGIN ch=%s", CC_ID, ch_id); @@ -337,7 +340,6 @@ void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id) { /* 1. отмена медиа-загрузок/релэев/суперузлов канала (держат conn_mgr handle группы) */ media_delivery_cancel_channel(inst, gid); - chat_attachment_delete_channel(inst, ch_id); member_files_cancel_group(inst, gid); group_avatar_cancel_group(inst, gid); @@ -357,16 +359,22 @@ void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id) { /* 5. немедленно убрать канал из кеша chat_sync */ chat_sync_on_channel_deleted(inst, ch_id); - /* 6. удалить медиафайлы канала на диске + строки media_files */ - char media_base[MEDIA_BASE_MAX]; - int media_error = media_index_get_base(cc->db, media_base) || media_index_delete_channel(cc->db, ch_id, media_base); - if (media_error) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "channel=%s removed with incomplete media cleanup; retry required", ch_id); - - /* 7. удалить таблицы и метаданные канала */ + /* 6. удалить таблицы, метаданные и attachment-записи одной SQL-транзакцией до файловой очистки */ if (topo_node_sqlite_channel_delete(cc->db, ch_id)) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel metadata deletion failed ch=%s", ch_id); return; + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel metadata deletion failed ch=%s; files preserved, retry required", ch_id); + if (!sqlite3_get_autocommit(cc->db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel delete left transaction open ch=%s; requesting owner stop", ch_id); + chat_attachment_quiesce(inst); utun_instance_stop(inst); + } + return; } + /* 7. удалить файлы только после commit; I/O-отказ не восстанавливает удалённый канал */ + int media_error = chat_attachment_delete_channel(inst, ch_id); + char media_base[MEDIA_BASE_MAX]; + if (media_index_get_base(cc->db, media_base) || media_index_delete_channel(cc->db, ch_id, media_base)) media_error = 1; + if (media_error) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "channel=%s removed with incomplete media cleanup; retry required", ch_id); + /* 8. уведомить GUI */ { uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch_id); diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 275a8887..976efdcd 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -623,19 +623,29 @@ int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id) { if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC); if (rc == SQLITE_OK) rc = sqlite3_step(st); if (rc != SQLITE_DONE) goto fail; - sqlite3_finalize(st); st = NULL; + rc = sqlite3_finalize(st); st = NULL; + if (rc != SQLITE_OK) goto fail; + /* IGNORE может вернуть DONE, сохранив канал: это не успешное удаление. */ + rc = sqlite3_prepare_v2(db, "SELECT 1 FROM channels WHERE channel_id=?", -1, &st, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(st); + if (rc != SQLITE_DONE) goto fail; + rc = sqlite3_finalize(st); st = NULL; + if (rc != SQLITE_OK) goto fail; rc = sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='merkle_tree_hash'", -1, &st, NULL); if (rc != SQLITE_OK) goto fail; rc = sqlite3_step(st); if (rc != SQLITE_ROW && rc != SQLITE_DONE) goto fail; int has_merkle = rc == SQLITE_ROW; - sqlite3_finalize(st); st = NULL; + rc = sqlite3_finalize(st); st = NULL; + if (rc != SQLITE_OK) goto fail; if (has_merkle) { rc = sqlite3_prepare_v2(db, "DELETE FROM merkle_tree_hash WHERE substr(namespace,instr(namespace,':')+1)=?", -1, &st, NULL); if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC); if (rc == SQLITE_OK) rc = sqlite3_step(st); if (rc != SQLITE_DONE) goto fail; - sqlite3_finalize(st); st = NULL; + rc = sqlite3_finalize(st); st = NULL; + if (rc != SQLITE_OK) goto fail; } if ((rc = sqlite3_exec(db, "RELEASE delete_topo_channel", NULL, NULL, NULL)) != SQLITE_OK) goto fail; DEBUG_INFO(DEBUG_CATEGORY_BGP, "channel deleted: channel=%s tables=peers/msg/feedback merkle=%d", channel_id, has_merkle);