gout

A static git page generator
git clone https://git.bracken.jp/gout.git
Log | Files | Refs | README | LICENSE

commit f1c0362fd86ca639e2694d1343afce39d39a80d6
parent 2ed590a4100e6c6ecb4ae72dbed322774f85baf0
Author: Chris Bracken <chris@bracken.jp>
Date:   Sat,  6 Jun 2026 14:42:23 +0900

fs_inmemory: prevent duplicate file slots and stale reads

When opening a file in write mode, reuse the existing file slot if the
path already exists instead of appending a duplicate slot in order to
prevent readers in read mode from reading stale content from the first
slot.

Diffstat:
Msrc/fs_inmemory.c | 25++++++++++++++++++-------
Msrc/gout_tests.c | 37+++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/src/fs_inmemory.c b/src/fs_inmemory.c @@ -43,14 +43,25 @@ static int inmemory_mkdirp(const char* path) { static FILE* inmemory_fopen(const char* path, const char* mode) { if (strcmp(mode, "w") == 0) { - if (g_files_count >= MAX_MOCK_FILES) { - return NULL; + InMemoryFile* file = NULL; + for (size_t i = 0; i < g_files_count; i++) { + if (strcmp(g_files[i].path, path) == 0) { + file = &g_files[i]; + free(file->buf); + file->buf = NULL; + file->size = 0; + break; + } + } + if (!file) { + if (g_files_count >= MAX_MOCK_FILES) { + return NULL; + } + file = &g_files[g_files_count++]; + file->path = estrdup(path); + file->buf = NULL; + file->size = 0; } - // Register a new file. - InMemoryFile* file = &g_files[g_files_count++]; - file->path = estrdup(path); - file->buf = NULL; - file->size = 0; FILE* f = open_memstream(&file->buf, &file->size); register_active_stream(f); return f; diff --git a/src/gout_tests.c b/src/gout_tests.c @@ -16,3 +16,40 @@ UTEST(gout_options_create, ReturnsInitializedOptions) { ASSERT_NE(NULL, options); gout_options_free(options); } + +#include "fs_inmemory.h" + +UTEST(fs_inmemory, overwrite_file) { + inmemory_fs_clear(); + + // 1. Write initial content. + FILE* f1 = g_fs_inmemory->fopen("test_file.txt", "w"); + ASSERT_NE(NULL, f1); + fprintf(f1, "Initial Content"); + g_fs_inmemory->fclose(f1); + + // Verify we can read it. + FILE* r1 = g_fs_inmemory->fopen("test_file.txt", "r"); + ASSERT_NE(NULL, r1); + char buf[64]; + memset(buf, 0, sizeof(buf)); + fgets(buf, sizeof(buf), r1); + EXPECT_STREQ("Initial Content", buf); + g_fs_inmemory->fclose(r1); + + // 2. Overwrite the file. + FILE* f2 = g_fs_inmemory->fopen("test_file.txt", "w"); + ASSERT_NE(NULL, f2); + fprintf(f2, "New Updated Content"); + g_fs_inmemory->fclose(f2); + + // Verify that the read returns the NEW updated content. + FILE* r2 = g_fs_inmemory->fopen("test_file.txt", "r"); + ASSERT_NE(NULL, r2); + memset(buf, 0, sizeof(buf)); + fgets(buf, sizeof(buf), r2); + EXPECT_STREQ("New Updated Content", buf); + g_fs_inmemory->fclose(r2); + + inmemory_fs_clear(); +}