gout

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

cache.c (5547B)


      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     free(temp_cache_path);
     80     if (cache_in) {
     81       fs->fclose(cache_in);
     82     }
     83     err(1, "mkstemp: %s", temp_cache_path);
     84   }
     85   FILE* cache_out = fs->fdopen(out_fd, "w");
     86   if (!cache_out) {
     87     fs->close(out_fd);
     88     free(temp_cache_path);
     89     if (cache_in) {
     90       fs->fclose(cache_in);
     91     }
     92     err(1, "fdopen: %s", temp_cache_path);
     93   }
     94 
     95   Cache* cache = cache_create(cache_in, cache_out, write_func);
     96   cache->fs = fs;
     97   cache->cache_path = estrdup(path);
     98   cache->temp_cache_path = temp_cache_path;
     99   cache->owns_streams = true;
    100   return cache;
    101 }
    102 
    103 void cache_free(Cache* cache) {
    104   if (!cache) {
    105     return;
    106   }
    107   if (cache->owns_streams) {
    108     if (cache->cache_in) {
    109       cache->fs->fclose(cache->cache_in);
    110     }
    111     if (cache->cache_out) {
    112       cache->fs->fclose(cache->cache_out);
    113     }
    114   }
    115   free(cache->cache_path);
    116   free(cache->temp_cache_path);
    117   free(cache->lastoid_in);
    118   free(cache);
    119 }
    120 
    121 bool cache_can_add_commits(const Cache* cache) {
    122   assert(cache != NULL);
    123   return cache->can_add_commits;
    124 }
    125 
    126 void cache_add_commit_row(Cache* cache, const GitCommit* commit) {
    127   assert(cache != NULL);
    128   assert(commit != NULL);
    129   // The first row of the file is the last OID.
    130   if (!cache->wrote_lastoid_out) {
    131     fprintf(cache->cache_out, "%s\n", commit->oid);
    132     cache->wrote_lastoid_out = true;
    133   }
    134   // If all commits are already written; do nothing.
    135   if (!cache->can_add_commits ||
    136       strncmp(commit->oid, cache->lastoid_in, GOUT_OID_MAX_SIZE - 1) == 0) {
    137     cache->can_add_commits = false;
    138     return;
    139   }
    140   // The rest of the file is the log contents up to and including that commit.
    141   cache->write_commit_row(cache->cache_out, commit);
    142 }
    143 
    144 void cache_finish(Cache* cache) {
    145   assert(cache != NULL);
    146   if (cache->cache_in) {
    147     // If we didn't write any records, copy the previous cache lastoid.
    148     if (!cache->wrote_lastoid_out) {
    149       fprintf(cache->cache_out, "%s\n", cache->lastoid_in);
    150       cache->wrote_lastoid_out = true;
    151     }
    152     // append previous cached log to new cached log.
    153     char buf[BUFSIZ];
    154     size_t n;
    155     while ((n = fread(buf, 1, sizeof(buf), cache->cache_in)) > 0) {
    156       if (fwrite(buf, 1, n, cache->cache_out) != n) {
    157         break;
    158       }
    159     }
    160   }
    161 }
    162 
    163 void cache_close_and_replace(Cache* cache, FILE* out) {
    164   assert(cache != NULL);
    165   assert(cache->owns_streams);
    166   assert(out != NULL);
    167 
    168   cache_finish(cache);
    169 
    170   if (cache->cache_in) {
    171     cache->fs->fclose(cache->cache_in);
    172     cache->cache_in = NULL;
    173   }
    174   cache->fs->fclose(cache->cache_out);
    175   cache->cache_out = NULL;
    176 
    177   if (cache->fs->rename(cache->temp_cache_path, cache->cache_path)) {
    178     err(1, "rename: %s -> %s", cache->temp_cache_path, cache->cache_path);
    179   }
    180 
    181   mode_t mask = umask(0);
    182   umask(mask);
    183   if (cache->fs->chmod(cache->cache_path, kReadWriteAll & ~mask)) {
    184     err(1, "chmod: %s", cache->cache_path);
    185   }
    186 
    187   FILE* fcache = cache->fs->fopen(cache->cache_path, "r");
    188   if (!fcache) {
    189     err(1, "fopen: %s", cache->cache_path);
    190   }
    191   cache_copy_log(fcache, out);
    192   cache->fs->fclose(fcache);
    193 }
    194 
    195 void cache_copy_log(FILE* fcache, FILE* out) {
    196   assert(fcache != NULL);
    197   assert(out != NULL);
    198   // Copy the log lines to out.
    199   char* line = NULL;
    200   size_t len = 0;
    201   bool is_sha = true;
    202   while (getline(&line, &len, fcache) != -1) {
    203     // Skip the first line containing the commit SHA.
    204     if (is_sha) {
    205       is_sha = false;
    206       continue;
    207     }
    208     fprintf(out, "%s", line);
    209   }
    210   free(line);
    211 }