fs_inmemory.c (5383B)
1 #include "fs_inmemory.h" 2 3 #include <err.h> 4 #include <limits.h> 5 #include <stdbool.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <sys/stat.h> 10 #include <unistd.h> 11 12 #include "utils.h" 13 14 typedef struct { 15 char* path; 16 char* buf; 17 size_t size; 18 } InMemoryFile; 19 20 #define MAX_MOCK_FILES 1024 21 static InMemoryFile g_files[MAX_MOCK_FILES]; 22 static size_t g_files_count = 0; 23 24 static FILE* g_active_streams[MAX_MOCK_FILES]; 25 static size_t g_active_streams_count = 0; 26 27 static void register_active_stream(FILE* f) { 28 if (g_active_streams_count < MAX_MOCK_FILES) { 29 g_active_streams[g_active_streams_count++] = f; 30 } 31 } 32 33 static int inmemory_mkdir(const char* path, mode_t mode) { 34 (void)path; 35 (void)mode; 36 return 0; 37 } 38 39 static int inmemory_mkdirp(const char* path) { 40 (void)path; 41 return 0; 42 } 43 44 static FILE* inmemory_fopen(const char* path, const char* mode) { 45 if (strcmp(mode, "w") == 0) { 46 if (g_files_count >= MAX_MOCK_FILES) { 47 return NULL; 48 } 49 // Register a new file. 50 InMemoryFile* file = &g_files[g_files_count++]; 51 file->path = estrdup(path); 52 file->buf = NULL; 53 file->size = 0; 54 FILE* f = open_memstream(&file->buf, &file->size); 55 register_active_stream(f); 56 return f; 57 } 58 if (strcmp(mode, "r") == 0) { 59 for (size_t i = 0; i < g_files_count; i++) { 60 if (strcmp(g_files[i].path, path) == 0) { 61 if (g_files[i].buf) { 62 FILE* f = fmemopen(g_files[i].buf, g_files[i].size, "r"); 63 if (!f) { 64 return NULL; 65 } 66 register_active_stream(f); 67 return f; 68 } 69 } 70 } 71 } 72 return NULL; 73 } 74 75 static int inmemory_fclose(FILE* stream) { 76 for (size_t i = 0; i < g_active_streams_count; i++) { 77 if (g_active_streams[i] == stream) { 78 // Remove from active streams by shifting the rest down. 79 if (i < g_active_streams_count - 1) { 80 memmove(&g_active_streams[i], &g_active_streams[i + 1], 81 (g_active_streams_count - i - 1) * sizeof(FILE*)); 82 } 83 g_active_streams_count--; 84 return fclose(stream); 85 } 86 } 87 return fclose(stream); // Fallback for untracked streams. 88 } 89 90 const char* inmemory_fs_get_buffer(const char* path) { 91 // We MUST flush all active streams to ensure buffers are up to date. 92 for (size_t i = 0; i < g_active_streams_count; i++) { 93 fflush(g_active_streams[i]); 94 } 95 for (size_t i = 0; i < g_files_count; i++) { 96 if (strcmp(g_files[i].path, path) == 0) { 97 return g_files[i].buf; 98 } 99 } 100 return NULL; 101 } 102 103 void inmemory_fs_clear(void) { 104 for (size_t i = 0; i < g_active_streams_count; i++) { 105 fclose(g_active_streams[i]); 106 } 107 g_active_streams_count = 0; 108 109 for (size_t i = 0; i < g_files_count; i++) { 110 free(g_files[i].path); 111 free(g_files[i].buf); 112 g_files[i].path = NULL; 113 g_files[i].buf = NULL; 114 g_files[i].size = 0; 115 } 116 g_files_count = 0; 117 } 118 119 static int inmemory_mkstemp(char* template) { 120 // We MUST return a real file descriptor because the code calls fdopen(). 121 return mkstemp(template); 122 } 123 124 static FILE* inmemory_fdopen(int fd, const char* mode) { 125 return fdopen(fd, mode); 126 } 127 128 static int inmemory_close(int fd) { 129 return close(fd); 130 } 131 132 static int inmemory_rename(const char* oldpath, const char* newpath) { 133 // If oldpath is a real file on disk (from mkstemp), we read it into our 134 // in-memory registry and then delete the disk file. 135 if (access(oldpath, F_OK) == 0) { 136 FILE* f = fopen(oldpath, "r"); 137 if (f) { 138 fseek(f, 0, SEEK_END); 139 long fsize = ftell(f); 140 fseek(f, 0, SEEK_SET); 141 142 char* content = malloc(fsize + 1); 143 if (fread(content, 1, fsize, f) != (size_t)fsize) { 144 err(1, "fread: %s", oldpath); 145 } 146 content[fsize] = '\0'; 147 fclose(f); 148 149 // Register/Update the path in our in-memory map. 150 bool found = false; 151 for (size_t i = 0; i < g_files_count; i++) { 152 if (strcmp(g_files[i].path, newpath) == 0) { 153 free(g_files[i].buf); 154 g_files[i].buf = content; 155 g_files[i].size = fsize; 156 found = true; 157 break; 158 } 159 } 160 if (!found) { 161 if (g_files_count < MAX_MOCK_FILES) { 162 InMemoryFile* file = &g_files[g_files_count++]; 163 file->path = estrdup(newpath); 164 file->buf = content; 165 file->size = fsize; 166 } else { 167 free(content); 168 } 169 } 170 unlink(oldpath); 171 return 0; 172 } 173 } 174 return 0; 175 } 176 177 static int inmemory_chmod(const char* path, mode_t mode) { 178 (void)path; 179 (void)mode; 180 return 0; 181 } 182 183 static int inmemory_access(const char* path, int amode) { 184 (void)amode; 185 for (size_t i = 0; i < g_files_count; i++) { 186 if (strcmp(g_files[i].path, path) == 0) { 187 return 0; 188 } 189 } 190 return -1; 191 } 192 193 static char* inmemory_realpath(const char* path, char* resolved_path) { 194 if (resolved_path) { 195 estrlcpy(resolved_path, path, PATH_MAX); 196 return resolved_path; 197 } 198 return estrdup(path); 199 } 200 201 static const FileSystem kStdInmemoryFs = { 202 203 .mkdir = inmemory_mkdir, 204 .mkdirp = inmemory_mkdirp, 205 .fopen = inmemory_fopen, 206 .fdopen = inmemory_fdopen, 207 .fclose = inmemory_fclose, 208 .close = inmemory_close, 209 .mkstemp = inmemory_mkstemp, 210 .rename = inmemory_rename, 211 .chmod = inmemory_chmod, 212 .access = inmemory_access, 213 .realpath = inmemory_realpath, 214 }; 215 216 const FileSystem* g_fs_inmemory = &kStdInmemoryFs;