summaryrefslogtreecommitdiff
path: root/src/stat_cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stat_cache.c')
-rw-r--r--src/stat_cache.c563
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;
-}