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:
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();
+}