Merge branch 'lh/cache'
* lh/cache: Add page 'ls_cache' Redesign the caching layer
Tento commit je obsažen v:
		
							
								
								
									
										437
									
								
								cache.c
									
									
									
									
									
								
							
							
						
						
									
										437
									
								
								cache.c
									
									
									
									
									
								
							| @@ -4,117 +4,408 @@ | ||||
|  * | ||||
|  * Licensed under GNU General Public License v2 | ||||
|  *   (see COPYING for full license text) | ||||
|  * | ||||
|  * | ||||
|  * The cache is just a directory structure where each file is a cache slot, | ||||
|  * and each filename is based on the hash of some key (e.g. the cgit url). | ||||
|  * Each file contains the full key followed by the cached content for that | ||||
|  * key. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "cgit.h" | ||||
| #include "cache.h" | ||||
|  | ||||
| const int NOLOCK = -1; | ||||
| #define CACHE_BUFSIZE (1024 * 4) | ||||
|  | ||||
| char *cache_safe_filename(const char *unsafe) | ||||
| struct cache_slot { | ||||
| 	const char *key; | ||||
| 	int keylen; | ||||
| 	int ttl; | ||||
| 	cache_fill_fn fn; | ||||
| 	void *cbdata; | ||||
| 	int cache_fd; | ||||
| 	int lock_fd; | ||||
| 	const char *cache_name; | ||||
| 	const char *lock_name; | ||||
| 	int match; | ||||
| 	struct stat cache_st; | ||||
| 	struct stat lock_st; | ||||
| 	int bufsize; | ||||
| 	char buf[CACHE_BUFSIZE]; | ||||
| }; | ||||
|  | ||||
| /* Open an existing cache slot and fill the cache buffer with | ||||
|  * (part of) the content of the cache file. Return 0 on success | ||||
|  * and errno otherwise. | ||||
|  */ | ||||
| static int open_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	static char buf[4][PATH_MAX]; | ||||
| 	static int bufidx; | ||||
| 	char *s; | ||||
| 	char c; | ||||
| 	char *bufz; | ||||
| 	int bufkeylen = -1; | ||||
|  | ||||
| 	bufidx++; | ||||
| 	bufidx &= 3; | ||||
| 	s = buf[bufidx]; | ||||
| 	slot->cache_fd = open(slot->cache_name, O_RDONLY); | ||||
| 	if (slot->cache_fd == -1) | ||||
| 		return errno; | ||||
|  | ||||
| 	while(unsafe && (c = *unsafe++) != 0) { | ||||
| 		if (c == '/' || c == ' ' || c == '&' || c == '|' || | ||||
| 		    c == '>' || c == '<' || c == '.') | ||||
| 			c = '_'; | ||||
| 		*s++ = c; | ||||
| 	} | ||||
| 	*s = '\0'; | ||||
| 	return buf[bufidx]; | ||||
| 	if (fstat(slot->cache_fd, &slot->cache_st)) | ||||
| 		return errno; | ||||
|  | ||||
| 	slot->bufsize = read(slot->cache_fd, slot->buf, sizeof(slot->buf)); | ||||
| 	if (slot->bufsize == 0) | ||||
| 		return errno; | ||||
|  | ||||
| 	bufz = memchr(slot->buf, 0, slot->bufsize); | ||||
| 	if (bufz) | ||||
| 		bufkeylen = bufz - slot->buf; | ||||
|  | ||||
| 	slot->match = bufkeylen == slot->keylen && | ||||
| 	    !memcmp(slot->key, slot->buf, bufkeylen + 1); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int cache_exist(struct cacheitem *item) | ||||
| /* Close the active cache slot */ | ||||
| static void close_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	if (stat(item->name, &item->st)) { | ||||
| 		item->st.st_mtime = 0; | ||||
| 		return 0; | ||||
| 	if (slot->cache_fd > 0) { | ||||
| 		close(slot->cache_fd); | ||||
| 		slot->cache_fd = -1; | ||||
| 	} | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| int cache_create_dirs() | ||||
| /* Print the content of the active cache slot (but skip the key). */ | ||||
| static int print_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	char *path; | ||||
| 	ssize_t i, j = 0; | ||||
|  | ||||
| 	path = fmt("%s", ctx.cfg.cache_root); | ||||
| 	if (mkdir(path, S_IRWXU) && errno!=EEXIST) | ||||
| 	i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET); | ||||
| 	if (i != slot->keylen + 1) | ||||
| 		return errno; | ||||
|  | ||||
| 	while((i=read(slot->cache_fd, slot->buf, sizeof(slot->buf))) > 0) | ||||
| 		j = write(STDOUT_FILENO, slot->buf, i); | ||||
|  | ||||
| 	if (j < 0) | ||||
| 		return errno; | ||||
| 	else | ||||
| 		return 0; | ||||
|  | ||||
| 	if (!ctx.repo) | ||||
| 		return 0; | ||||
|  | ||||
| 	path = fmt("%s/%s", ctx.cfg.cache_root, | ||||
| 		   cache_safe_filename(ctx.repo->url)); | ||||
|  | ||||
| 	if (mkdir(path, S_IRWXU) && errno!=EEXIST) | ||||
| 		return 0; | ||||
|  | ||||
| 	if (ctx.qry.page) { | ||||
| 		path = fmt("%s/%s/%s", ctx.cfg.cache_root, | ||||
| 			   cache_safe_filename(ctx.repo->url), | ||||
| 			   ctx.qry.page); | ||||
| 		if (mkdir(path, S_IRWXU) && errno!=EEXIST) | ||||
| 			return 0; | ||||
| 	} | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| int cache_refill_overdue(const char *lockfile) | ||||
| /* Check if the slot has expired */ | ||||
| static int is_expired(struct cache_slot *slot) | ||||
| { | ||||
| 	if (slot->ttl < 0) | ||||
| 		return 0; | ||||
| 	else | ||||
| 		return slot->cache_st.st_mtime + slot->ttl*60 < time(NULL); | ||||
| } | ||||
|  | ||||
| /* Check if the slot has been modified since we opened it. | ||||
|  * NB: If stat() fails, we pretend the file is modified. | ||||
|  */ | ||||
| static int is_modified(struct cache_slot *slot) | ||||
| { | ||||
| 	struct stat st; | ||||
|  | ||||
| 	if (stat(lockfile, &st)) | ||||
| 		return 0; | ||||
| 	if (stat(slot->cache_name, &st)) | ||||
| 		return 1; | ||||
| 	return (st.st_ino != slot->cache_st.st_ino || | ||||
| 		st.st_mtime != slot->cache_st.st_mtime || | ||||
| 		st.st_size != slot->cache_st.st_size); | ||||
| } | ||||
|  | ||||
| /* Close an open lockfile */ | ||||
| static void close_lock(struct cache_slot *slot) | ||||
| { | ||||
| 	if (slot->lock_fd > 0) { | ||||
| 		close(slot->lock_fd); | ||||
| 		slot->lock_fd = -1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* Create a lockfile used to store the generated content for a cache | ||||
|  * slot, and write the slot key + \0 into it. | ||||
|  * Returns 0 on success and errno otherwise. | ||||
|  */ | ||||
| static int lock_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	slot->lock_fd = open(slot->lock_name, O_RDWR|O_CREAT|O_EXCL, | ||||
| 			     S_IRUSR|S_IWUSR); | ||||
| 	if (slot->lock_fd == -1) | ||||
| 		return errno; | ||||
| 	write(slot->lock_fd, slot->key, slot->keylen + 1); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Release the current lockfile. If `replace_old_slot` is set the | ||||
|  * lockfile replaces the old cache slot, otherwise the lockfile is | ||||
|  * just deleted. | ||||
|  */ | ||||
| static int unlock_slot(struct cache_slot *slot, int replace_old_slot) | ||||
| { | ||||
| 	int err; | ||||
|  | ||||
| 	if (replace_old_slot) | ||||
| 		err = rename(slot->lock_name, slot->cache_name); | ||||
| 	else | ||||
| 		return (time(NULL) - st.st_mtime > ctx.cfg.cache_max_create_time); | ||||
| 		err = unlink(slot->lock_name); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int cache_lock(struct cacheitem *item) | ||||
| /* Generate the content for the current cache slot by redirecting | ||||
|  * stdout to the lock-fd and invoking the callback function | ||||
|  */ | ||||
| static int fill_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	char *lockfile = xstrdup(fmt("%s.lock", item->name)); | ||||
| 	int tmp; | ||||
|  | ||||
|  top: | ||||
| 	if (++i > ctx.cfg.max_lock_attempts) | ||||
| 		die("cache_lock: unable to lock %s: %s", | ||||
| 		    item->name, strerror(errno)); | ||||
| 	/* Preserve stdout */ | ||||
| 	tmp = dup(STDOUT_FILENO); | ||||
| 	if (tmp == -1) | ||||
| 		return errno; | ||||
|  | ||||
|        	item->fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); | ||||
| 	/* Redirect stdout to lockfile */ | ||||
| 	if (dup2(slot->lock_fd, STDOUT_FILENO) == -1) | ||||
| 		return errno; | ||||
|  | ||||
| 	if (item->fd == NOLOCK && errno == ENOENT && cache_create_dirs()) | ||||
| 		goto top; | ||||
| 	/* Generate cache content */ | ||||
| 	slot->fn(slot->cbdata); | ||||
|  | ||||
| 	if (item->fd == NOLOCK && errno == EEXIST && | ||||
| 	    cache_refill_overdue(lockfile) && !unlink(lockfile)) | ||||
| 			goto top; | ||||
| 	/* Restore stdout */ | ||||
| 	if (dup2(tmp, STDOUT_FILENO) == -1) | ||||
| 		return errno; | ||||
|  | ||||
| 	free(lockfile); | ||||
| 	return (item->fd > 0); | ||||
| 	/* Close the temporary filedescriptor */ | ||||
| 	close(tmp); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int cache_unlock(struct cacheitem *item) | ||||
| /* Crude implementation of 32-bit FNV-1 hash algorithm, | ||||
|  * see http://www.isthe.com/chongo/tech/comp/fnv/ for details | ||||
|  * about the magic numbers. | ||||
|  */ | ||||
| #define FNV_OFFSET 0x811c9dc5 | ||||
| #define FNV_PRIME  0x01000193 | ||||
|  | ||||
| unsigned long hash_str(const char *str) | ||||
| { | ||||
| 	close(item->fd); | ||||
| 	return (rename(fmt("%s.lock", item->name), item->name) == 0); | ||||
| 	unsigned long h = FNV_OFFSET; | ||||
| 	unsigned char *s = (unsigned char *)str; | ||||
|  | ||||
| 	if (!s) | ||||
| 		return h; | ||||
|  | ||||
| 	while(*s) { | ||||
| 		h *= FNV_PRIME; | ||||
| 		h ^= *s++; | ||||
| 	} | ||||
| 	return h; | ||||
| } | ||||
|  | ||||
| int cache_cancel_lock(struct cacheitem *item) | ||||
| static int process_slot(struct cache_slot *slot) | ||||
| { | ||||
| 	return (unlink(fmt("%s.lock", item->name)) == 0); | ||||
| } | ||||
| 	int err; | ||||
|  | ||||
| int cache_expired(struct cacheitem *item) | ||||
| { | ||||
| 	if (item->ttl < 0) | ||||
| 	err = open_slot(slot); | ||||
| 	if (!err && slot->match) { | ||||
| 		if (is_expired(slot)) { | ||||
| 			if (!lock_slot(slot)) { | ||||
| 				/* If the cachefile has been replaced between | ||||
| 				 * `open_slot` and `lock_slot`, we'll just | ||||
| 				 * serve the stale content from the original | ||||
| 				 * cachefile. This way we avoid pruning the | ||||
| 				 * newly generated slot. The same code-path | ||||
| 				 * is chosen if fill_slot() fails for some | ||||
| 				 * reason. | ||||
| 				 * | ||||
| 				 * TODO? check if the new slot contains the | ||||
| 				 * same key as the old one, since we would | ||||
| 				 * prefer to serve the newest content. | ||||
| 				 * This will require us to open yet another | ||||
| 				 * file-descriptor and read and compare the | ||||
| 				 * key from the new file, so for now we're | ||||
| 				 * lazy and just ignore the new file. | ||||
| 				 */ | ||||
| 				if (is_modified(slot) || fill_slot(slot)) { | ||||
| 					unlock_slot(slot, 0); | ||||
| 					close_lock(slot); | ||||
| 				} else { | ||||
| 					close_slot(slot); | ||||
| 					unlock_slot(slot, 1); | ||||
| 					slot->cache_fd = slot->lock_fd; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		print_slot(slot); | ||||
| 		close_slot(slot); | ||||
| 		return 0; | ||||
| 	return item->st.st_mtime + item->ttl * 60 < time(NULL); | ||||
| 	} | ||||
|  | ||||
| 	/* If the cache slot does not exist (or its key doesn't match the | ||||
| 	 * current key), lets try to create a new cache slot for this | ||||
| 	 * request. If this fails (for whatever reason), lets just generate | ||||
| 	 * the content without caching it and fool the caller to belive | ||||
| 	 * everything worked out (but print a warning on stdout). | ||||
| 	 */ | ||||
|  | ||||
| 	close_slot(slot); | ||||
| 	if ((err = lock_slot(slot)) != 0) { | ||||
| 		cache_log("[cgit] Unable to lock slot %s: %s (%d)\n", | ||||
| 			  slot->lock_name, strerror(err), err); | ||||
| 		slot->fn(slot->cbdata); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if ((err = fill_slot(slot)) != 0) { | ||||
| 		cache_log("[cgit] Unable to fill slot %s: %s (%d)\n", | ||||
| 			  slot->lock_name, strerror(err), err); | ||||
| 		unlock_slot(slot, 0); | ||||
| 		close_lock(slot); | ||||
| 		slot->fn(slot->cbdata); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	// We've got a valid cache slot in the lock file, which | ||||
| 	// is about to replace the old cache slot. But if we | ||||
| 	// release the lockfile and then try to open the new cache | ||||
| 	// slot, we might get a race condition with a concurrent | ||||
| 	// writer for the same cache slot (with a different key). | ||||
| 	// Lets avoid such a race by just printing the content of | ||||
| 	// the lock file. | ||||
| 	slot->cache_fd = slot->lock_fd; | ||||
| 	unlock_slot(slot, 1); | ||||
| 	err = print_slot(slot); | ||||
| 	close_slot(slot); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| /* Print cached content to stdout, generate the content if necessary. */ | ||||
| int cache_process(int size, const char *path, const char *key, int ttl, | ||||
| 		  cache_fill_fn fn, void *cbdata) | ||||
| { | ||||
| 	unsigned long hash; | ||||
| 	int len, i; | ||||
| 	char filename[1024]; | ||||
| 	char lockname[1024 + 5];  /* 5 = ".lock" */ | ||||
| 	struct cache_slot slot; | ||||
|  | ||||
| 	/* If the cache is disabled, just generate the content */ | ||||
| 	if (size <= 0) { | ||||
| 		fn(cbdata); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	/* Verify input, calculate filenames */ | ||||
| 	if (!path) { | ||||
| 		cache_log("[cgit] Cache path not specified, caching is disabled\n"); | ||||
| 		fn(cbdata); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	len = strlen(path); | ||||
| 	if (len > sizeof(filename) - 10) { /* 10 = "/01234567\0" */ | ||||
| 		cache_log("[cgit] Cache path too long, caching is disabled: %s\n", | ||||
| 			  path); | ||||
| 		fn(cbdata); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	if (!key) | ||||
| 		key = ""; | ||||
| 	hash = hash_str(key) % size; | ||||
| 	strcpy(filename, path); | ||||
| 	if (filename[len - 1] != '/') | ||||
| 		filename[len++] = '/'; | ||||
| 	for(i = 0; i < 8; i++) { | ||||
| 		sprintf(filename + len++, "%x", | ||||
| 			(unsigned char)(hash & 0xf)); | ||||
| 		hash >>= 4; | ||||
| 	} | ||||
| 	filename[len] = '\0'; | ||||
| 	strcpy(lockname, filename); | ||||
| 	strcpy(lockname + len, ".lock"); | ||||
| 	slot.fn = fn; | ||||
| 	slot.cbdata = cbdata; | ||||
| 	slot.ttl = ttl; | ||||
| 	slot.cache_name = filename; | ||||
| 	slot.lock_name = lockname; | ||||
| 	slot.key = key; | ||||
| 	slot.keylen = strlen(key); | ||||
| 	return process_slot(&slot); | ||||
| } | ||||
|  | ||||
| /* Return a strftime formatted date/time | ||||
|  * NB: the result from this function is to shared memory | ||||
|  */ | ||||
| char *sprintftime(const char *format, time_t time) | ||||
| { | ||||
| 	static char buf[64]; | ||||
| 	struct tm *tm; | ||||
|  | ||||
| 	if (!time) | ||||
| 		return NULL; | ||||
| 	tm = gmtime(&time); | ||||
| 	strftime(buf, sizeof(buf)-1, format, tm); | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| int cache_ls(const char *path) | ||||
| { | ||||
| 	DIR *dir; | ||||
| 	struct dirent *ent; | ||||
| 	int err = 0; | ||||
| 	struct cache_slot slot; | ||||
| 	char fullname[1024]; | ||||
| 	char *name; | ||||
|  | ||||
| 	if (!path) { | ||||
| 		cache_log("[cgit] cache path not specified\n"); | ||||
| 		return -1; | ||||
| 	} | ||||
| 	if (strlen(path) > 1024 - 10) { | ||||
| 		cache_log("[cgit] cache path too long: %s\n", | ||||
| 			  path); | ||||
| 		return -1; | ||||
| 	} | ||||
| 	dir = opendir(path); | ||||
| 	if (!dir) { | ||||
| 		err = errno; | ||||
| 		cache_log("[cgit] unable to open path %s: %s (%d)\n", | ||||
| 			  path, strerror(err), err); | ||||
| 		return err; | ||||
| 	} | ||||
| 	strcpy(fullname, path); | ||||
| 	name = fullname + strlen(path); | ||||
| 	if (*(name - 1) != '/') { | ||||
| 		*name++ = '/'; | ||||
| 		*name = '\0'; | ||||
| 	} | ||||
| 	slot.cache_name = fullname; | ||||
| 	while((ent = readdir(dir)) != NULL) { | ||||
| 		if (strlen(ent->d_name) != 8) | ||||
| 			continue; | ||||
| 		strcpy(name, ent->d_name); | ||||
| 		if ((err = open_slot(&slot)) != 0) { | ||||
| 			cache_log("[cgit] unable to open path %s: %s (%d)\n", | ||||
| 				  fullname, strerror(err), err); | ||||
| 			continue; | ||||
| 		} | ||||
| 		printf("%s %s %10lld %s\n", | ||||
| 		       name, | ||||
| 		       sprintftime("%Y-%m-%d %H:%M:%S", | ||||
| 				   slot.cache_st.st_mtime), | ||||
| 		       slot.cache_st.st_size, | ||||
| 		       slot.buf); | ||||
| 		close_slot(&slot); | ||||
| 	} | ||||
| 	closedir(dir); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Print a message to stdout */ | ||||
| void cache_log(const char *format, ...) | ||||
| { | ||||
| 	va_list args; | ||||
| 	va_start(args, format); | ||||
| 	vfprintf(stderr, format, args); | ||||
| 	va_end(args); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										36
									
								
								cache.h
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								cache.h
									
									
									
									
									
								
							| @@ -6,18 +6,30 @@ | ||||
| #ifndef CGIT_CACHE_H | ||||
| #define CGIT_CACHE_H | ||||
|  | ||||
| struct cacheitem { | ||||
| 	char *name; | ||||
| 	struct stat st; | ||||
| 	int ttl; | ||||
| 	int fd; | ||||
| }; | ||||
| typedef void (*cache_fill_fn)(void *cbdata); | ||||
|  | ||||
| extern char *cache_safe_filename(const char *unsafe); | ||||
| extern int cache_lock(struct cacheitem *item); | ||||
| extern int cache_unlock(struct cacheitem *item); | ||||
| extern int cache_cancel_lock(struct cacheitem *item); | ||||
| extern int cache_exist(struct cacheitem *item); | ||||
| extern int cache_expired(struct cacheitem *item); | ||||
|  | ||||
| /* Print cached content to stdout, generate the content if necessary. | ||||
|  * | ||||
|  * Parameters | ||||
|  *   size    max number of cache files | ||||
|  *   path    directory used to store cache files | ||||
|  *   key     the key used to lookup cache files | ||||
|  *   ttl     max cache time in seconds for this key | ||||
|  *   fn      content generator function for this key | ||||
|  *   cbdata  user-supplied data to the content generator function | ||||
|  * | ||||
|  * Return value | ||||
|  *   0 indicates success, everyting else is an error | ||||
|  */ | ||||
| extern int cache_process(int size, const char *path, const char *key, int ttl, | ||||
| 			 cache_fill_fn fn, void *cbdata); | ||||
|  | ||||
|  | ||||
| /* List info about all cache entries on stdout */ | ||||
| extern int cache_ls(const char *path); | ||||
|  | ||||
| /* Print a message to stdout */ | ||||
| extern void cache_log(const char *format, ...); | ||||
|  | ||||
| #endif /* CGIT_CACHE_H */ | ||||
|   | ||||
							
								
								
									
										166
									
								
								cgit.c
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								cgit.c
									
									
									
									
									
								
							| @@ -49,6 +49,8 @@ void config_cb(const char *name, const char *value) | ||||
| 		ctx.cfg.enable_log_filecount = atoi(value); | ||||
| 	else if (!strcmp(name, "enable-log-linecount")) | ||||
| 		ctx.cfg.enable_log_linecount = atoi(value); | ||||
| 	else if (!strcmp(name, "cache-size")) | ||||
| 		ctx.cfg.cache_size = atoi(value); | ||||
| 	else if (!strcmp(name, "cache-root")) | ||||
| 		ctx.cfg.cache_root = xstrdup(value); | ||||
| 	else if (!strcmp(name, "cache-root-ttl")) | ||||
| @@ -147,6 +149,8 @@ static void prepare_context(struct cgit_context *ctx) | ||||
| { | ||||
| 	memset(ctx, 0, sizeof(ctx)); | ||||
| 	ctx->cfg.agefile = "info/web/last-modified"; | ||||
| 	ctx->cfg.nocache = 0; | ||||
| 	ctx->cfg.cache_size = 0; | ||||
| 	ctx->cfg.cache_dynamic_ttl = 5; | ||||
| 	ctx->cfg.cache_max_create_time = 5; | ||||
| 	ctx->cfg.cache_repo_ttl = 5; | ||||
| @@ -168,47 +172,8 @@ static void prepare_context(struct cgit_context *ctx) | ||||
| 	ctx->page.mimetype = "text/html"; | ||||
| 	ctx->page.charset = PAGE_ENCODING; | ||||
| 	ctx->page.filename = NULL; | ||||
| } | ||||
|  | ||||
| static int cgit_prepare_cache(struct cacheitem *item) | ||||
| { | ||||
| 	if (!ctx.repo && ctx.qry.repo) { | ||||
| 		ctx.page.title = fmt("%s - %s", ctx.cfg.root_title, | ||||
| 				      "Bad request"); | ||||
| 		cgit_print_http_headers(&ctx); | ||||
| 		cgit_print_docstart(&ctx); | ||||
| 		cgit_print_pageheader(&ctx); | ||||
| 		cgit_print_error(fmt("Unknown repo: %s", ctx.qry.repo)); | ||||
| 		cgit_print_docend(); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if (!ctx.repo) { | ||||
| 		item->name = xstrdup(fmt("%s/index.%s.html", | ||||
| 					 ctx.cfg.cache_root, | ||||
| 					 cache_safe_filename(ctx.qry.raw))); | ||||
| 		item->ttl = ctx.cfg.cache_root_ttl; | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	if (!ctx.qry.page) { | ||||
| 		item->name = xstrdup(fmt("%s/%s/index.%s.html", ctx.cfg.cache_root, | ||||
| 					 cache_safe_filename(ctx.repo->url), | ||||
| 					 cache_safe_filename(ctx.qry.raw))); | ||||
| 		item->ttl = ctx.cfg.cache_repo_ttl; | ||||
| 	} else { | ||||
| 		item->name = xstrdup(fmt("%s/%s/%s/%s.html", ctx.cfg.cache_root, | ||||
| 					 cache_safe_filename(ctx.repo->url), | ||||
| 					 ctx.qry.page, | ||||
| 					 cache_safe_filename(ctx.qry.raw))); | ||||
| 		if (ctx.qry.has_symref) | ||||
| 			item->ttl = ctx.cfg.cache_dynamic_ttl; | ||||
| 		else if (ctx.qry.has_sha1) | ||||
| 			item->ttl = ctx.cfg.cache_static_ttl; | ||||
| 		else | ||||
| 			item->ttl = ctx.cfg.cache_repo_ttl; | ||||
| 	} | ||||
| 	return 1; | ||||
| 	ctx->page.modified = time(NULL); | ||||
| 	ctx->page.expires = ctx->page.modified; | ||||
| } | ||||
|  | ||||
| struct refmatch { | ||||
| @@ -293,8 +258,9 @@ static int prepare_repo_cmd(struct cgit_context *ctx) | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void process_request(struct cgit_context *ctx) | ||||
| static void process_request(void *cbdata) | ||||
| { | ||||
| 	struct cgit_context *ctx = cbdata; | ||||
| 	struct cgit_cmd *cmd; | ||||
|  | ||||
| 	cmd = cgit_get_cmd(ctx); | ||||
| @@ -333,82 +299,6 @@ static void process_request(struct cgit_context *ctx) | ||||
| 		cgit_print_docend(); | ||||
| } | ||||
|  | ||||
| static long ttl_seconds(long ttl) | ||||
| { | ||||
| 	if (ttl<0) | ||||
| 		return 60 * 60 * 24 * 365; | ||||
| 	else | ||||
| 		return ttl * 60; | ||||
| } | ||||
|  | ||||
| static void cgit_fill_cache(struct cacheitem *item, int use_cache) | ||||
| { | ||||
| 	int stdout2; | ||||
|  | ||||
| 	if (use_cache) { | ||||
| 		stdout2 = chk_positive(dup(STDOUT_FILENO), | ||||
| 				       "Preserving STDOUT"); | ||||
| 		chk_zero(close(STDOUT_FILENO), "Closing STDOUT"); | ||||
| 		chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)"); | ||||
| 	} | ||||
|  | ||||
| 	ctx.page.modified = time(NULL); | ||||
| 	ctx.page.expires = ctx.page.modified + ttl_seconds(item->ttl); | ||||
| 	process_request(&ctx); | ||||
|  | ||||
| 	if (use_cache) { | ||||
| 		chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT"); | ||||
| 		chk_positive(dup2(stdout2, STDOUT_FILENO), | ||||
| 			     "Restoring original STDOUT"); | ||||
| 		chk_zero(close(stdout2), "Closing temporary STDOUT"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void cgit_check_cache(struct cacheitem *item) | ||||
| { | ||||
| 	int i = 0; | ||||
|  | ||||
|  top: | ||||
| 	if (++i > ctx.cfg.max_lock_attempts) { | ||||
| 		die("cgit_refresh_cache: unable to lock %s: %s", | ||||
| 		    item->name, strerror(errno)); | ||||
| 	} | ||||
| 	if (!cache_exist(item)) { | ||||
| 		if (!cache_lock(item)) { | ||||
| 			sleep(1); | ||||
| 			goto top; | ||||
| 		} | ||||
| 		if (!cache_exist(item)) { | ||||
| 			cgit_fill_cache(item, 1); | ||||
| 			cache_unlock(item); | ||||
| 		} else { | ||||
| 			cache_cancel_lock(item); | ||||
| 		} | ||||
| 	} else if (cache_expired(item) && cache_lock(item)) { | ||||
| 		if (cache_expired(item)) { | ||||
| 			cgit_fill_cache(item, 1); | ||||
| 			cache_unlock(item); | ||||
| 		} else { | ||||
| 			cache_cancel_lock(item); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void cgit_print_cache(struct cacheitem *item) | ||||
| { | ||||
| 	static char buf[4096]; | ||||
| 	ssize_t i; | ||||
|  | ||||
| 	int fd = open(item->name, O_RDONLY); | ||||
| 	if (fd<0) | ||||
| 		die("Unable to open cached file %s", item->name); | ||||
|  | ||||
| 	while((i=read(fd, buf, sizeof(buf))) > 0) | ||||
| 		write(STDOUT_FILENO, buf, i); | ||||
|  | ||||
| 	close(fd); | ||||
| } | ||||
|  | ||||
| static void cgit_parse_args(int argc, const char **argv) | ||||
| { | ||||
| 	int i; | ||||
| @@ -443,13 +333,29 @@ static void cgit_parse_args(int argc, const char **argv) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static int calc_ttl() | ||||
| { | ||||
| 	if (!ctx.repo) | ||||
| 		return ctx.cfg.cache_root_ttl; | ||||
|  | ||||
| 	if (!ctx.qry.page) | ||||
| 		return ctx.cfg.cache_repo_ttl; | ||||
|  | ||||
| 	if (ctx.qry.has_symref) | ||||
| 		return ctx.cfg.cache_dynamic_ttl; | ||||
|  | ||||
| 	if (ctx.qry.has_sha1) | ||||
| 		return ctx.cfg.cache_static_ttl; | ||||
|  | ||||
| 	return ctx.cfg.cache_repo_ttl; | ||||
| } | ||||
|  | ||||
| int main(int argc, const char **argv) | ||||
| { | ||||
| 	struct cacheitem item; | ||||
| 	const char *cgit_config_env = getenv("CGIT_CONFIG"); | ||||
| 	int err, ttl; | ||||
|  | ||||
| 	prepare_context(&ctx); | ||||
| 	item.st.st_mtime = time(NULL); | ||||
| 	cgit_repolist.length = 0; | ||||
| 	cgit_repolist.count = 0; | ||||
| 	cgit_repolist.repos = NULL; | ||||
| @@ -463,13 +369,15 @@ int main(int argc, const char **argv) | ||||
| 		ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); | ||||
| 	cgit_parse_args(argc, argv); | ||||
| 	http_parse_querystring(ctx.qry.raw, querystring_cb); | ||||
| 	if (!cgit_prepare_cache(&item)) | ||||
| 		return 0; | ||||
| 	if (ctx.cfg.nocache) { | ||||
| 		cgit_fill_cache(&item, 0); | ||||
| 	} else { | ||||
| 		cgit_check_cache(&item); | ||||
| 		cgit_print_cache(&item); | ||||
| 	} | ||||
| 	return 0; | ||||
|  | ||||
| 	ttl = calc_ttl(); | ||||
| 	ctx.page.expires += ttl*60; | ||||
| 	if (ctx.cfg.nocache) | ||||
| 		ctx.cfg.cache_size = 0; | ||||
| 	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, | ||||
| 			    ctx.qry.raw, ttl, process_request, &ctx); | ||||
| 	if (err) | ||||
| 		cache_log("[cgit] error %d - %s\n", | ||||
| 			  err, strerror(err)); | ||||
| 	return err; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								cgit.h
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								cgit.h
									
									
									
									
									
								
							| @@ -136,6 +136,7 @@ struct cgit_config { | ||||
| 	char *root_readme; | ||||
| 	char *script_name; | ||||
| 	char *virtual_root; | ||||
| 	int cache_size; | ||||
| 	int cache_dynamic_ttl; | ||||
| 	int cache_max_create_time; | ||||
| 	int cache_repo_ttl; | ||||
|   | ||||
							
								
								
									
										21
									
								
								cmd.c
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								cmd.c
									
									
									
									
									
								
							| @@ -8,6 +8,8 @@ | ||||
|  | ||||
| #include "cgit.h" | ||||
| #include "cmd.h" | ||||
| #include "cache.h" | ||||
| #include "ui-shared.h" | ||||
| #include "ui-blob.h" | ||||
| #include "ui-commit.h" | ||||
| #include "ui-diff.h" | ||||
| @@ -43,17 +45,25 @@ static void diff_fn(struct cgit_context *ctx) | ||||
| 	cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); | ||||
| } | ||||
|  | ||||
| static void repolist_fn(struct cgit_context *ctx) | ||||
| { | ||||
| 	cgit_print_repolist(); | ||||
| } | ||||
|  | ||||
| static void log_fn(struct cgit_context *ctx) | ||||
| { | ||||
| 	cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, | ||||
| 		       ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); | ||||
| } | ||||
|  | ||||
| static void ls_cache_fn(struct cgit_context *ctx) | ||||
| { | ||||
| 	ctx->page.mimetype = "text/plain"; | ||||
| 	ctx->page.filename = "ls-cache.txt"; | ||||
| 	cgit_print_http_headers(ctx); | ||||
| 	cache_ls(ctx->cfg.cache_root); | ||||
| } | ||||
|  | ||||
| static void repolist_fn(struct cgit_context *ctx) | ||||
| { | ||||
| 	cgit_print_repolist(); | ||||
| } | ||||
|  | ||||
| static void patch_fn(struct cgit_context *ctx) | ||||
| { | ||||
| 	cgit_print_patch(ctx->qry.sha1); | ||||
| @@ -97,6 +107,7 @@ struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) | ||||
| 		def_cmd(commit, 1, 1), | ||||
| 		def_cmd(diff, 1, 1), | ||||
| 		def_cmd(log, 1, 1), | ||||
| 		def_cmd(ls_cache, 0, 0), | ||||
| 		def_cmd(patch, 1, 0), | ||||
| 		def_cmd(refs, 1, 1), | ||||
| 		def_cmd(repolist, 0, 0), | ||||
|   | ||||
| @@ -44,7 +44,7 @@ setup_repos() | ||||
| virtual-root=/ | ||||
| cache-root=$PWD/trash/cache | ||||
|  | ||||
| nocache=0 | ||||
| cache-size=1021 | ||||
| snapshots=tar.gz tar.bz zip | ||||
| enable-log-filecount=1 | ||||
| enable-log-linecount=1 | ||||
|   | ||||
							
								
								
									
										67
									
								
								tests/t0020-validate-cache.sh
									
									
									
									
									
										Spustitelný soubor
									
								
							
							
						
						
									
										67
									
								
								tests/t0020-validate-cache.sh
									
									
									
									
									
										Spustitelný soubor
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| . ./setup.sh | ||||
|  | ||||
| prepare_tests 'Validate cache' | ||||
|  | ||||
| run_test 'verify cache-size=0' ' | ||||
|  | ||||
| 	rm -f trash/cache/* && | ||||
| 	sed -i -e "s/cache-size=1021$/cache-size=0/" trash/cgitrc && | ||||
| 	cgit_url "" && | ||||
| 	cgit_url "foo" && | ||||
| 	cgit_url "foo/refs" && | ||||
| 	cgit_url "foo/tree" && | ||||
| 	cgit_url "foo/log" && | ||||
| 	cgit_url "foo/diff" && | ||||
| 	cgit_url "foo/patch" && | ||||
| 	cgit_url "bar" && | ||||
| 	cgit_url "bar/refs" && | ||||
| 	cgit_url "bar/tree" && | ||||
| 	cgit_url "bar/log" && | ||||
| 	cgit_url "bar/diff" && | ||||
| 	cgit_url "bar/patch" && | ||||
| 	test 0 -eq $(ls trash/cache | wc -l) | ||||
| ' | ||||
|  | ||||
| run_test 'verify cache-size=1' ' | ||||
|  | ||||
| 	rm -f trash/cache/* && | ||||
| 	sed -i -e "s/cache-size=0$/cache-size=1/" trash/cgitrc && | ||||
| 	cgit_url "" && | ||||
| 	cgit_url "foo" && | ||||
| 	cgit_url "foo/refs" && | ||||
| 	cgit_url "foo/tree" && | ||||
| 	cgit_url "foo/log" && | ||||
| 	cgit_url "foo/diff" && | ||||
| 	cgit_url "foo/patch" && | ||||
| 	cgit_url "bar" && | ||||
| 	cgit_url "bar/refs" && | ||||
| 	cgit_url "bar/tree" && | ||||
| 	cgit_url "bar/log" && | ||||
| 	cgit_url "bar/diff" && | ||||
| 	cgit_url "bar/patch" && | ||||
| 	test 1 -eq $(ls trash/cache | wc -l) | ||||
| ' | ||||
|  | ||||
| run_test 'verify cache-size=1021' ' | ||||
|  | ||||
| 	rm -f trash/cache/* && | ||||
| 	sed -i -e "s/cache-size=1$/cache-size=1021/" trash/cgitrc && | ||||
| 	cgit_url "" && | ||||
| 	cgit_url "foo" && | ||||
| 	cgit_url "foo/refs" && | ||||
| 	cgit_url "foo/tree" && | ||||
| 	cgit_url "foo/log" && | ||||
| 	cgit_url "foo/diff" && | ||||
| 	cgit_url "foo/patch" && | ||||
| 	cgit_url "bar" && | ||||
| 	cgit_url "bar/refs" && | ||||
| 	cgit_url "bar/tree" && | ||||
| 	cgit_url "bar/log" && | ||||
| 	cgit_url "bar/diff" && | ||||
| 	cgit_url "bar/patch" && | ||||
| 	test 13 -eq $(ls trash/cache | wc -l) | ||||
| ' | ||||
|  | ||||
| tests_done | ||||
		Odkázat v novém úkolu
	
	Zablokovat Uživatele
	 Lars Hjemli
					Lars Hjemli