gout

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

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;