gout

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

cache.c (5422B)


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