gout

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

cache.c (5520B)


      1 #include "cache.h"
      2 
      3 #include <assert.h>
      4 #include <err.h>
      5 #include <libgen.h>
      6 #include <stdbool.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <sys/stat.h>
     10 #include <sys/types.h>
     11 #include <unistd.h>
     12 
     13 #include "git/git.h"
     14 #include "utils.h"
     15 
     16 static const char* kTempCacheTemplate = "cache.XXXXXXXXXXXX";
     17 static const mode_t kReadWriteAll =
     18     S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
     19 
     20 struct Cache {
     21   const FileSystem* fs;
     22   char* cache_path;
     23   char* temp_cache_path;
     24   bool owns_streams;
     25 
     26   bool can_add_commits;
     27   WriteCommitRow write_commit_row;
     28   FILE* cache_in;
     29   FILE* cache_out;
     30   char* lastoid_in;
     31   bool wrote_lastoid_out;
     32 };
     33 
     34 Cache* cache_create(FILE* cache_in,
     35                     FILE* cache_out,
     36                     WriteCommitRow write_func) {
     37   assert(cache_out != NULL);
     38   assert(write_func != NULL);
     39   Cache* cache = ecalloc(1, sizeof(Cache));
     40   cache->can_add_commits = true;
     41   cache->write_commit_row = write_func;
     42   cache->cache_in = cache_in;
     43   cache->cache_out = cache_out;
     44 
     45   // Read lastoid from previous cache, if it exists.
     46   cache->lastoid_in = ecalloc(GOUT_OID_MAX_SIZE, sizeof(char));
     47   if (cache->cache_in) {
     48     // Read into a local buffer that has extra space for \r\n.
     49     char line_buf[GOUT_OID_MAX_SIZE + 2];
     50     char* oid_str = fgets(line_buf, sizeof(line_buf), cache->cache_in);
     51     if (oid_str) {
     52       // Strip trailing newline/carriage return before copying.
     53       line_buf[strcspn(line_buf, "\r\n")] = '\0';
     54       estrlcpy(cache->lastoid_in, line_buf, GOUT_OID_MAX_SIZE);
     55     } else {
     56       warnx("corrupt cachefile");
     57     }
     58   }
     59   cache->wrote_lastoid_out = false;
     60 
     61   return cache;
     62 }
     63 
     64 Cache* cache_open(const FileSystem* fs,
     65                   const char* path,
     66                   WriteCommitRow write_func) {
     67   assert(fs != NULL);
     68   assert(path != NULL);
     69   assert(write_func != NULL);
     70 
     71   char* path_copy = estrdup(path);
     72   const char* dir = dirname(path_copy);
     73   char* temp_cache_path = path_concat(dir, kTempCacheTemplate);
     74   free(path_copy);
     75 
     76   FILE* cache_in = fs->fopen(path, "r");
     77   int out_fd = fs->mkstemp(temp_cache_path);
     78   if (out_fd == -1) {
     79     if (cache_in) {
     80       fs->fclose(cache_in);
     81     }
     82     err(1, "mkstemp: %s", temp_cache_path);
     83   }
     84   FILE* cache_out = fs->fdopen(out_fd, "w");
     85   if (!cache_out) {
     86     fs->close(out_fd);
     87     if (cache_in) {
     88       fs->fclose(cache_in);
     89     }
     90     err(1, "fdopen: %s", temp_cache_path);
     91   }
     92 
     93   Cache* cache = cache_create(cache_in, cache_out, write_func);
     94   cache->fs = fs;
     95   cache->cache_path = estrdup(path);
     96   cache->temp_cache_path = temp_cache_path;
     97   cache->owns_streams = true;
     98   return cache;
     99 }
    100 
    101 void cache_free(Cache* cache) {
    102   if (!cache) {
    103     return;
    104   }
    105   if (cache->owns_streams) {
    106     if (cache->cache_in) {
    107       cache->fs->fclose(cache->cache_in);
    108     }
    109     if (cache->cache_out) {
    110       cache->fs->fclose(cache->cache_out);
    111     }
    112   }
    113   free(cache->cache_path);
    114   free(cache->temp_cache_path);
    115   free(cache->lastoid_in);
    116   free(cache);
    117 }
    118 
    119 bool cache_can_add_commits(const Cache* cache) {
    120   assert(cache != NULL);
    121   return cache->can_add_commits;
    122 }
    123 
    124 void cache_add_commit_row(Cache* cache, const GitCommit* commit) {
    125   assert(cache != NULL);
    126   assert(commit != NULL);
    127   // The first row of the file is the last OID.
    128   if (!cache->wrote_lastoid_out) {
    129     fprintf(cache->cache_out, "%s\n", commit->oid);
    130     cache->wrote_lastoid_out = true;
    131   }
    132   // If all commits are already written; do nothing.
    133   if (!cache->can_add_commits ||
    134       strncmp(commit->oid, cache->lastoid_in, GOUT_OID_MAX_SIZE - 1) == 0) {
    135     cache->can_add_commits = false;
    136     return;
    137   }
    138   // The rest of the file is the log contents up to and including that commit.
    139   cache->write_commit_row(cache->cache_out, commit);
    140 }
    141 
    142 void cache_finish(Cache* cache) {
    143   assert(cache != NULL);
    144   if (cache->cache_in && !cache->can_add_commits) {
    145     // If we didn't write any records, copy the previous cache lastoid.
    146     if (!cache->wrote_lastoid_out) {
    147       fprintf(cache->cache_out, "%s\n", cache->lastoid_in);
    148       cache->wrote_lastoid_out = true;
    149     }
    150     // append previous cached log to new cached log.
    151     char buf[BUFSIZ];
    152     size_t n;
    153     while ((n = fread(buf, 1, sizeof(buf), cache->cache_in)) > 0) {
    154       if (fwrite(buf, 1, n, cache->cache_out) != n) {
    155         break;
    156       }
    157     }
    158   }
    159 }
    160 
    161 void cache_close_and_replace(Cache* cache, FILE* out) {
    162   assert(cache != NULL);
    163   assert(cache->owns_streams);
    164   assert(out != NULL);
    165 
    166   cache_finish(cache);
    167 
    168   if (cache->cache_in) {
    169     cache->fs->fclose(cache->cache_in);
    170     cache->cache_in = NULL;
    171   }
    172   cache->fs->fclose(cache->cache_out);
    173   cache->cache_out = NULL;
    174 
    175   if (cache->fs->rename(cache->temp_cache_path, cache->cache_path)) {
    176     err(1, "rename: %s -> %s", cache->temp_cache_path, cache->cache_path);
    177   }
    178 
    179   mode_t mask = umask(0);
    180   umask(mask);
    181   if (cache->fs->chmod(cache->cache_path, kReadWriteAll & ~mask)) {
    182     err(1, "chmod: %s", cache->cache_path);
    183   }
    184 
    185   FILE* fcache = cache->fs->fopen(cache->cache_path, "r");
    186   if (!fcache) {
    187     err(1, "fopen: %s", cache->cache_path);
    188   }
    189   cache_copy_log(fcache, out);
    190   cache->fs->fclose(fcache);
    191 }
    192 
    193 void cache_copy_log(FILE* fcache, FILE* out) {
    194   assert(fcache != NULL);
    195   assert(out != NULL);
    196   // Copy the log lines to out.
    197   char* line = NULL;
    198   size_t len = 0;
    199   bool is_sha = true;
    200   while (getline(&line, &len, fcache) != -1) {
    201     // Skip the first line containing the commit SHA.
    202     if (is_sha) {
    203       is_sha = false;
    204       continue;
    205     }
    206     fprintf(out, "%s", line);
    207   }
    208   free(line);
    209 }