diff --git a/lib/file_io.c b/lib/file_io.c index 87cb925a..7bf2e12e 100644 --- a/lib/file_io.c +++ b/lib/file_io.c @@ -142,7 +142,11 @@ static int file_io_sync_descriptor(FILE *file, const char *path) { #else int result = fsync(fileno(file)); #endif - if (result) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file sync failed path=%s errno=%d", path, errno); + if (result) { + int error = errno ? errno : EIO; + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file sync failed path=%s errno=%d", path, error); + errno = error; + } return result ? -1 : 0; } diff --git a/tests/test_encrypted_attachment.c b/tests/test_encrypted_attachment.c index b30ba997..846be8ec 100644 --- a/tests/test_encrypted_attachment.c +++ b/tests/test_encrypted_attachment.c @@ -96,6 +96,46 @@ void __wrap_media_download_handle_chunk(struct UTUN_INSTANCE* inst, uint64_t gro static void hex(const uint8_t* p, size_t len, char* out) { for (size_t i = 0; i < len; i++) snprintf(out + i * 2, 3, "%02x", p[i]); } + +/* Startup/restart удаляет только строгие собственные stale temp; живые owners обоих деревьев сохраняются. */ +static int start_with_recovery(struct UTUN_INSTANCE* inst, const char* base) { + struct file_io_temp live[2] = {0}; + char target[2][1024], stale[2][1100], foreign[2][1100]; + const char* trees[] = {"media", "decrypted"}; + const char* extensions[] = {"uenc", "file"}; + int result = -1; + for (unsigned i = 0; i < 2; i++) { + char directory[900]; snprintf(directory, sizeof(directory), "%s/%s", base, trees[i]); + if (utun_mkdir(directory, 0700) && errno != EEXIST) goto done; + snprintf(directory, sizeof(directory), "%s/%s/123", base, trees[i]); + if (utun_mkdir(directory, 0700) && errno != EEXIST) goto done; + snprintf(target[i], sizeof(target[i]), "%s/00000000000000000000000000000001.%s", directory, extensions[i]); + snprintf(stale[i], sizeof(stale[i]), "%s.tmp.00000000000000000000000000000002", target[i]); + snprintf(foreign[i], sizeof(foreign[i]), "%s.tmp.foreign", target[i]); + const char* paths[] = {target[i], stale[i], foreign[i]}; + for (unsigned j = 0; j < 3; j++) { + FILE* file = fopen(paths[j], "wb"); + if (!file) goto done; + int failed = fputs("retained", file) < 0; + if (fclose(file)) failed = 1; + if (failed) goto done; + } + if (file_io_temp_open(&live[i], target[i], FILE_IO_DURABLE) || fputs("live", live[i].file) < 0) goto done; + if (i ? file_io_temp_finish(&live[i]) : fflush(live[i].file)) goto done; + } + if (utun_instance_init(inst)) goto done; + for (unsigned i = 0; i < 2; i++) { + if (ma_file_size(stale[i]) >= 0 || ma_file_size(target[i]) != 8 || ma_file_size(foreign[i]) != 8 || + ma_file_size(live[i].temporary) != 4 || file_io_remove_orphan(live[i].temporary) != 0) goto done; + } + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS attachment restart recovery: both trees, open/finished owners, final and foreign files"); + result = 0; +done: + for (unsigned i = 0; i < 2; i++) file_io_temp_discard(&live[i]); + if (result) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment recovery fixture failed base=%s errno=%d", base, errno); + return result; +} + static int start_node(struct media_test* t, int index) { char path[768], pub[65], priv[65], server[65], db[768]; hex(t->keys[index].public_key, 32, pub); hex(t->keys[index].private_key, 32, priv); @@ -117,7 +157,7 @@ static int start_node(struct media_test* t, int index) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "encrypted attachment fixture: node=%d port=%u retained for restart", index, t->ports[index]); } if (t->nodes[index] && index != S) test_etcp_set_peer(t->nodes[index], "to_s", t->nodes[S], "s1"); - return t->nodes[index] ? utun_instance_init(t->nodes[index]) : -1; + return t->nodes[index] ? start_with_recovery(t->nodes[index], db) : -1; } /* Фикстура создаёт канал напрямую, поэтому сама снимает gate после реального Merkle round. */