/* cache.c: cache management
*
- * Copyright (C) 2006 Lars Hjemli
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
#include "cgit.h"
#include "cache.h"
+#include "html.h"
+#ifdef HAVE_LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
#define CACHE_BUFSIZE (1024 * 4)
struct cache_slot {
const char *key;
- int keylen;
+ size_t 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];
};
static int open_slot(struct cache_slot *slot)
{
char *bufz;
- int bufkeylen = -1;
+ ssize_t bufkeylen = -1;
slot->cache_fd = open(slot->cache_name, O_RDONLY);
if (slot->cache_fd == -1)
if (fstat(slot->cache_fd, &slot->cache_st))
return errno;
- slot->bufsize = read(slot->cache_fd, slot->buf, sizeof(slot->buf));
+ slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
if (slot->bufsize < 0)
return errno;
if (bufz)
bufkeylen = bufz - slot->buf;
- slot->match = bufkeylen == slot->keylen &&
- !memcmp(slot->key, slot->buf, bufkeylen + 1);
+ if (slot->key)
+ slot->match = bufkeylen == slot->keylen &&
+ !memcmp(slot->key, slot->buf, bufkeylen + 1);
return 0;
}
/* Print the content of the active cache slot (but skip the key). */
static int print_slot(struct cache_slot *slot)
{
- ssize_t i, j = 0;
+#ifdef HAVE_LINUX_SENDFILE
+ off_t start_off;
+ int ret;
+
+ start_off = slot->keylen + 1;
+
+ do {
+ ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off,
+ slot->cache_st.st_size - start_off);
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ return errno;
+ }
+ return 0;
+ } while (1);
+#else
+ ssize_t i, j;
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);
+ do {
+ i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
+ if (i > 0)
+ j = xwrite(STDOUT_FILENO, slot->buf, i);
+ } while (i > 0 && j == i);
- if (j < 0)
+ if (i < 0 || j != i)
return errno;
else
return 0;
+#endif
}
/* Check if the slot has expired */
if (slot->ttl < 0)
return 0;
else
- return slot->cache_st.st_mtime + slot->ttl*60 < time(NULL);
+ return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
}
/* Check if the slot has been modified since we opened it.
*/
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);
+ struct flock lock = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0,
+ };
+
+ slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
+ S_IRUSR | S_IWUSR);
if (slot->lock_fd == -1)
return errno;
- if (write(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
+ if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
+ int saved_errno = errno;
+ close(slot->lock_fd);
+ slot->lock_fd = -1;
+ return saved_errno;
+ }
+ if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
return errno;
return 0;
}
return errno;
/* Redirect stdout to lockfile */
- if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
+ if (dup2(slot->lock_fd, STDOUT_FILENO) == -1) {
+ close(tmp);
return errno;
+ }
/* Generate cache content */
- slot->fn(slot->cbdata);
+ slot->fn();
+
+ /* Make sure any buffered data is flushed to the file */
+ if (fflush(stdout)) {
+ close(tmp);
+ return errno;
+ }
+
+ /* update stat info */
+ if (fstat(slot->lock_fd, &slot->cache_st)) {
+ close(tmp);
+ return errno;
+ }
/* Restore stdout */
- if (dup2(tmp, STDOUT_FILENO) == -1)
+ if (dup2(tmp, STDOUT_FILENO) == -1) {
+ close(tmp);
return errno;
+ }
/* Close the temporary filedescriptor */
if (close(tmp))
if (!s)
return h;
- while(*s) {
+ while (*s) {
h *= FNV_PRIME;
h ^= *s++;
}
}
}
}
- print_slot(slot);
+ if ((err = print_slot(slot)) != 0) {
+ cache_log("[cgit] error printing cache %s: %s (%d)\n",
+ slot->cache_name,
+ strerror(err),
+ err);
+ }
close_slot(slot);
- return 0;
+ return err;
}
/* 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
+ * the content without caching it and fool the caller to believe
* everything worked out (but print a warning on stdout).
*/
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);
+ slot->fn();
return 0;
}
slot->lock_name, strerror(err), err);
unlock_slot(slot, 0);
close_lock(slot);
- slot->fn(slot->cbdata);
+ slot->fn();
return 0;
}
// We've got a valid cache slot in the lock file, which
// the lock file.
slot->cache_fd = slot->lock_fd;
unlock_slot(slot, 1);
- err = print_slot(slot);
+ if ((err = print_slot(slot)) != 0) {
+ cache_log("[cgit] error printing cache %s: %s (%d)\n",
+ slot->cache_name,
+ strerror(err),
+ err);
+ }
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)
+ cache_fill_fn fn)
{
unsigned long hash;
- int len, i;
- char filename[1024];
- char lockname[1024 + 5]; /* 5 = ".lock" */
+ int i;
+ struct strbuf filename = STRBUF_INIT;
+ struct strbuf lockname = STRBUF_INIT;
struct cache_slot slot;
+ int result;
/* If the cache is disabled, just generate the content */
- if (size <= 0) {
- fn(cbdata);
+ if (size <= 0 || ttl == 0) {
+ fn();
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);
+ fn();
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));
+ strbuf_addstr(&filename, path);
+ strbuf_ensure_end(&filename, '/');
+ for (i = 0; i < 8; i++) {
+ strbuf_addf(&filename, "%x", (unsigned char)(hash & 0xf));
hash >>= 4;
}
- filename[len] = '\0';
- strcpy(lockname, filename);
- strcpy(lockname + len, ".lock");
+ strbuf_addbuf(&lockname, &filename);
+ strbuf_addstr(&lockname, ".lock");
slot.fn = fn;
- slot.cbdata = cbdata;
slot.ttl = ttl;
- slot.cache_name = filename;
- slot.lock_name = lockname;
+ slot.cache_name = filename.buf;
+ slot.lock_name = lockname.buf;
slot.key = key;
slot.keylen = strlen(key);
- return process_slot(&slot);
+ result = process_slot(&slot);
+
+ strbuf_release(&filename);
+ strbuf_release(&lockname);
+ return result;
}
/* 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 *sprintftime(const char *format, time_t time)
{
static char buf[64];
struct tm *tm;
DIR *dir;
struct dirent *ent;
int err = 0;
- struct cache_slot slot;
- char fullname[1024];
- char *name;
+ struct cache_slot slot = { NULL };
+ struct strbuf fullname = STRBUF_INIT;
+ size_t prefixlen;
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;
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) {
+ strbuf_addstr(&fullname, path);
+ strbuf_ensure_end(&fullname, '/');
+ prefixlen = fullname.len;
+ while ((ent = readdir(dir)) != NULL) {
if (strlen(ent->d_name) != 8)
continue;
- strcpy(name, ent->d_name);
+ strbuf_setlen(&fullname, prefixlen);
+ strbuf_addstr(&fullname, ent->d_name);
+ slot.cache_name = fullname.buf;
if ((err = open_slot(&slot)) != 0) {
cache_log("[cgit] unable to open path %s: %s (%d)\n",
- fullname, strerror(err), err);
+ fullname.buf, 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);
+ htmlf("%s %s %10"PRIuMAX" %s\n",
+ fullname.buf,
+ sprintftime("%Y-%m-%d %H:%M:%S",
+ slot.cache_st.st_mtime),
+ (uintmax_t)slot.cache_st.st_size,
+ slot.buf);
close_slot(&slot);
}
closedir(dir);
+ strbuf_release(&fullname);
return 0;
}