lighttpd1.4/src/file_cache.c

484 lines
9.8 KiB
C
Raw Normal View History

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include "log.h"
#include "file_cache.h"
#include "fdevent.h"
#include "etag.h"
#ifdef HAVE_ATTR_ATTRIBUTES_H
#include <attr/attributes.h>
#endif
#include "sys-mmap.h"
/* NetBSD 1.3.x needs it */
#ifndef MAP_FAILED
# define MAP_FAILED -1
#endif
#ifndef O_LARGEFILE
# define O_LARGEFILE 0
#endif
#ifndef HAVE_LSTAT
#define lstat stat
#endif
/* don't enable the dir-cache
*
* F_NOTIFY would be nice but only works with linux-rtsig
*/
#undef USE_LINUX_SIGIO
file_cache *file_cache_init(void) {
file_cache *fc = NULL;
fc = calloc(1, sizeof(*fc));
fc->dir_name = buffer_init();
return fc;
}
static file_cache_entry * file_cache_entry_init(void) {
file_cache_entry *fce = NULL;
fce = calloc(1, sizeof(*fce));
fce->fd = -1;
fce->fde_ndx = -1;
fce->name = buffer_init();
fce->etag = buffer_init();
fce->content_type = buffer_init();
return fce;
}
static void file_cache_entry_free(server *srv, file_cache_entry *fce) {
if (!fce) return;
if (fce->fd >= 0) {
close(fce->fd);
srv->cur_fds--;
}
buffer_free(fce->etag);
buffer_free(fce->name);
buffer_free(fce->content_type);
if (fce->mmap_p) munmap(fce->mmap_p, fce->mmap_length);
free(fce);
}
static int file_cache_entry_reset(server *srv, file_cache_entry *fce) {
if (fce->fd < 0) return 0;
close(fce->fd);
srv->cur_fds--;
#ifdef USE_LINUX_SIGIO
/* doesn't work anymore */
if (fce->fde_ndx != -1) {
fdevent_event_del(srv->ev, &(fce->fde_ndx), fce->fd);
}
#else
UNUSED(srv);
#endif
if (fce->mmap_p) {
munmap(fce->mmap_p, fce->mmap_length);
fce->mmap_p = NULL;
}
fce->fd = -1;
buffer_reset(fce->etag);
buffer_reset(fce->name);
buffer_reset(fce->content_type);
return 0;
}
void file_cache_free(server *srv, file_cache *fc) {
size_t i;
for (i = 0; i < fc->used; i++) {
file_cache_entry_free(srv, fc->ptr[i]);
}
free(fc->ptr);
buffer_free(fc->dir_name);
free(fc);
}
#ifdef HAVE_XATTR
int fce_attr_get(buffer *buf, char *name) {
int attrlen;
int ret;
attrlen = 1024;
buffer_prepare_copy(buf, attrlen);
attrlen--;
if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) {
buf->used = attrlen + 1;
buf->ptr[attrlen] = '\0';
}
return ret;
}
#endif
file_cache_entry * file_cache_get_unused_entry(server *srv) {
file_cache_entry *fce = NULL;
file_cache *fc = srv->file_cache;
size_t i;
if (fc->size == 0) {
fc->size = 16;
fc->ptr = calloc(fc->size, sizeof(*fc->ptr));
fc->used = 0;
}
for (i = 0; i < fc->used; i++) {
file_cache_entry *f = fc->ptr[i];
if (f->fd == -1) {
return f;
}
}
if (fc->used < fc->size) {
fce = file_cache_entry_init();
fc->ptr[fc->used++] = fce;
} else {
/* the cache is full, time to resize */
fc->size += 16;
fc->ptr = realloc(fc->ptr, sizeof(*fc->ptr) * fc->size);
fce = file_cache_entry_init();
fc->ptr[fc->used++] = fce;
}
return fce;
}
handler_t file_cache_handle_fdevent(void *_srv, void *_fce, int revent) {
size_t i;
server *srv = _srv;
file_cache_entry *fce = _fce;
file_cache *fc = srv->file_cache;;
UNUSED(revent);
/* */
#if 0
log_error_write(srv, __FILE__, __LINE__, "sds", "dir has changed: ", fce->fd, fce->name->ptr);
#endif
/* touch all files below this directory */
for (i = 0; i < fc->used; i++) {
file_cache_entry *f = fc->ptr[i];
if (fce == f) continue;
if (0 == strncmp(fce->name->ptr, f->name->ptr, fce->name->used - 1)) {
#if 0
log_error_write(srv, __FILE__, __LINE__, "ss", "file hit: ", f->name->ptr);
#endif
f->is_dirty = 1;
}
}
return HANDLER_GO_ON;
}
#if 0
/* dead code, might be reused somewhere again */
int file_cache_check_cache() {
file_cache_entry *first_unused_fce = NULL;
file_cache *fc = srv->file_cache;
size_t i;
/* check the cache */
for (i = 0; i < fc->used; i++) {
fce = fc->ptr[i];
if (buffer_is_equal(name, fce->name)) {
log_error_write(srv, __FILE__, __LINE__, "sb", "cache hit:", name);
#ifdef USE_LINUX_SIGIO
if (fce->is_dirty == 0) {
fce->in_use++;
return fce;
}
#endif
/* get the etag information */
if (-1 == (stat(name->ptr, &(fce->st)))) {
fce->in_use = 0;
log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed: ", name, strerror(errno));
file_cache_entry_reset(srv, fce);
return NULL;
}
fce->stat_ts = srv->cur_ts;
/* create etag */
etag_create(srv->file_cache_etag, &(fce->st));
if (!buffer_is_equal(srv->file_cache_etag, fce->etag)) {
size_t s_len = 0, k;
/* etag has changed: reopen file */
file_cache_entry_reset(srv, fce);
if (-1 == (fce->fd = open(fce->name->ptr, O_RDONLY | O_LARGEFILE))) {
log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
buffer_reset(fce->name);
return NULL;
}
srv->cur_fds++;
buffer_copy_string_buffer(fce->etag, srv->file_cache_etag);
/* determine mimetype */
buffer_reset(fce->content_type);
s_len = name->used - 1;
for (k = 0; k < con->conf.mimetypes->used; k++) {
data_string *ds = (data_string *)con->conf.mimetypes->data[k];
size_t ct_len;
ct_len = ds->key->used - 1;
if (buffer_is_equal_right_len(name, ds->key, ct_len)) {
buffer_copy_string_buffer(fce->content_type, ds->value);
break;
}
}
#ifdef HAVE_XATTR
if (buffer_is_empty(fce->content_type)) {
fce_attr_get(fce->content_type, name->ptr);
}
#endif
}
#ifdef USE_LINUX_SIGIO
fce->is_dirty = 0;
#endif
fce->in_use++;
if (fce->fd == -1) {
log_error_write(srv, __FILE__, __LINE__, "sb", "fd is still -1 !", fce->name);
}
if (fce->st.st_size == 0) {
log_error_write(srv, __FILE__, __LINE__, "sb", "size is still 0 !", fce->name);
}
return fce;
}
if (fce->in_use == 0) {
if (!first_unused_fce) first_unused_fce = fce;
if (srv->cur_ts - fce->stat_ts > 10) {
file_cache_entry_reset(srv, fce);
}
}
}
if (first_unused_fce) {
file_cache_entry_reset(srv, fce);
fce = first_unused_fce;
} else {
/* not found, insert */
fce = file_cache_get_unused_entry(srv);
}
}
#endif
handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce_ptr) {
file_cache_entry *fce = NULL;
file_cache_entry *o_fce = *o_fce_ptr;
UNUSED(con);
/* still valid ? */
if (o_fce != NULL) {
if (buffer_is_equal(name, o_fce->name) &&
(o_fce->fd != -1) &&
(o_fce->stat_ts == srv->cur_ts)
) {
return HANDLER_GO_ON;
} else {
o_fce->in_use--;
}
file_cache_entry_reset(srv, o_fce);
}
fce = file_cache_get_unused_entry(srv);
buffer_copy_string_buffer(fce->name, name);
fce->in_use = 0;
fce->fd = -1;
if (-1 == (con->conf.follow_symlink ? stat(name->ptr, &(fce->st)) : lstat(name->ptr, &(fce->st)))) {
int oerrno = errno;
#if 0
log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed:", name, strerror(errno));
#endif
file_cache_entry_reset(srv, fce);
buffer_reset(fce->name);
errno = oerrno;
return HANDLER_ERROR;
}
fce->stat_ts = srv->cur_ts;
if (S_ISREG(fce->st.st_mode)) {
size_t k, s_len;
#ifdef USE_LINUX_SIGIO
file_cache_entry *dir_fce;
char *slash;
#endif
if (-1 == (fce->fd = open(name->ptr, O_RDONLY | O_LARGEFILE))) {
int oerrno = errno;
if (errno == EMFILE || errno == EINTR) {
return HANDLER_WAIT_FOR_FD;
}
log_error_write(srv, __FILE__, __LINE__, "sbs",
"open failed for:", name,
strerror(errno));
buffer_reset(fce->name);
errno = oerrno;
return HANDLER_ERROR;
}
srv->cur_fds++;
/* determine mimetype */
buffer_reset(fce->content_type);
s_len = name->used - 1;
for (k = 0; k < con->conf.mimetypes->used; k++) {
data_string *ds = (data_string *)con->conf.mimetypes->data[k];
size_t ct_len;
if (ds->key->used == 0) continue;
ct_len = ds->key->used - 1;
if (s_len < ct_len) continue;
if (0 == strncmp(name->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
buffer_copy_string_buffer(fce->content_type, ds->value);
break;
}
}
#ifdef HAVE_XATTR
if (buffer_is_empty(fce->content_type)) {
fce_attr_get(fce->content_type, name->ptr);
}
#endif
etag_create(fce->etag, &(fce->st));
#ifdef USE_LINUX_SIGIO
/* register sigio for the directory */
dir_fce = file_cache_get_unused_entry(srv);
buffer_copy_string_buffer(fc->dir_name, name);
/* get dirname */
if (0 == (slash = strrchr(fc->dir_name->ptr, '/'))) {
SEGFAULT();
}
*(slash+1) = '\0';
if (-1 == (dir_fce->fd = open(fc->dir_name->ptr, O_RDONLY))) {
int oerrno = errno;
log_error_write(srv, __FILE__, __LINE__, "sbs",
"open failed:", fc->dir_name, strerror(errno));
errno = oerrno;
return HANDLER_ERROR;
}
srv->cur_fds++;
if (fcntl(dir_fce->fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT) < 0) {
int oerrno = errno;
log_error_write(srv, __FILE__, __LINE__, "ss",
"fcntl failed:", strerror(errno));
close(dir_fce->fd);
srv->cur_fds--;
errno = oerrno;
return HANDLER_ERROR;
}
/* ->used is not updated -> no _buffer copy */
buffer_copy_string(dir_fce->name, fc->dir_name->ptr);
/* register fd-handler */
fdevent_register(srv->ev, dir_fce->fd, file_cache_handle_fdevent, dir_fce);
fdevent_event_add(srv->ev, &(dir_fce->fde_ndx), dir_fce->fd, FDEVENT_IN);
# if 1
log_error_write(srv, __FILE__, __LINE__, "sddb", "fdevent_event_add:", fce->fde_ndx, fce->fd, fce->name);
# endif
#endif
}
fce->in_use++;
*o_fce_ptr = fce;
return HANDLER_GO_ON;
}
int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce) {
UNUSED(srv);
UNUSED(con);
if (fce->in_use > 0) fce->in_use--;
file_cache_entry_reset(srv, fce);
return 0;
}