From 3b425477408126d1b0aab666a7169abf91c847a4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 17:17:08 +0100 Subject: [PATCH] Report native Android database cleanup failures accurately --- tests/test_android_bridge.c | 48 ++++++++++++++++++- .../jni_bridge/android_jni_bridge.c | 44 ++++++++++++----- .../jni_bridge/android_jni_bridge.h | 5 ++ 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/tests/test_android_bridge.c b/tests/test_android_bridge.c index 855f0d4b..c4e6373b 100644 --- a/tests/test_android_bridge.c +++ b/tests/test_android_bridge.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "../lib/debug_config.h" #include "../lib/mem.h" @@ -78,6 +79,47 @@ static int check_bridge_config(void) { return 0; } +/* Настоящий unlink: все три файла, повторное удаление, ошибка одного пути и восстановление после неё. */ +static int check_database_cleanup(const char* directory) { + REQUIRE(utun_bridge_clear_database(NULL) == -1); + REQUIRE(utun_bridge_clear_database("") == -1); + REQUIRE(!utun_bridge_clear_database(directory)); + const char* names[] = {"chats.db", "chats.db-wal", "chats.db-shm"}; + char path[1024]; + for (unsigned i = 0; i < 3; ++i) { + snprintf(path, sizeof(path), "%s/%s", directory, names[i]); + FILE* file = fopen(path, "wb"); REQUIRE(file); + int written = fputs("fixture", file); + int closed = fclose(file); + REQUIRE(written >= 0 && !closed); + } + REQUIRE(!utun_bridge_clear_database(directory)); + for (unsigned i = 0; i < 3; ++i) { + snprintf(path, sizeof(path), "%s/%s", directory, names[i]); + REQUIRE(test_unlink(path) == -1 && errno == ENOENT); + } + REQUIRE(!utun_bridge_clear_database(directory)); + snprintf(path, sizeof(path), "%s/chats.db-wal", directory); + REQUIRE(!utun_mkdir(path, 0700)); + for (unsigned i = 0; i < 3; i += 2) { + snprintf(path, sizeof(path), "%s/%s", directory, names[i]); + FILE* file = fopen(path, "wb"); REQUIRE(file); + int written = fputs("fixture", file); + int closed = fclose(file); + REQUIRE(written >= 0 && !closed); + } + REQUIRE(utun_bridge_clear_database(directory) == -1); + for (unsigned i = 0; i < 3; i += 2) { + snprintf(path, sizeof(path), "%s/%s", directory, names[i]); + REQUIRE(test_unlink(path) == -1 && errno == ENOENT); + } + snprintf(path, sizeof(path), "%s/chats.db-wal", directory); + REQUIRE(!test_rmdir(path)); + REQUIRE(!utun_bridge_clear_database(directory)); + printf("[PASS] database cleanup: exact files, missing files, real unlink failure and retry\n"); + return 0; +} + /* Один и тот же файл декодируется покадрово и батчами: PCM и количество отсчётов должны совпасть. */ static int check_voice_batches(const char* path, int rate, int channels) { int frame = rate / 50 * channels, count = frame * 27; @@ -129,8 +171,12 @@ int main(void) { /* Проверки bridge требуют исправного владения таблицей провайдера. */ if (!failed) failed += check_bridge_config(); utun_config_set_provider(NULL); - char directory[512] = "/tmp/utun_android_bridge_XXXXXX", path[1024]; + char directory[512], path[1024]; + const char* temporary = getenv("TMPDIR"); + int length = snprintf(directory, sizeof(directory), "%s/utun_android_bridge_XXXXXX", temporary ? temporary : "/tmp"); + REQUIRE(length > 0 && length < (int)sizeof(directory)); REQUIRE(!test_mkdtemp(directory)); + failed += check_database_cleanup(directory); snprintf(path, sizeof(path), "%s/voice.opus", directory); const int rates[] = {8000, 12000, 16000, 24000, 48000}; for (unsigned i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 62c738c5..74e9e33a 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -51,7 +51,8 @@ #include #include #include -#include +#include +#include "../../../lib/platform_compat.h" #include #ifdef __ANDROID__ @@ -176,6 +177,35 @@ void utun_bridge_destroy(void) { bridge_log(BLEV_INFO, "bridge destroy"); } +/* Удалять только после остановки ядра: отсутствие файла нормально, остальные ошибки не скрывать. */ +int utun_bridge_clear_database(const char* db_path) { + if (!db_path || !db_path[0]) { bridge_log(BLEV_ERROR, "clearDatabase: no db path"); return -1; } + const char* names[] = {"chats.db", "chats.db-wal", "chats.db-shm"}; + size_t length = strlen(db_path); + if (length > SIZE_MAX - sizeof("/chats.db-wal")) { + bridge_log(BLEV_ERROR, "clearDatabase: path length overflow bytes=%zu", length); return -1; + } + size_t capacity = length + sizeof("/chats.db-wal"); + char* path = u_malloc(capacity); + if (!path) { bridge_log(BLEV_ERROR, "clearDatabase: path allocation failed bytes=%zu", capacity); return -1; } + unsigned removed = 0, missing = 0, failed = 0; + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { + snprintf(path, capacity, "%s/%s", db_path, names[i]); + if (!unlink(path)) removed++; + else { + int error = errno; + if (error == ENOENT) missing++; + else { + failed++; + bridge_log(BLEV_ERROR, "clearDatabase: unlink failed path=%s errno=%d: %s", path, error, strerror(error)); + } + } + } + u_free(path); + bridge_log(failed ? BLEV_WARN : BLEV_INFO, "clearDatabase: path=%s removed=%u missing=%u failed=%u", db_path, removed, missing, failed); + return failed ? -1 : 0; +} + int utun_bridge_start(const char* config_text) { bridge_log(BLEV_INFO, "bridge start: launching utun instance"); int rc = instance_lite_start(config_text); @@ -2462,17 +2492,7 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeRegenerateKeys( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeClearDatabase( JNIEnv* env, jobject thiz) { (void)env; (void)thiz; - if (!g_db_path[0]) { - bridge_log(BLEV_ERROR, "clearDatabase: no db path"); - return JNI_FALSE; - } - char db_file[512], wal_file[512], shm_file[512]; - snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path); - snprintf(wal_file, sizeof(wal_file), "%s/chats.db-wal", g_db_path); - snprintf(shm_file, sizeof(shm_file), "%s/chats.db-shm", g_db_path); - unlink(db_file); unlink(wal_file); unlink(shm_file); - bridge_log(BLEV_INFO, "clearDatabase: deleted DB files at %s", g_db_path); - return JNI_TRUE; + return utun_bridge_clear_database(g_db_path) == 0 ? JNI_TRUE : JNI_FALSE; } /* ── Voice recording JNI ── */ diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.h b/tools/chatgui-android/jni_bridge/android_jni_bridge.h index e1b7a0ef..47887202 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.h +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.h @@ -54,6 +54,11 @@ bool utun_bridge_init(const char* db_path, int control_port, * Готовность сервисов — CHAT_EVT_SERVICE_STARTED, а не возврат start. */ int utun_bridge_start(const char* config_text); +/* После полного stop, без параллельного start: удалить chats.db и его -wal/-shm из db_path. + * db_path — непустой каталог. 0 — все файлы удалены/уже отсутствуют; -1 — неверный путь, память либо ошибка IO. + * При частичном отказе проверяет остальные файлы; каждую ошибку и итоговые счётчики пишет в лог. */ +int utun_bridge_clear_database(const char* db_path); + /* Освободить recorder, вызвать instance_lite_stop и снять C log/config callbacks. * Config-provider сбрасывается к defaults; повторный init начинает новую регистрацию. * Перед вызовом остановить внешние media workers/DSP; ограничения ожидания — instance_lite.h.