cache.c (4171B)
1 #include "cache.h" 2 3 #include <err.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <sys/stat.h> 7 #include <sys/types.h> 8 #include <unistd.h> 9 10 #include "git/git.h" 11 #include "utils.h" 12 13 struct Cache { 14 bool can_add_commits; 15 char* cache_path; 16 char* temp_cache_path; 17 WriteCommitRow write_commit_row; 18 FILE* cache_in; 19 FILE* cache_out; 20 char* lastoid_in; 21 bool wrote_lastoid_out; 22 }; 23 24 static const char* kTempCachePath = "cache.XXXXXXXXXXXX"; 25 static const mode_t kReadWriteAll = 26 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; 27 28 Cache* cache_create(const char* cache_path, WriteCommitRow write_func) { 29 Cache* cache = ecalloc(1, sizeof(Cache)); 30 cache->can_add_commits = true; 31 cache->cache_path = estrdup(cache_path); 32 cache->temp_cache_path = estrdup(kTempCachePath); 33 cache->write_commit_row = write_func; 34 35 // Open previous cache for reading, if it exists, and read lastoid. 36 cache->lastoid_in = ecalloc(kOidLen, sizeof(char)); 37 cache->cache_in = fopen(cache_path, "r"); 38 if (cache->cache_in) { 39 // OID + '\n' + '\0'. 40 char buf[kOidLen + 1]; 41 char* oid_str = fgets(buf, sizeof(buf), cache->cache_in); 42 if (!oid_str) { 43 err(1, "fgets"); 44 } 45 estrlcpy(cache->lastoid_in, oid_str, sizeof(buf)); 46 } 47 cache->wrote_lastoid_out = false; 48 49 // Create temporary cache and open for writing. 50 int out_fd = mkstemp(cache->temp_cache_path); 51 if (out_fd == -1) { 52 err(1, "mkstemp"); 53 } 54 cache->cache_out = fdopen(out_fd, "w"); 55 if (!cache->cache_out) { 56 err(1, "fdopen"); 57 } 58 return cache; 59 } 60 61 void cache_free(Cache* cache) { 62 if (!cache) { 63 return; 64 } 65 free(cache->cache_path); 66 cache->cache_path = NULL; 67 free(cache->temp_cache_path); 68 cache->temp_cache_path = NULL; 69 // Clean up in case the cache wasn't written. 70 if (cache->cache_in) { 71 fclose(cache->cache_in); 72 cache->cache_in = NULL; 73 } 74 if (cache->cache_out) { 75 fclose(cache->cache_out); 76 cache->cache_out = NULL; 77 } 78 free(cache->lastoid_in); 79 cache->lastoid_in = NULL; 80 free(cache); 81 } 82 83 bool cache_can_add_commits(const Cache* cache) { 84 return cache->can_add_commits; 85 } 86 87 void cache_add_commit_row(Cache* cache, const GitCommit* commit) { 88 // The first row of the file is the last OID. 89 if (!cache->wrote_lastoid_out) { 90 fprintf(cache->cache_out, "%s\n", gitcommit_oid(commit)); 91 cache->wrote_lastoid_out = true; 92 } 93 // If all commits are already written; do nothing. 94 if (!cache->can_add_commits || 95 strncmp(gitcommit_oid(commit), cache->lastoid_in, kOidLen - 1) == 0) { 96 cache->can_add_commits = false; 97 return; 98 } 99 // The rest of the file is the log contents up to and including that commit. 100 cache->write_commit_row(cache->cache_out, commit); 101 } 102 103 void cache_write(Cache* cache) { 104 if (cache->cache_in) { 105 // If we didn't write any records, copy the previous cache lastoid. 106 if (!cache->wrote_lastoid_out) { 107 fprintf(cache->cache_out, "%s\n", cache->lastoid_in); 108 cache->wrote_lastoid_out = true; 109 } 110 // append previous cached log to new cached log. 111 char buf[BUFSIZ]; 112 while (!feof(cache->cache_in)) { 113 size_t n = fread(buf, 1, sizeof(buf), cache->cache_in); 114 if (ferror(cache->cache_in)) { 115 break; 116 } 117 if (fwrite(buf, 1, n, cache->cache_out) != n) { 118 break; 119 } 120 } 121 fclose(cache->cache_in); 122 cache->cache_in = NULL; 123 } 124 fclose(cache->cache_out); 125 cache->cache_out = NULL; 126 127 // Replace previous cache with new cache. 128 if (rename(cache->temp_cache_path, cache->cache_path)) { 129 err(1, "rename"); 130 } 131 132 // Set the cache to read-write for user, group, other, modulo umask. 133 mode_t mask; 134 umask(mask = umask(0)); 135 if (chmod(cache->cache_path, kReadWriteAll & ~mask)) { 136 err(1, "chmod"); 137 } 138 } 139 140 void cache_copy_log(Cache* cache, FILE* out) { 141 FILE* fcache = efopen(cache->cache_path, "r"); 142 143 // Copy the log lines to out. 144 char* line = NULL; 145 size_t len = 0; 146 bool is_sha = true; 147 while (getline(&line, &len, fcache) != -1) { 148 // Skip the first line containing the commit SHA. 149 if (is_sha) { 150 is_sha = false; 151 continue; 152 } 153 fprintf(out, "%s", line); 154 } 155 free(line); 156 157 fclose(fcache); 158 }