From 9e0949434eb53341909e81644f92dbf21ea2b25d Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 14:39:46 +0200 Subject: [PATCH] Preserve video temp mode and test durable file commit boundaries --- src/video/video.c | 4 +-- tests/Makefile.am | 2 +- tests/test_media_primitives.c | 53 +++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/video/video.c b/src/video/video.c index 93358ec4..6879f894 100644 --- a/src/video/video.c +++ b/src/video/video.c @@ -713,7 +713,7 @@ static int vc_run(struct vc_ctx* ctx) { static void vc_thread_entry(void* raw) { struct vc_ctx* ctx = raw; video_ffmpeg_init(); - if (file_io_paths(ctx->source, ctx->destination) || file_io_temp_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; } + if (file_io_paths(ctx->source, ctx->destination) || file_io_temp_open(&ctx->file, ctx->destination, 0)) { ctx->error = -1; return; } struct stat info; if (!stat(ctx->source, &info)) ctx->source_size = info.st_size; int error = vc_run(ctx); @@ -844,7 +844,7 @@ int video_extract_thumbnail(const char* src, const char* dst, int max_w) { } if (error < 0) goto done; phase = "write JPEG"; - if (file_io_temp_open(&output, dst)) { error = AVERROR(EIO); goto done; } + if (file_io_temp_open(&output, dst, 0)) { error = AVERROR(EIO); goto done; } if (fwrite(packet->data, 1, packet->size, output.file) != (size_t)packet->size) { error = AVERROR(errno ? errno : EIO); goto done; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 7ad32aa5..b5608f8e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -884,7 +884,7 @@ if OS_LINUX test_media_primitives_LDFLAGS = -Wl,--wrap=fopen -Wl,--wrap=fdopen -Wl,--wrap=fread -Wl,--wrap=__fread_chk -Wl,--wrap=ferror \ -Wl,--wrap=fwrite -Wl,--wrap=fflush -Wl,--wrap=fclose -Wl,--wrap=rename -Wl,--wrap=RAND_bytes \ -Wl,--wrap=opendir -Wl,--wrap=readdir -Wl,--wrap=closedir -Wl,--wrap=fseeko \ - -Wl,--wrap=pthread_create -Wl,--wrap=u_calloc_impl -Wl,--wrap=uasync_post_reserved + -Wl,--wrap=pthread_create -Wl,--wrap=u_calloc_impl -Wl,--wrap=uasync_post_reserved -Wl,--wrap=fsync endif test_media_index_SOURCES = test_media_index.c diff --git a/tests/test_media_primitives.c b/tests/test_media_primitives.c index 9a82355a..a02be870 100644 --- a/tests/test_media_primitives.c +++ b/tests/test_media_primitives.c @@ -19,7 +19,8 @@ static char root[256], source[512], destination[512]; static unsigned visits; #ifdef __linux__ -enum fault { NONE, READ, WRITE, FLUSH, SOURCE_CLOSE, OUTPUT_CLOSE, FDOPEN, PUBLISH, OPEN_DIR, READ_DIR, CLOSE_DIR, SEEK, THREAD }; +enum fault { NONE, READ, WRITE, FLUSH, SOURCE_CLOSE, OUTPUT_CLOSE, FDOPEN, PUBLISH, OPEN_DIR, READ_DIR, CLOSE_DIR, SEEK, THREAD, + SYNC_FILE, SYNC_DIRECTORY }; static enum fault fault; static int injected, read_failed, rng_after = -1, allocation_after = -1; static FILE *input_file, *output_file; @@ -35,6 +36,7 @@ extern size_t __real_fwrite(const void*, size_t, size_t, FILE*); extern int __real_ferror(FILE*), __real_fflush(FILE*), __real_fclose(FILE*); extern int __real_rename(const char*, const char*), __real_RAND_bytes(unsigned char*, int); extern int __real_fseeko(FILE*, off_t, int); +extern int __real_fsync(int); extern DIR *__real_opendir(const char*); extern struct dirent *__real_readdir(DIR*); extern int __real_closedir(DIR*); @@ -84,6 +86,11 @@ int __wrap_fclose(FILE *file) { return result; } int __wrap_rename(const char *from, const char *to) { return inject(PUBLISH) ? -1 : __real_rename(from, to); } +int __wrap_fsync(int fd) { + struct stat data; assert(!fstat(fd, &data)); + if (inject(S_ISDIR(data.st_mode) ? SYNC_DIRECTORY : SYNC_FILE)) return -1; + return __real_fsync(fd); +} int __wrap_RAND_bytes(unsigned char *out, int size) { if (rng_after == 0) { injected = 1; return 0; } if (rng_after > 0) rng_after--; @@ -169,7 +176,7 @@ static void temporary_tests(void) { struct file_io_temp outputs[3] = {0}; char paths[3][FILE_IO_PATH_MAX], stale[FILE_IO_PATH_MAX]; for (unsigned i = 0; i < 3; i++) { - assert(!file_io_temp_open(&outputs[i], destination)); + assert(!file_io_temp_open(&outputs[i], destination, 0)); snprintf(paths[i], sizeof(paths[i]), "%s", outputs[i].temporary); assert(file_io_remove_orphan(paths[i]) == 0); assert(fputs("owned", outputs[i].file) >= 0 && !file_io_temp_finish(&outputs[i])); @@ -187,10 +194,10 @@ static void temporary_tests(void) { #ifdef __linux__ struct file_io_temp failed = {0}; fault = FDOPEN; injected = 0; - assert(file_io_temp_open(&failed, destination) == -1 && injected); fault = NONE; + assert(file_io_temp_open(&failed, destination, 0) == -1 && injected); fault = NONE; assert(failed.temporary[0] && file_io_remove_orphan(failed.temporary) == 0); file_io_temp_discard(&failed); - assert(!file_io_temp_open(&failed, destination) && !file_io_temp_finish(&failed)); + assert(!file_io_temp_open(&failed, destination, 0) && !file_io_temp_finish(&failed)); fault = PUBLISH; injected = 0; assert(file_io_temp_publish(&failed, source, destination) == -1 && injected); fault = NONE; assert(file_io_remove_orphan(failed.temporary) == 0); file_io_temp_discard(&failed); @@ -198,6 +205,40 @@ static void temporary_tests(void) { DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS active temp ownership: open/finished, multiple generations, publish/discard and failures"); } +/* Ошибка до rename сохраняет прежний файл; отказ directory sync уже не отменяет замену. */ +static void durable_tests(void) { + struct file_io_temp output = {0}; + write_text(destination, "previous"); + assert(!file_io_temp_open(&output, destination, FILE_IO_DURABLE)); + assert(fputs("durable", output.file) >= 0 && !file_io_temp_finish(&output)); + check_text(destination, "previous"); assert(file_io_remove_orphan(output.temporary) == 0); + assert(!file_io_temp_publish(&output, source, destination) && !output.temporary[0]); + file_io_temp_discard(&output); check_text(destination, "durable"); + assert(!file_io_sync_file(destination) && !file_io_sync_directory("relative-name")); +#ifndef _WIN32 + assert(!file_io_sync_directory("/file")); +#endif +#ifdef __linux__ + assert(!file_io_temp_open(&output, destination, FILE_IO_DURABLE)); + assert(fputs("failed", output.file) >= 0); + fault = SYNC_FILE; injected = 0; + assert(file_io_temp_finish(&output) == -1 && injected && !output.file && output.temporary[0]); fault = NONE; + file_io_temp_discard(&output); check_text(destination, "durable"); + assert(!file_io_temp_open(&output, destination, FILE_IO_DURABLE)); + assert(fputs("installed", output.file) >= 0 && !file_io_temp_finish(&output)); + fault = SYNC_DIRECTORY; injected = 0; + assert(file_io_temp_publish(&output, source, destination) == -1 && injected && !output.temporary[0]); fault = NONE; + file_io_temp_discard(&output); check_text(destination, "installed"); + assert(!file_io_temp_open(&output, destination, 0)); + assert(fputs("ordinary", output.file) >= 0); + fault = SYNC_FILE; injected = 0; + assert(!file_io_temp_finish(&output) && !injected); fault = SYNC_DIRECTORY; + assert(!file_io_temp_publish(&output, source, destination) && !injected); fault = NONE; + file_io_temp_discard(&output); check_text(destination, "ordinary"); +#endif + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS durable temp: sync-before-close, rename commit point, directory failure and ordinary mode"); +} + static void crypto_tests(void) { uint8_t output[64], zero[64] = {0}; memset(output, 0xa5, sizeof(output)); @@ -291,7 +332,7 @@ static void registration_tests(struct UASYNC *ua) { struct temp_job { struct file_io_temp output; pthread_barrier_t barrier; int publish; }; static void* temp_worker(void* arg) { struct temp_job* job = arg; - assert(!file_io_temp_open(&job->output, destination)); + assert(!file_io_temp_open(&job->output, destination, 0)); assert(fputs("worker", job->output.file) >= 0 && !file_io_temp_finish(&job->output)); pthread_barrier_wait(&job->barrier); pthread_barrier_wait(&job->barrier); if (job->publish) assert(!file_io_temp_publish(&job->output, source, destination)); @@ -384,7 +425,7 @@ int main(void) { #endif assert(!utun_mkdir(root, 0700)); snprintf(source, sizeof(source), "%s/source", root); snprintf(destination, sizeof(destination), "%s/destination", root); - copy_tests(); temporary_tests(); crypto_tests(); size_and_walk_tests(); + copy_tests(); temporary_tests(); durable_tests(); crypto_tests(); size_and_walk_tests(); struct UASYNC *ua = uasync_create(); assert(ua); registration_tests(ua); #ifdef __linux__