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 }