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 }