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