fs_inmemory.c (5763B)
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 InMemoryFile* file = NULL; 47 for (size_t i = 0; i < g_files_count; i++) { 48 if (strcmp(g_files[i].path, path) == 0) { 49 file = &g_files[i]; 50 free(file->buf); 51 file->buf = NULL; 52 file->size = 0; 53 break; 54 } 55 } 56 if (!file) { 57 if (g_files_count >= MAX_MOCK_FILES) { 58 return NULL; 59 } 60 file = &g_files[g_files_count++]; 61 file->path = estrdup(path); 62 file->buf = NULL; 63 file->size = 0; 64 } 65 FILE* f = open_memstream(&file->buf, &file->size); 66 register_active_stream(f); 67 return f; 68 } 69 if (strcmp(mode, "r") == 0) { 70 for (size_t i = 0; i < g_files_count; i++) { 71 if (strcmp(g_files[i].path, path) == 0) { 72 if (g_files[i].buf) { 73 FILE* f = fmemopen(g_files[i].buf, g_files[i].size, "r"); 74 if (!f) { 75 return NULL; 76 } 77 register_active_stream(f); 78 return f; 79 } 80 } 81 } 82 } 83 return NULL; 84 } 85 86 static int inmemory_fclose(FILE* stream) { 87 for (size_t i = 0; i < g_active_streams_count; i++) { 88 if (g_active_streams[i] == stream) { 89 // Remove from active streams by shifting the rest down. 90 if (i < g_active_streams_count - 1) { 91 memmove(&g_active_streams[i], &g_active_streams[i + 1], 92 (g_active_streams_count - i - 1) * sizeof(FILE*)); 93 } 94 g_active_streams_count--; 95 return fclose(stream); 96 } 97 } 98 return fclose(stream); // Fallback for untracked streams. 99 } 100 101 const char* inmemory_fs_get_buffer(const char* path) { 102 // We MUST flush all active streams to ensure buffers are up to date. 103 for (size_t i = 0; i < g_active_streams_count; i++) { 104 fflush(g_active_streams[i]); 105 } 106 for (size_t i = 0; i < g_files_count; i++) { 107 if (strcmp(g_files[i].path, path) == 0) { 108 return g_files[i].buf; 109 } 110 } 111 return NULL; 112 } 113 114 void inmemory_fs_clear(void) { 115 for (size_t i = 0; i < g_active_streams_count; i++) { 116 fclose(g_active_streams[i]); 117 } 118 g_active_streams_count = 0; 119 120 for (size_t i = 0; i < g_files_count; i++) { 121 free(g_files[i].path); 122 free(g_files[i].buf); 123 g_files[i].path = NULL; 124 g_files[i].buf = NULL; 125 g_files[i].size = 0; 126 } 127 g_files_count = 0; 128 } 129 130 static int inmemory_mkstemp(char* template) { 131 // We MUST return a real file descriptor because the code calls fdopen(). 132 return mkstemp(template); 133 } 134 135 static FILE* inmemory_fdopen(int fd, const char* mode) { 136 return fdopen(fd, mode); 137 } 138 139 static int inmemory_close(int fd) { 140 return close(fd); 141 } 142 143 static int inmemory_rename(const char* oldpath, const char* newpath) { 144 // If oldpath is a real file on disk (from mkstemp), we read it into our 145 // in-memory registry and then delete the disk file. 146 if (access(oldpath, F_OK) == 0) { 147 FILE* f = fopen(oldpath, "r"); 148 if (f) { 149 fseek(f, 0, SEEK_END); 150 long fsize = ftell(f); 151 if (fsize < 0) { 152 err(1, "ftell: %s", oldpath); 153 } 154 fseek(f, 0, SEEK_SET); 155 156 char* content = malloc(fsize + 1); 157 if (!content) { 158 err(1, "malloc"); 159 } 160 if (fread(content, 1, fsize, f) != (size_t)fsize) { 161 err(1, "fread: %s", oldpath); 162 } 163 content[fsize] = '\0'; 164 fclose(f); 165 166 // Register/Update the path in our in-memory map. 167 bool found = false; 168 for (size_t i = 0; i < g_files_count; i++) { 169 if (strcmp(g_files[i].path, newpath) == 0) { 170 free(g_files[i].buf); 171 g_files[i].buf = content; 172 g_files[i].size = fsize; 173 found = true; 174 break; 175 } 176 } 177 if (!found) { 178 if (g_files_count < MAX_MOCK_FILES) { 179 InMemoryFile* file = &g_files[g_files_count++]; 180 file->path = estrdup(newpath); 181 file->buf = content; 182 file->size = fsize; 183 } else { 184 free(content); 185 } 186 } 187 unlink(oldpath); 188 return 0; 189 } 190 } 191 return 0; 192 } 193 194 static int inmemory_chmod(const char* path, mode_t mode) { 195 (void)path; 196 (void)mode; 197 return 0; 198 } 199 200 static int inmemory_access(const char* path, int amode) { 201 (void)amode; 202 for (size_t i = 0; i < g_files_count; i++) { 203 if (strcmp(g_files[i].path, path) == 0) { 204 return 0; 205 } 206 } 207 return -1; 208 } 209 210 static char* inmemory_realpath(const char* path, char* resolved_path) { 211 if (resolved_path) { 212 estrlcpy(resolved_path, path, PATH_MAX); 213 return resolved_path; 214 } 215 return estrdup(path); 216 } 217 218 static const FileSystem kStdInmemoryFs = { 219 220 .mkdir = inmemory_mkdir, 221 .mkdirp = inmemory_mkdirp, 222 .fopen = inmemory_fopen, 223 .fdopen = inmemory_fdopen, 224 .fclose = inmemory_fclose, 225 .close = inmemory_close, 226 .mkstemp = inmemory_mkstemp, 227 .rename = inmemory_rename, 228 .chmod = inmemory_chmod, 229 .access = inmemory_access, 230 .realpath = inmemory_realpath, 231 }; 232 233 const FileSystem* g_fs_inmemory = &kStdInmemoryFs;