gout

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

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;