aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Hjemli2008-04-28 11:32:42 +0200
committerLars Hjemli2008-04-28 11:32:42 +0200
commit939d32fda70ea66c9db51687beb3cea6da7b0599 (patch)
tree50915facf89b78e3856fe6b0564a26c3678c01ba
parent9ec5cd7944a7099515b7d41107007d6332a2540e (diff)
downloadcgit-939d32fda70ea66c9db51687beb3cea6da7b0599.tar.gz
cgit-939d32fda70ea66c9db51687beb3cea6da7b0599.tar.bz2
cgit-939d32fda70ea66c9db51687beb3cea6da7b0599.zip
Redesign the caching layer
The original caching layer in cgit has no upper bound on the number of concurrent cache entries, so when cgit is traversed by a spider (like the googlebot), the cache might end up filling your disk. Also, if any error occurs in the cache layer, no content is returned to the client. This patch redesigns the caching layer to avoid these flaws by * giving the cache a bound number of slots * disabling the cache for the current request when errors occur The cache size limit is implemented by hashing the querystring (the cache lookup key) and generating a cache filename based on this hash modulo the cache size. In order to detect hash collisions, the full lookup key (i.e. the querystring) is stored in the cache file (separated from its associated content by ascii 0). The cache filename is the reversed 8-digit hexadecimal representation of hash(key) % cache_size which should make the filesystem lookup pretty fast (if directory content is indexed/sorted); reversing the representation avoids the problem where all keys have equal prefix. There is a new config option, cache-size, which sets the upper bound for the cache. Default value for this option is 0, which has the same effect as setting nocache=1 (hence nocache is now deprecated). Included in this patch is also a new testfile which verifies that the new option works as intended. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r--cache.c359
-rw-r--r--cache.h33
-rw-r--r--cgit.c166
-rw-r--r--cgit.h1
-rwxr-xr-xtests/setup.sh2
-rwxr-xr-xtests/t0020-validate-cache.sh67
6 files changed, 418 insertions, 210 deletions
diff --git a/cache.c b/cache.c
index 89f7ecd..e590d7b 100644
--- a/cache.c
+++ b/cache.c
@@ -4,117 +4,340 @@
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 *
8 *
9 * The cache is just a directory structure where each file is a cache slot,
10 * and each filename is based on the hash of some key (e.g. the cgit url).
11 * Each file contains the full key followed by the cached content for that
12 * key.
13 *
7 */ 14 */
8 15
9#include "cgit.h" 16#include "cgit.h"
10#include "cache.h" 17#include "cache.h"
11 18
12const int NOLOCK = -1; 19#define CACHE_BUFSIZE (1024 * 4)
20
21struct cache_slot {
22 const char *key;
23 int keylen;
24 int ttl;
25 cache_fill_fn fn;
26 void *cbdata;
27 int cache_fd;
28 int lock_fd;
29 const char *cache_name;
30 const char *lock_name;
31 int match;
32 struct stat cache_st;
33 struct stat lock_st;
34 int bufsize;
35 char buf[CACHE_BUFSIZE];
36};
13 37
14char *cache_safe_filename(const char *unsafe) 38/* Open an existing cache slot and fill the cache buffer with
39 * (part of) the content of the cache file. Return 0 on success
40 * and errno otherwise.
41 */
42static int open_slot(struct cache_slot *slot)
15{ 43{
16 static char buf[4][PATH_MAX]; 44 char *bufz;
17 static int bufidx; 45 int bufkeylen = -1;
18 char *s; 46
19 char c; 47 slot->cache_fd = open(slot->cache_name, O_RDONLY);
20 48 if (slot->cache_fd == -1)
21 bufidx++; 49 return errno;
22 bufidx &= 3; 50
23 s = buf[bufidx]; 51 if (fstat(slot->cache_fd, &slot->cache_st))
24 52 return errno;
25 while(unsafe && (c = *unsafe++) != 0) { 53
26 if (c == '/' || c == ' ' || c == '&' || c == '|' || 54 slot->bufsize = read(slot->cache_fd, slot->buf, sizeof(slot->buf));
27 c == '>' || c == '<' || c == '.') 55 if (slot->bufsize == 0)
28 c = '_'; 56 return errno;
29 *s++ = c; 57
30 } 58 bufz = memchr(slot->buf, 0, slot->bufsize);
31 *s = '\0'; 59 if (bufz)
32 return buf[bufidx]; 60 bufkeylen = bufz - slot->buf;
61
62 slot->match = bufkeylen == slot->keylen &&
63 !memcmp(slot->key, slot->buf, bufkeylen + 1);
64
65 return 0;
33} 66}
34 67
35int cache_exist(struct cacheitem *item) 68/* Close the active cache slot */
69static void close_slot(struct cache_slot *slot)
36{ 70{
37 if (stat(item->name, &item->st)) { 71 if (slot->cache_fd > 0) {
38 item->st.st_mtime = 0; 72 close(slot->cache_fd);
39 return 0; 73 slot->cache_fd = -1;
40 } 74 }
41 return 1;
42} 75}
43 76
44int cache_create_dirs() 77/* Print the content of the active cache slot (but skip the key). */
78static int print_slot(struct cache_slot *slot)
45{ 79{
46 char *path; 80 ssize_t i, j = 0;
81
82 i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET);
83 if (i != slot->keylen + 1)
84 return errno;
47 85
48 path = fmt("%s", ctx.cfg.cache_root); 86 while((i=read(slot->cache_fd, slot->buf, sizeof(slot->buf))) > 0)
49 if (mkdir(path, S_IRWXU) && errno!=EEXIST) 87 j = write(STDOUT_FILENO, slot->buf, i);
88
89 if (j < 0)
90 return errno;
91 else
50 return 0; 92 return 0;
93}
51 94
52 if (!ctx.repo) 95/* Check if the slot has expired */
96static int is_expired(struct cache_slot *slot)
97{
98 if (slot->ttl < 0)
53 return 0; 99 return 0;
100 else
101 return slot->cache_st.st_mtime + slot->ttl*60 < time(NULL);
102}
54 103
55 path = fmt("%s/%s", ctx.cfg.cache_root, 104/* Check if the slot has been modified since we opened it.
56 cache_safe_filename(ctx.repo->url)); 105 * NB: If stat() fails, we pretend the file is modified.
106 */
107static int is_modified(struct cache_slot *slot)
108{
109 struct stat st;
57 110
58 if (mkdir(path, S_IRWXU) && errno!=EEXIST) 111 if (stat(slot->cache_name, &st))
59 return 0; 112 return 1;
113 return (st.st_ino != slot->cache_st.st_ino ||
114 st.st_mtime != slot->cache_st.st_mtime ||
115 st.st_size != slot->cache_st.st_size);
116}
60 117
61 if (ctx.qry.page) { 118/* Close an open lockfile */
62 path = fmt("%s/%s/%s", ctx.cfg.cache_root, 119static void close_lock(struct cache_slot *slot)
63 cache_safe_filename(ctx.repo->url), 120{
64 ctx.qry.page); 121 if (slot->lock_fd > 0) {
65 if (mkdir(path, S_IRWXU) && errno!=EEXIST) 122 close(slot->lock_fd);
66 return 0; 123 slot->lock_fd = -1;
67 } 124 }
68 return 1;
69} 125}
70 126
71int cache_refill_overdue(const char *lockfile) 127/* Create a lockfile used to store the generated content for a cache
128 * slot, and write the slot key + \0 into it.
129 * Returns 0 on success and errno otherwise.
130 */
131static int lock_slot(struct cache_slot *slot)
72{ 132{
73 struct stat st; 133 slot->lock_fd = open(slot->lock_name, O_RDWR|O_CREAT|O_EXCL,
134 S_IRUSR|S_IWUSR);
135 if (slot->lock_fd == -1)
136 return errno;
137 write(slot->lock_fd, slot->key, slot->keylen + 1);
138 return 0;
139}
74 140
75 if (stat(lockfile, &st)) 141/* Release the current lockfile. If `replace_old_slot` is set the
76 return 0; 142 * lockfile replaces the old cache slot, otherwise the lockfile is
143 * just deleted.
144 */
145static int unlock_slot(struct cache_slot *slot, int replace_old_slot)
146{
147 int err;
148
149 if (replace_old_slot)
150 err = rename(slot->lock_name, slot->cache_name);
77 else 151 else
78 return (time(NULL) - st.st_mtime > ctx.cfg.cache_max_create_time); 152 err = unlink(slot->lock_name);
153 return err;
79} 154}
80 155
81int cache_lock(struct cacheitem *item) 156/* Generate the content for the current cache slot by redirecting
157 * stdout to the lock-fd and invoking the callback function
158 */
159static int fill_slot(struct cache_slot *slot)
82{ 160{
83 int i = 0; 161 int tmp;
84 char *lockfile = xstrdup(fmt("%s.lock", item->name));
85 162
86 top: 163 /* Preserve stdout */
87 if (++i > ctx.cfg.max_lock_attempts) 164 tmp = dup(STDOUT_FILENO);
88 die("cache_lock: unable to lock %s: %s", 165 if (tmp == -1)
89 item->name, strerror(errno)); 166 return errno;
90 167
91 item->fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); 168 /* Redirect stdout to lockfile */
169 if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
170 return errno;
92 171
93 if (item->fd == NOLOCK && errno == ENOENT && cache_create_dirs()) 172 /* Generate cache content */
94 goto top; 173 slot->fn(slot->cbdata);
95 174
96 if (item->fd == NOLOCK && errno == EEXIST && 175 /* Restore stdout */
97 cache_refill_overdue(lockfile) && !unlink(lockfile)) 176 if (dup2(tmp, STDOUT_FILENO) == -1)
98 goto top; 177 return errno;
99 178
100 free(lockfile); 179 /* Close the temporary filedescriptor */
101 return (item->fd > 0); 180 close(tmp);
181 return 0;
102} 182}
103 183
104int cache_unlock(struct cacheitem *item) 184/* Crude implementation of 32-bit FNV-1 hash algorithm,
185 * see http://www.isthe.com/chongo/tech/comp/fnv/ for details
186 * about the magic numbers.
187 */
188#define FNV_OFFSET 0x811c9dc5
189#define FNV_PRIME 0x01000193
190
191unsigned long hash_str(const char *str)
105{ 192{
106 close(item->fd); 193 unsigned long h = FNV_OFFSET;
107 return (rename(fmt("%s.lock", item->name), item->name) == 0); 194 unsigned char *s = (unsigned char *)str;
195
196 if (!s)
197 return h;
198
199 while(*s) {
200 h *= FNV_PRIME;
201 h ^= *s++;
202 }
203 return h;
108} 204}
109 205
110int cache_cancel_lock(struct cacheitem *item) 206static int process_slot(struct cache_slot *slot)
111{ 207{
112 return (unlink(fmt("%s.lock", item->name)) == 0); 208 int err;
209
210 err = open_slot(slot);
211 if (!err && slot->match) {
212 if (is_expired(slot)) {
213 if (!lock_slot(slot)) {
214 /* If the cachefile has been replaced between
215 * `open_slot` and `lock_slot`, we'll just
216 * serve the stale content from the original
217 * cachefile. This way we avoid pruning the
218 * newly generated slot. The same code-path
219 * is chosen if fill_slot() fails for some
220 * reason.
221 *
222 * TODO? check if the new slot contains the
223 * same key as the old one, since we would
224 * prefer to serve the newest content.
225 * This will require us to open yet another
226 * file-descriptor and read and compare the
227 * key from the new file, so for now we're
228 * lazy and just ignore the new file.
229 */
230 if (is_modified(slot) || fill_slot(slot)) {
231 unlock_slot(slot, 0);
232 close_lock(slot);
233 } else {
234 close_slot(slot);
235 unlock_slot(slot, 1);
236 slot->cache_fd = slot->lock_fd;
237 }
238 }
239 }
240 print_slot(slot);
241 close_slot(slot);
242 return 0;
243 }
244
245 /* If the cache slot does not exist (or its key doesn't match the
246 * current key), lets try to create a new cache slot for this
247 * request. If this fails (for whatever reason), lets just generate
248 * the content without caching it and fool the caller to belive
249 * everything worked out (but print a warning on stdout).
250 */
251
252 close_slot(slot);
253 if ((err = lock_slot(slot)) != 0) {
254 cache_log("[cgit] Unable to lock slot %s: %s (%d)\n",
255 slot->lock_name, strerror(err), err);
256 slot->fn(slot->cbdata);
257 return 0;
258 }
259
260 if ((err = fill_slot(slot)) != 0) {
261 cache_log("[cgit] Unable to fill slot %s: %s (%d)\n",
262 slot->lock_name, strerror(err), err);
263 unlock_slot(slot, 0);
264 close_lock(slot);
265 slot->fn(slot->cbdata);
266 return 0;
267 }
268 // We've got a valid cache slot in the lock file, which
269 // is about to replace the old cache slot. But if we
270 // release the lockfile and then try to open the new cache
271 // slot, we might get a race condition with a concurrent
272 // writer for the same cache slot (with a different key).
273 // Lets avoid such a race by just printing the content of
274 // the lock file.
275 slot->cache_fd = slot->lock_fd;
276 unlock_slot(slot, 1);
277 err = print_slot(slot);
278 close_slot(slot);
279 return err;
113} 280}
114 281
115int cache_expired(struct cacheitem *item) 282/* Print cached content to stdout, generate the content if necessary. */
283int cache_process(int size, const char *path, const char *key, int ttl,
284 cache_fill_fn fn, void *cbdata)
116{ 285{
117 if (item->ttl < 0) 286 unsigned long hash;
287 int len, i;
288 char filename[1024];
289 char lockname[1024 + 5]; /* 5 = ".lock" */
290 struct cache_slot slot;
291
292 /* If the cache is disabled, just generate the content */
293 if (size <= 0) {
294 fn(cbdata);
118 return 0; 295 return 0;
119 return item->st.st_mtime + item->ttl * 60 < time(NULL); 296 }
297
298 /* Verify input, calculate filenames */
299 if (!path) {
300 cache_log("[cgit] Cache path not specified, caching is disabled\n");
301 fn(cbdata);
302 return 0;
303 }
304 len = strlen(path);
305 if (len > sizeof(filename) - 10) { /* 10 = "/01234567\0" */
306 cache_log("[cgit] Cache path too long, caching is disabled: %s\n",
307 path);
308 fn(cbdata);
309 return 0;
310 }
311 if (!key)
312 key = "";
313 hash = hash_str(key) % size;
314 strcpy(filename, path);
315 if (filename[len - 1] != '/')
316 filename[len++] = '/';
317 for(i = 0; i < 8; i++) {
318 sprintf(filename + len++, "%x",
319 (unsigned char)(hash & 0xf));
320 hash >>= 4;
321 }
322 filename[len] = '\0';
323 strcpy(lockname, filename);
324 strcpy(lockname + len, ".lock");
325 slot.fn = fn;
326 slot.cbdata = cbdata;
327 slot.ttl = ttl;
328 slot.cache_name = filename;
329 slot.lock_name = lockname;
330 slot.key = key;
331 slot.keylen = strlen(key);
332 return process_slot(&slot);
120} 333}
334
335/* Print a message to stdout */
336void cache_log(const char *format, ...)
337{
338 va_list args;
339 va_start(args, format);
340 vfprintf(stderr, format, args);
341 va_end(args);
342}
343
diff --git a/cache.h b/cache.h
index 4dcbea3..5f2178d 100644
--- a/cache.h
+++ b/cache.h
@@ -6,18 +6,27 @@
6#ifndef CGIT_CACHE_H 6#ifndef CGIT_CACHE_H
7#define CGIT_CACHE_H 7#define CGIT_CACHE_H
8 8
9struct cacheitem { 9typedef void (*cache_fill_fn)(void *cbdata);
10 char *name;
11 struct stat st;
12 int ttl;
13 int fd;
14};
15 10
16extern char *cache_safe_filename(const char *unsafe); 11
17extern int cache_lock(struct cacheitem *item); 12/* Print cached content to stdout, generate the content if necessary.
18extern int cache_unlock(struct cacheitem *item); 13 *
19extern int cache_cancel_lock(struct cacheitem *item); 14 * Parameters
20extern int cache_exist(struct cacheitem *item); 15 * size max number of cache files
21extern int cache_expired(struct cacheitem *item); 16 * path directory used to store cache files
17 * key the key used to lookup cache files
18 * ttl max cache time in seconds for this key
19 * fn content generator function for this key
20 * cbdata user-supplied data to the content generator function
21 *
22 * Return value
23 * 0 indicates success, everyting else is an error
24 */
25extern int cache_process(int size, const char *path, const char *key, int ttl,
26 cache_fill_fn fn, void *cbdata);
27
28
29/* Print a message to stdout */
30extern void cache_log(const char *format, ...);
22 31
23#endif /* CGIT_CACHE_H */ 32#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
index 38b0ba5..4dc8eec 100644
--- a/cgit.c
+++ b/cgit.c
@@ -45,6 +45,8 @@ void config_cb(const char *name, const char *value)
45 ctx.cfg.enable_log_filecount = atoi(value); 45 ctx.cfg.enable_log_filecount = atoi(value);
46 else if (!strcmp(name, "enable-log-linecount")) 46 else if (!strcmp(name, "enable-log-linecount"))
47 ctx.cfg.enable_log_linecount = atoi(value); 47 ctx.cfg.enable_log_linecount = atoi(value);
48 else if (!strcmp(name, "cache-size"))
49 ctx.cfg.cache_size = atoi(value);
48 else if (!strcmp(name, "cache-root")) 50 else if (!strcmp(name, "cache-root"))
49 ctx.cfg.cache_root = xstrdup(value); 51 ctx.cfg.cache_root = xstrdup(value);
50 else if (!strcmp(name, "cache-root-ttl")) 52 else if (!strcmp(name, "cache-root-ttl"))
@@ -143,6 +145,8 @@ static void prepare_context(struct cgit_context *ctx)
143{ 145{
144 memset(ctx, 0, sizeof(ctx)); 146 memset(ctx, 0, sizeof(ctx));
145 ctx->cfg.agefile = "info/web/last-modified"; 147 ctx->cfg.agefile = "info/web/last-modified";
148 ctx->cfg.nocache = 0;
149 ctx->cfg.cache_size = 0;
146 ctx->cfg.cache_dynamic_ttl = 5; 150 ctx->cfg.cache_dynamic_ttl = 5;
147 ctx->cfg.cache_max_create_time = 5; 151 ctx->cfg.cache_max_create_time = 5;
148 ctx->cfg.cache_repo_ttl = 5; 152 ctx->cfg.cache_repo_ttl = 5;
@@ -163,47 +167,8 @@ static void prepare_context(struct cgit_context *ctx)
163 ctx->page.mimetype = "text/html"; 167 ctx->page.mimetype = "text/html";
164 ctx->page.charset = PAGE_ENCODING; 168 ctx->page.charset = PAGE_ENCODING;
165 ctx->page.filename = NULL; 169 ctx->page.filename = NULL;
166} 170 ctx->page.modified = time(NULL);
167 171 ctx->page.expires = ctx->page.modified;
168static int cgit_prepare_cache(struct cacheitem *item)
169{
170 if (!ctx.repo && ctx.qry.repo) {
171 ctx.page.title = fmt("%s - %s", ctx.cfg.root_title,
172 "Bad request");
173 cgit_print_http_headers(&ctx);
174 cgit_print_docstart(&ctx);
175 cgit_print_pageheader(&ctx);
176 cgit_print_error(fmt("Unknown repo: %s", ctx.qry.repo));
177 cgit_print_docend();
178 return 0;
179 }
180
181 if (!ctx.repo) {
182 item->name = xstrdup(fmt("%s/index.%s.html",
183 ctx.cfg.cache_root,
184 cache_safe_filename(ctx.qry.raw)));
185 item->ttl = ctx.cfg.cache_root_ttl;
186 return 1;
187 }
188
189 if (!ctx.qry.page) {
190 item->name = xstrdup(fmt("%s/%s/index.%s.html", ctx.cfg.cache_root,
191 cache_safe_filename(ctx.repo->url),
192 cache_safe_filename(ctx.qry.raw)));
193 item->ttl = ctx.cfg.cache_repo_ttl;
194 } else {
195 item->name = xstrdup(fmt("%s/%s/%s/%s.html", ctx.cfg.cache_root,
196 cache_safe_filename(ctx.repo->url),
197 ctx.qry.page,
198 cache_safe_filename(ctx.qry.raw)));
199 if (ctx.qry.has_symref)
200 item->ttl = ctx.cfg.cache_dynamic_ttl;
201 else if (ctx.qry.has_sha1)
202 item->ttl = ctx.cfg.cache_static_ttl;
203 else
204 item->ttl = ctx.cfg.cache_repo_ttl;
205 }
206 return 1;
207} 172}
208 173
209struct refmatch { 174struct refmatch {
@@ -288,8 +253,9 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
288 return 0; 253 return 0;
289} 254}
290 255
291static void process_request(struct cgit_context *ctx) 256static void process_request(void *cbdata)
292{ 257{
258 struct cgit_context *ctx = cbdata;
293 struct cgit_cmd *cmd; 259 struct cgit_cmd *cmd;
294 260
295 cmd = cgit_get_cmd(ctx); 261 cmd = cgit_get_cmd(ctx);
@@ -319,82 +285,6 @@ static void process_request(struct cgit_context *ctx)
319 cgit_print_docend(); 285 cgit_print_docend();
320} 286}
321 287
322static long ttl_seconds(long ttl)
323{
324 if (ttl<0)
325 return 60 * 60 * 24 * 365;
326 else
327 return ttl * 60;
328}
329
330static void cgit_fill_cache(struct cacheitem *item, int use_cache)
331{
332 int stdout2;
333
334 if (use_cache) {
335 stdout2 = chk_positive(dup(STDOUT_FILENO),
336 "Preserving STDOUT");
337 chk_zero(close(STDOUT_FILENO), "Closing STDOUT");
338 chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)");
339 }
340
341 ctx.page.modified = time(NULL);
342 ctx.page.expires = ctx.page.modified + ttl_seconds(item->ttl);
343 process_request(&ctx);
344
345 if (use_cache) {
346 chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT");
347 chk_positive(dup2(stdout2, STDOUT_FILENO),
348 "Restoring original STDOUT");
349 chk_zero(close(stdout2), "Closing temporary STDOUT");
350 }
351}
352
353static void cgit_check_cache(struct cacheitem *item)
354{
355 int i = 0;
356
357 top:
358 if (++i > ctx.cfg.max_lock_attempts) {
359 die("cgit_refresh_cache: unable to lock %s: %s",
360 item->name, strerror(errno));
361 }
362 if (!cache_exist(item)) {
363 if (!cache_lock(item)) {
364 sleep(1);
365 goto top;
366 }
367 if (!cache_exist(item)) {
368 cgit_fill_cache(item, 1);
369 cache_unlock(item);
370 } else {
371 cache_cancel_lock(item);
372 }
373 } else if (cache_expired(item) && cache_lock(item)) {
374 if (cache_expired(item)) {
375 cgit_fill_cache(item, 1);
376 cache_unlock(item);
377 } else {
378 cache_cancel_lock(item);
379 }
380 }
381}
382
383static void cgit_print_cache(struct cacheitem *item)
384{
385 static char buf[4096];
386 ssize_t i;
387
388 int fd = open(item->name, O_RDONLY);
389 if (fd<0)
390 die("Unable to open cached file %s", item->name);
391
392 while((i=read(fd, buf, sizeof(buf))) > 0)
393 write(STDOUT_FILENO, buf, i);
394
395 close(fd);
396}
397
398static void cgit_parse_args(int argc, const char **argv) 288static void cgit_parse_args(int argc, const char **argv)
399{ 289{
400 int i; 290 int i;
@@ -429,13 +319,29 @@ static void cgit_parse_args(int argc, const char **argv)
429 } 319 }
430} 320}
431 321
322static int calc_ttl()
323{
324 if (!ctx.repo)
325 return ctx.cfg.cache_root_ttl;
326
327 if (!ctx.qry.page)
328 return ctx.cfg.cache_repo_ttl;
329
330 if (ctx.qry.has_symref)
331 return ctx.cfg.cache_dynamic_ttl;
332
333 if (ctx.qry.has_sha1)
334 return ctx.cfg.cache_static_ttl;
335
336 return ctx.cfg.cache_repo_ttl;
337}
338
432int main(int argc, const char **argv) 339int main(int argc, const char **argv)
433{ 340{
434 struct cacheitem item;
435 const char *cgit_config_env = getenv("CGIT_CONFIG"); 341 const char *cgit_config_env = getenv("CGIT_CONFIG");
342 int err, ttl;
436 343
437 prepare_context(&ctx); 344 prepare_context(&ctx);
438 item.st.st_mtime = time(NULL);
439 cgit_repolist.length = 0; 345 cgit_repolist.length = 0;
440 cgit_repolist.count = 0; 346 cgit_repolist.count = 0;
441 cgit_repolist.repos = NULL; 347 cgit_repolist.repos = NULL;
@@ -449,13 +355,15 @@ int main(int argc, const char **argv)
449 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 355 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
450 cgit_parse_args(argc, argv); 356 cgit_parse_args(argc, argv);
451 http_parse_querystring(ctx.qry.raw, querystring_cb); 357 http_parse_querystring(ctx.qry.raw, querystring_cb);
452 if (!cgit_prepare_cache(&item)) 358
453 return 0; 359 ttl = calc_ttl();
454 if (ctx.cfg.nocache) { 360 ctx.page.expires += ttl*60;
455 cgit_fill_cache(&item, 0); 361 if (ctx.cfg.nocache)
456 } else { 362 ctx.cfg.cache_size = 0;
457 cgit_check_cache(&item); 363 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
458 cgit_print_cache(&item); 364 ctx.qry.raw, ttl, process_request, &ctx);
459 } 365 if (err)
460 return 0; 366 cache_log("[cgit] error %d - %s\n",
367 err, strerror(err));
368 return err;
461} 369}
diff --git a/cgit.h b/cgit.h
index a3b6535..f04d227 100644
--- a/cgit.h
+++ b/cgit.h
@@ -134,6 +134,7 @@ struct cgit_config {
134 char *root_title; 134 char *root_title;
135 char *script_name; 135 char *script_name;
136 char *virtual_root; 136 char *virtual_root;
137 int cache_size;
137 int cache_dynamic_ttl; 138 int cache_dynamic_ttl;
138 int cache_max_create_time; 139 int cache_max_create_time;
139 int cache_repo_ttl; 140 int cache_repo_ttl;
diff --git a/tests/setup.sh b/tests/setup.sh
index 66bf406..e37306e 100755
--- a/tests/setup.sh
+++ b/tests/setup.sh
@@ -44,7 +44,7 @@ setup_repos()
44virtual-root=/ 44virtual-root=/
45cache-root=$PWD/trash/cache 45cache-root=$PWD/trash/cache
46 46
47nocache=0 47cache-size=1021
48snapshots=tar.gz tar.bz zip 48snapshots=tar.gz tar.bz zip
49enable-log-filecount=1 49enable-log-filecount=1
50enable-log-linecount=1 50enable-log-linecount=1
diff --git a/tests/t0020-validate-cache.sh b/tests/t0020-validate-cache.sh
new file mode 100755
index 0000000..53ec2eb
--- /dev/null
+++ b/tests/t0020-validate-cache.sh
@@ -0,0 +1,67 @@
1#!/bin/sh
2
3. ./setup.sh
4
5prepare_tests 'Validate cache'
6
7run_test 'verify cache-size=0' '
8
9 rm -f trash/cache/* &&
10 sed -i -e "s/cache-size=1021$/cache-size=0/" trash/cgitrc &&
11 cgit_url "" &&
12 cgit_url "foo" &&
13 cgit_url "foo/refs" &&
14 cgit_url "foo/tree" &&
15 cgit_url "foo/log" &&
16 cgit_url "foo/diff" &&
17 cgit_url "foo/patch" &&
18 cgit_url "bar" &&
19 cgit_url "bar/refs" &&
20 cgit_url "bar/tree" &&
21 cgit_url "bar/log" &&
22 cgit_url "bar/diff" &&
23 cgit_url "bar/patch" &&
24 test 0 -eq $(ls trash/cache | wc -l)
25'
26
27run_test 'verify cache-size=1' '
28
29 rm -f trash/cache/* &&
30 sed -i -e "s/cache-size=0$/cache-size=1/" trash/cgitrc &&
31 cgit_url "" &&
32 cgit_url "foo" &&
33 cgit_url "foo/refs" &&
34 cgit_url "foo/tree" &&
35 cgit_url "foo/log" &&
36 cgit_url "foo/diff" &&
37 cgit_url "foo/patch" &&
38 cgit_url "bar" &&
39 cgit_url "bar/refs" &&
40 cgit_url "bar/tree" &&
41 cgit_url "bar/log" &&
42 cgit_url "bar/diff" &&
43 cgit_url "bar/patch" &&
44 test 1 -eq $(ls trash/cache | wc -l)
45'
46
47run_test 'verify cache-size=1021' '
48
49 rm -f trash/cache/* &&
50 sed -i -e "s/cache-size=1$/cache-size=1021/" trash/cgitrc &&
51 cgit_url "" &&
52 cgit_url "foo" &&
53 cgit_url "foo/refs" &&
54 cgit_url "foo/tree" &&
55 cgit_url "foo/log" &&
56 cgit_url "foo/diff" &&
57 cgit_url "foo/patch" &&
58 cgit_url "bar" &&
59 cgit_url "bar/refs" &&
60 cgit_url "bar/tree" &&
61 cgit_url "bar/log" &&
62 cgit_url "bar/diff" &&
63 cgit_url "bar/patch" &&
64 test 13 -eq $(ls trash/cache | wc -l)
65'
66
67tests_done