diff options
Diffstat (limited to 'src/stat_cache.c')
-rw-r--r-- | src/stat_cache.c | 563 |
1 files changed, 0 insertions, 563 deletions
diff --git a/src/stat_cache.c b/src/stat_cache.c deleted file mode 100644 index 55587ec2..00000000 --- a/src/stat_cache.c +++ /dev/null @@ -1,563 +0,0 @@ -/* - * make sure _GNU_SOURCE is defined - */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include <sys/types.h> -#include <sys/stat.h> - -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <stdio.h> -#include <fcntl.h> -#include <assert.h> - -#include "log.h" -#include "stat_cache.h" -#include "fdevent.h" -#include "etag.h" -#include "server.h" -#include "joblist.h" - -#ifdef HAVE_ATTR_ATTRIBUTES_H -#include <attr/attributes.h> -#endif - -#include "sys-mmap.h" -#include "sys-files.h" -#include "sys-strings.h" - -#undef HAVE_FAM_H -#undef HAVE_SYS_INOTIFY_H - -#if 0 -/* enables debug code for testing if all nodes in the stat-cache as accessable */ -#define DEBUG_STAT_CACHE -#endif - -/* - * stat-cache - * - * we cache the stat() calls in our own storage - * the directories are cached in FAM - * - * if we get a change-event from FAM, we increment the version in the FAM->dir mapping - * - * if the stat()-cache is queried we check if the version id for the directory is the - * same and return immediatly. - * - * - * What we need: - * - * - for each stat-cache entry we need a fast indirect lookup on the directory name - * - for each FAMRequest we have to find the version in the directory cache (index as userdata) - * - * stat <<-> directory <-> FAMRequest - * - * if file is deleted, directory is dirty, file is rechecked ... - * if directory is deleted, directory mapping is removed - * - * */ - -/* the directory name is too long to always compare on it - * - we need a hash - * - the hash-key is used as sorting criteria for a tree - * - a splay-tree is used as we can use the caching effect of it - */ - -/* we want to cleanup the stat-cache every few seconds, let's say 10 - * - * - remove entries which are outdated since 30s - * - remove entries which are fresh but havn't been used since 60s - * - if we don't have a stat-cache entry for a directory, release it from the monitor - */ -#ifdef USE_GTHREAD -typedef struct { - buffer *name; - - void *con; -} stat_job; - -static stat_job *stat_job_init() { - stat_job *sj = calloc(1, sizeof(*sj)); - - sj->name = buffer_init(); - - return sj; -} - -static void stat_job_free(stat_job *sj) { - if (!sj) return; - - buffer_free(sj->name); - - free(sj); -} - -gpointer stat_cache_thread(gpointer _srv) { - server *srv = (server *)_srv; - stat_job *sj = NULL; - - /* take the stat-job-queue */ - GAsyncQueue * inq; - - g_async_queue_ref(srv->stat_queue); - - inq = srv->stat_queue; - - /* */ - while (!srv->is_shutdown) { - /* let's see what we have to stat */ - struct stat st; - - if ((sj = g_async_queue_pop(inq))) { - if(sj == (stat_job *) 1) - continue; /* just notifying us that srv->is_shutdown changed */ - - /* don't care about the return code for now */ - stat(sj->name->ptr, &st); - - joblist_async_append(srv, sj->con); - stat_job_free(sj); - } - } - - g_async_queue_unref(srv->stat_queue); - - return NULL; -} -#endif - -#ifdef HAVE_GLIB_H -static guint sc_key_hash(gconstpointer v) { - buffer *b = (buffer *)v; - - return g_str_hash(b->ptr); -} - -static gboolean sc_key_equal(gconstpointer v1, gconstpointer v2) { - buffer *b1 = (buffer *)v1; - buffer *b2 = (buffer *)v2; - - return buffer_is_equal(b1, b2); -} -#endif - -stat_cache *stat_cache_init(void) { - stat_cache *fc = NULL; - - fc = calloc(1, sizeof(*fc)); - - fc->dir_name = buffer_init(); - fc->hash_key = buffer_init(); - -#if defined(HAVE_SYS_INOTIFY_H) - fc->sock = iosocket_init(); -#endif -#ifdef HAVE_GLIB_H - fc->files = g_hash_table_new(sc_key_hash, sc_key_equal); -#endif - - return fc; -} - -static stat_cache_entry * stat_cache_entry_init(void) { - stat_cache_entry *sce = NULL; - - sce = calloc(1, sizeof(*sce)); - - sce->name = buffer_init(); - sce->etag = buffer_init(); - sce->content_type = buffer_init(); - - return sce; -} - -static void stat_cache_entry_free(void *data) { - stat_cache_entry *sce = data; - if (!sce) return; - - buffer_free(sce->etag); - buffer_free(sce->name); - buffer_free(sce->content_type); - - free(sce); -} - -#ifdef HAVE_GLIB_H -static gboolean stat_cache_free_hrfunc(gpointer _key, gpointer _value, gpointer _user_data) { - stat_cache_entry *sce = _value; - buffer *b = _key; - UNUSED(_user_data); - - buffer_free(b); - stat_cache_entry_free(sce); - - return TRUE; -} -#endif - -void stat_cache_free(stat_cache *sc) { -#ifdef HAVE_GLIB_H - g_hash_table_foreach_remove(sc->files, stat_cache_free_hrfunc, NULL); - g_hash_table_destroy(sc->files); -#endif - - buffer_free(sc->dir_name); - buffer_free(sc->hash_key); - -#if defined(HAVE_SYS_INOTIFY_H) - if (sc->sock) iosocket_free(sc->sock); -#endif - - free(sc); -} - -#ifdef HAVE_XATTR -static int stat_cache_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 - -handler_t stat_cache_handle_fdevent(void *_srv, void *_fce, int revent) { - server *srv = _srv; - - switch (srv->srvconf.stat_cache_engine) { -#ifdef HAVE_SYS_INOTIFY_H - case STAT_CACHE_ENGINE_INOTIFY: - return stat_cache_handle_fdevent_inotify(_srv, _fce, revent); -#else - UNUSED(_fce); - UNUSED(revent); -#endif - default: - return HANDLER_GO_ON; - } -} -#ifdef HAVE_LSTAT -static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) { - if (lstat(dname->ptr, lst) == 0) { - return S_ISLNK(lst->st_mode) ? 0 : 1; - } else { - log_error_write(srv, __FILE__, __LINE__, "sbs", - "lstat failed for:", - dname, strerror(errno)); - } - return -1; -} -#endif - -static int stat_cache_entry_is_current(server *srv, stat_cache_entry *sce) { - struct stat st; - UNUSED(srv); - UNUSED(sce); - - if (-1 == stat(sce->name->ptr, &st)) { - return 0; - } - /* still existing */ - - if (st.st_dev != sce->st.st_dev || st.st_ino != sce->st.st_ino) { - return 0; /* different file */ - } - - /* same file, still existing: update other stats: */ - sce->st = st; - /* need to check other properties before this: sce->stat_ts = srv->cur_ts; */ - return 1; -} - -static void stat_cache_remove_entry(stat_cache *sc, buffer *hash_key, stat_cache_entry *sce) { -#ifdef HAVE_GLIB_H - gpointer orig_key; - if (!g_hash_table_lookup_extended(sc->files, hash_key, &orig_key, NULL)) - return; - g_hash_table_remove(sc->files, hash_key); - stat_cache_entry_free(sce); - buffer_free((buffer*) orig_key); -#else - stat_cache_entry_free(sce); -#endif -} - -/*** - * - * - * - * returns: - * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file) - * - HANDLER_ERROR on stat() failed -> see errno for problem - * - * - * glib: if glib is not available, we don't cache - */ - -static handler_t stat_cache_get_entry_internal(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce, int async) { - stat_cache_entry *sce = NULL; - stat_cache *sc; - struct stat st; - size_t k; - int fd; - struct stat lst; - - *ret_sce = NULL; - - /* - * check if the directory for this file has changed - */ - - sc = srv->stat_cache; - - buffer_copy_string_buffer(sc->hash_key, name); - buffer_append_long(sc->hash_key, con->conf.follow_symlink); - -#ifdef HAVE_GLIB_H - if ((sce = (stat_cache_entry *)g_hash_table_lookup(sc->files, sc->hash_key))) { - /* know this entry already */ - - if (sce->state == STAT_CACHE_ENTRY_STAT_FINISHED && - stat_cache_entry_is_current(srv, sce)) { - /* verify that this entry is still fresh */ - - *ret_sce = sce; - - return HANDLER_GO_ON; - } - } - - if (!sce) { - sce = stat_cache_entry_init(); - - buffer_copy_string_buffer(sce->name, name); - - g_hash_table_insert(sc->files, buffer_init_string(BUF_STR(sc->hash_key)), sce); - } -#else - /** - * we don't have glib, but we still have to store the sce somewhere to not loose it - */ - sce = stat_cache_entry_init(); - - buffer_copy_string_buffer(sce->name, name); -#endif - - /* - * *lol* - * - open() + fstat() on a named-pipe results in a (intended) hang. - * - stat() if regular file + open() to see if we can read from it is better - * - * */ - - /* pass a job to the stat-queue */ -#ifdef USE_GTHREAD - if (async && - srv->srvconf.max_stat_threads > 0 && - sce->state == STAT_CACHE_ENTRY_UNSET) { - stat_job *sj = stat_job_init(); - - buffer_copy_string_buffer(sj->name, name); - sj->con = con; - - g_async_queue_push(srv->stat_queue, sj); - - sce->state = STAT_CACHE_ENTRY_ASYNC_STAT; - - /* the response for this will be in the stat-cache, - * a second call will just fetch the data from the stat-cache */ - - return HANDLER_WAIT_FOR_EVENT; - } -#endif - /* - * O_NONBLOCK skips named pipes and locked files - * - * O_NOATIME leads to EPERM on SYMLINKS - * */ -#ifndef O_NONBLOCK -#define O_NONBLOCK 0 -#endif - if (-1 == (fd = open(name->ptr, O_NONBLOCK | O_RDONLY | (srv->srvconf.use_noatime ? O_NOATIME : 0)))) { - if (srv->srvconf.use_noatime && errno == EPERM) { - if (-1 == (fd = open(name->ptr, O_NONBLOCK | O_RDONLY))) { - stat_cache_remove_entry(sc, sc->hash_key, sce); - return HANDLER_ERROR; - } - } else { - stat_cache_remove_entry(sc, sc->hash_key, sce); - return HANDLER_ERROR; - } - } - - if (-1 == fstat(fd, &st)) { - close(fd); - stat_cache_remove_entry(sc, sc->hash_key, sce); - return HANDLER_ERROR; - } - - close(fd); - - sce->st = st; - sce->stat_ts = srv->cur_ts; - sce->state = STAT_CACHE_ENTRY_STAT_FINISHED; - - /* catch the obvious symlinks - * - * this is not a secure check as we still have a race-condition between - * the stat() and the open. We can only solve this by - * 1. open() the file - * 2. fstat() the fd - * - * and keeping the file open for the rest of the time. But this can - * only be done at network level. - * - * per default it is not a symlink - * */ - -#ifdef HAVE_LSTAT - sce->is_symlink = 0; - - /* we want to only check for symlinks if we should block symlinks. - */ - if (!con->conf.follow_symlink) { - if (stat_cache_lstat(srv, name, &lst) == 0) { -#ifdef DEBUG_STAT_CACHE - TRACE("found symlink in %s", SAFE_BUF_STR(name)); -#endif - sce->is_symlink = 1; - } - - /* - * we assume "/" can not be symlink, so - * skip the symlink stuff if our path is / - **/ - else if ((name->used > 2)) { - buffer *dname; - char *s_cur; - - dname = buffer_init(); - buffer_copy_string_buffer(dname, name); - - while ((s_cur = strrchr(dname->ptr,'/'))) { - *s_cur = '\0'; - dname->used = s_cur - dname->ptr + 1; - if (dname->ptr == s_cur) { -#ifdef DEBUG_STAT_CACHE - log_error_write(srv, __FILE__, __LINE__, "s", "reached /"); -#endif - break; - } -#ifdef DEBUG_STAT_CACHE - log_error_write(srv, __FILE__, __LINE__, "sbs", - "checking if", dname, "is a symlink"); -#endif - if (stat_cache_lstat(srv, dname, &lst) == 0) { - sce->is_symlink = 1; -#ifdef DEBUG_STAT_CACHE - log_error_write(srv, __FILE__, __LINE__, "sb", - "found symlink", dname); -#endif - break; - } - } - buffer_free(dname); - } - } -#endif - - if (S_ISREG(st.st_mode)) { - /* determine mimetype */ - buffer_reset(sce->content_type); -#ifdef HAVE_XATTR - if (con->conf.use_xattr) { - stat_cache_attr_get(sce->content_type, name->ptr); - } -#endif - /* xattr did not set a content-type. ask the config */ - if (buffer_is_empty(sce->content_type)) { - for (k = 0; k < con->conf.mimetypes->used; k++) { - data_string *ds = (data_string *)con->conf.mimetypes->data[k]; - buffer *type = ds->key; - - if (type->used == 0) continue; - - /* check if the right side is the same */ - if (type->used > name->used) continue; - - if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) { - buffer_copy_string_buffer(sce->content_type, ds->value); - break; - } - } - } - etag_create(sce->etag, &(sce->st), con->etag_flags); - } else if (S_ISDIR(st.st_mode)) { - etag_create(sce->etag, &(sce->st), con->etag_flags); - } - - *ret_sce = sce; - - return HANDLER_GO_ON; -} - - -handler_t stat_cache_get_entry_async(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) { - return stat_cache_get_entry_internal(srv, con, name, ret_sce, 1); -} - -handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) { - return stat_cache_get_entry_internal(srv, con, name, ret_sce, 0); -} - -/** - * remove stat() from cache which havn't been stat()ed for - * more than 10 seconds - * - * - * walk though the stat-cache, collect the ids which are too old - * and remove them in a second loop - */ - -#ifdef HAVE_GLIB_H -static gboolean stat_cache_remove_old_entry(gpointer _key, gpointer _value, gpointer user_data) { - server *srv = user_data; - buffer *key = _key; - stat_cache_entry *sce = _value; - - if (sce->state == STAT_CACHE_ENTRY_STAT_FINISHED && - srv->cur_ts - sce->stat_ts > 10) { - buffer_free(key); - stat_cache_entry_free(sce); - - return TRUE; - } - - return FALSE; -} -#endif - -int stat_cache_trigger_cleanup(server *srv) { - stat_cache *sc; - - sc = srv->stat_cache; - -#ifdef HAVE_GLIB_H - if (!sc->files) return 0; - - g_hash_table_foreach_remove(sc->files, stat_cache_remove_old_entry, srv); -#endif - - return 0; -} |