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 }