summaryrefslogtreecommitdiffstats
path: root/mod_glusterfs/lighttpd
diff options
context:
space:
mode:
Diffstat (limited to 'mod_glusterfs/lighttpd')
-rw-r--r--mod_glusterfs/lighttpd/1.4/Makefile.am3
-rw-r--r--mod_glusterfs/lighttpd/1.4/Makefile.am.diff29
-rw-r--r--mod_glusterfs/lighttpd/1.4/README.txt57
-rw-r--r--mod_glusterfs/lighttpd/1.4/mod_glusterfs.c1709
-rw-r--r--mod_glusterfs/lighttpd/1.4/mod_glusterfs.h29
-rw-r--r--mod_glusterfs/lighttpd/1.5/Makefile.am3
-rw-r--r--mod_glusterfs/lighttpd/1.5/Makefile.am.diff29
-rw-r--r--mod_glusterfs/lighttpd/1.5/README.txt57
-rw-r--r--mod_glusterfs/lighttpd/1.5/mod_glusterfs.c1476
-rw-r--r--mod_glusterfs/lighttpd/1.5/mod_glusterfs.h29
-rw-r--r--mod_glusterfs/lighttpd/Makefile.am3
11 files changed, 3424 insertions, 0 deletions
diff --git a/mod_glusterfs/lighttpd/1.4/Makefile.am b/mod_glusterfs/lighttpd/1.4/Makefile.am
new file mode 100644
index 000000000..eda329111
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.4/Makefile.am
@@ -0,0 +1,3 @@
+EXTRA_DIST = Makefile.am.diff mod_glusterfs.c mod_glusterfs.h README.txt
+
+CLEANFILES =
diff --git a/mod_glusterfs/lighttpd/1.4/Makefile.am.diff b/mod_glusterfs/lighttpd/1.4/Makefile.am.diff
new file mode 100644
index 000000000..375696b5d
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.4/Makefile.am.diff
@@ -0,0 +1,29 @@
+--- lighttpd-1.4.19/src/Makefile.am 2008-04-16 18:42:18.000000000 +0400
++++ lighttpd-1.4.19.mod/src/Makefile.am 2008-04-16 18:41:11.000000000 +0400
+@@ -1,4 +1,4 @@
+-AM_CFLAGS = $(FAM_CFLAGS)
++AM_CFLAGS = $(FAM_CFLAGS) -D_FILE_OFFSET_BITS=64 -D__USE_FILE_OFFSET64
+
+ noinst_PROGRAMS=proc_open lemon # simple-fcgi #graphic evalo bench ajp ssl error_test adserver gen-license
+ sbin_PROGRAMS=lighttpd lighttpd-angel
+@@ -241,6 +241,11 @@
+ mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+ mod_accesslog_la_LIBADD = $(common_libadd)
+
++lib_LTLIBRARIES += mod_glusterfs.la
++mod_glusterfs_la_SOURCES = mod_glusterfs.c
++mod_glusterfs_la_CFLAGS = $(AM_CFLAGS)
++mod_glusterfs_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined -lglusterfsclient -lpthread
++mod_glusterfs_la_LIBADD = $(common_libadd)
+
+ hdr = server.h buffer.h network.h log.h keyvalue.h \
+ response.h request.h fastcgi.h chunk.h \
+@@ -254,7 +259,7 @@
+ configparser.h mod_ssi_exprparser.h \
+ sys-mmap.h sys-socket.h mod_cml.h mod_cml_funcs.h \
+ splaytree.h proc_open.h status_counter.h \
+- mod_magnet_cache.h
++ mod_magnet_cache.h mod_glusterfs.h
+
+ DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\"" -DSBIN_DIR="\"$(sbindir)\""
+
diff --git a/mod_glusterfs/lighttpd/1.4/README.txt b/mod_glusterfs/lighttpd/1.4/README.txt
new file mode 100644
index 000000000..786a146e4
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.4/README.txt
@@ -0,0 +1,57 @@
+Introduction
+============
+mod_glusterfs is a module written for lighttpd to speed up the access of files present on glusterfs. mod_glusterfs uses libglusterfsclient library provided for glusterfs and hence can be used without fuse (File System in User Space).
+
+Usage
+=====
+To use mod_glusterfs with lighttpd-1.4, copy mod_glusterfs.c and mod_glusterfs.h into src/ of lighttpd-1.4 source tree, and apply the Makefile.am.diff to src/Makefile.am. Re-run ./autogen.sh on the top level of the lighttpd-1.4 build tree and recompile.
+
+# cp mod_glusterfs.[ch] /home/glusterfs/lighttpd-1.4/src/
+# cp Makefile.am.diff /home/glusterfs/lighttpd-1.4/
+# cd /home/glusterfs/lighttpd-1.4
+# patch -p1 < Makefile.am.diff
+# ./autogen.sh
+# ./configure
+# make
+# make install
+
+Configuration
+=============
+* mod_glusterfs should be listed at the begining of the list server.modules in lighttpd.conf.
+
+Below is a snippet from lighttpd.conf concerning to mod_glusterfs.
+
+$HTTP["url"] =~ "^/glusterfs" {
+ glusterfs.prefix = "/glusterfs"
+ glusterfs.document-root = "/home/glusterfs/document-root"
+ glusterfs.logfile = "/var/log/glusterfs-logfile"
+ glusterfs.volume-specfile = "/etc/glusterfs/glusterfs.vol"
+ glusterfs.loglevel = "error"
+ glusterfs.cache-timeout = 300
+ glusterfs.xattr-interface-size-limit = "65536"
+}
+
+* $HTTP["url"] =~ "^/glusterfs"
+ A perl style regular expression used to match against the url. If regular expression matches the url, the url is handled by mod_glusterfs. Note that the pattern given here should match glusterfs.prefix.
+
+* glusterfs.prefix (COMPULSORY)
+ A string to be present at the starting of the file path in the url so that the file would be handled by glusterfs.
+ Eg., A GET request on the url http://www.example.com/glusterfs-prefix/some-dir/example-file will result in fetching of the file "/some-dir/example-file" from glusterfs mount if glusterfs.prefix is set to "/glusterfs-prefix".
+
+* glusterfs.volume-specfile (COMPULSORY)
+ Path to the the glusterfs volume specification file.
+
+* glusterfs.logfile (COMPULSORY)
+ Path to the glusterfs logfile.
+
+* glusterfs.loglevel (OPTIONAL, default = warning)
+ Allowed values are critical, error, warning, debug, none in the decreasing order of severity of error conditions.
+
+* glusterfs.cache-timeout (OPTIONAL, default = 0)
+ Timeout values for glusterfs stat and lookup cache.
+
+* glusterfs.document-root (COMPULSORY)
+ An absolute path, relative to which all the files are fetched from glusterfs.
+
+* glusterfs.xattr-interface-size-limit (OPTIONAL, default = 0)
+ Files with sizes upto and including this value are fetched through the extended attribute interface of glusterfs rather than the usual open-read-close set of operations. For files of small sizes, it is recommended to use extended attribute interface.
diff --git a/mod_glusterfs/lighttpd/1.4/mod_glusterfs.c b/mod_glusterfs/lighttpd/1.4/mod_glusterfs.c
new file mode 100644
index 000000000..9de33b7c4
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.4/mod_glusterfs.c
@@ -0,0 +1,1709 @@
+/*
+ Copyright (c) 2008 Z RESEARCH, Inc. <http://www.zresearch.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "stat_cache.h"
+#include "mod_glusterfs.h"
+#include "etag.h"
+#include "http_chunk.h"
+#include "response.h"
+
+#include "fdevent.h"
+#include <libglusterfsclient.h>
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+#ifdef HAVE_FAM_H
+# include <fam.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
+
+#if 0
+/* enables debug code for testing if all nodes in the stat-cache as accessable */
+#define DEBUG_STAT_CACHE
+#endif
+
+#ifdef HAVE_LSTAT
+#undef HAVE_LSTAT
+#endif
+
+#define GLUSTERFS_FILE_CHUNK (FILE_CHUNK + 1)
+
+/* Keep this value large. Each glusterfs_async_read of GLUSTERFS_CHUNK_SIZE results in a network_backend_write of the read data*/
+
+#define GLUSTERFS_CHUNK_SIZE 8192
+
+/**
+ * this is a staticfile for a lighttpd plugin
+ *
+ */
+
+typedef struct glusterfs_async_local {
+ int op_ret;
+ int op_errno;
+ char async_read_complete;
+ off_t length;
+ size_t read_bytes;
+ glusterfs_read_buf_t *buf;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+} glusterfs_async_local_t;
+
+
+typedef struct {
+ unsigned long fd;
+ void *buf;
+ buffer *glusterfs_path;
+ /* off_t response_content_length; */
+ int prefix;
+}mod_glusterfs_ctx_t;
+
+/* plugin config for all request/connections */
+typedef struct {
+ buffer *logfile;
+ buffer *loglevel;
+ buffer *specfile;
+ buffer *prefix;
+ buffer *xattr_file_size;
+ buffer *document_root;
+ array *exclude_exts;
+ unsigned short cache_timeout;
+
+ /* FIXME: its a pointer, hence cant be short */
+ unsigned long handle;
+} plugin_config;
+
+static int (*network_backend_write)(struct server *srv, connection *con, int fd, chunkqueue *cq);
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *range_buf;
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef struct {
+ chunkqueue *cq;
+ glusterfs_read_buf_t *buf;
+ size_t length;
+}mod_glusterfs_chunkqueue;
+
+#ifdef HAVE_FAM_H
+typedef struct {
+ FAMRequest *req;
+ FAMConnection *fc;
+
+ buffer *name;
+
+ int version;
+} fam_dir_entry;
+#endif
+
+/* 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 DEBUG_STAT_CACHE
+typedef struct {
+ int *ptr;
+
+ size_t used;
+ size_t size;
+} fake_keys;
+
+static fake_keys ctrl;
+#endif
+
+int
+mod_glusterfs_readv_async_cbk (glusterfs_read_buf_t *buf,
+ void *cbk_data)
+{
+ glusterfs_async_local_t *local = cbk_data;
+ pthread_mutex_lock (&local->lock);
+ {
+ local->async_read_complete = 1;
+ local->buf = buf;
+
+ pthread_cond_signal (&local->cond);
+ }
+ pthread_mutex_unlock (&local->lock);
+
+ return 0;
+}
+
+static int
+mod_glusterfs_read_async (server *srv, connection *con, chunk *glusterfs_chunk)
+{
+ glusterfs_async_local_t local;
+ off_t end = 0;
+ int nbytes;
+ int complete;
+ chunkqueue *cq = NULL;
+ chunk *c = NULL;
+ off_t offset = glusterfs_chunk->file.start;
+ size_t length = glusterfs_chunk->file.length;
+ long fd = (long)glusterfs_chunk->file.name;
+
+ pthread_cond_init (&local.cond, NULL);
+ pthread_mutex_init (&local.lock, NULL);
+
+ //local.fd = fd;
+ memset (&local, 0, sizeof (local));
+
+ if (length > 0)
+ end = offset + length;
+
+ cq = chunkqueue_init ();
+ if (!cq) {
+ con->http_status = 500;
+ return HANDLER_FINISHED;
+ }
+
+ do {
+ glusterfs_read_buf_t *buf;
+ int i;
+ if (length > 0) {
+ nbytes = end - offset;
+ if (nbytes > GLUSTERFS_CHUNK_SIZE)
+ nbytes = GLUSTERFS_CHUNK_SIZE;
+ } else
+ nbytes = GLUSTERFS_CHUNK_SIZE;
+
+ glusterfs_read_async(fd,
+ nbytes,
+ offset,
+ mod_glusterfs_readv_async_cbk,
+ (void *)&local);
+
+ pthread_mutex_lock (&local.lock);
+ {
+ while (!local.async_read_complete) {
+ pthread_cond_wait (&local.cond, &local.lock);
+ }
+
+ local.op_ret = local.buf->op_ret;
+ local.op_errno = local.buf->op_errno;
+
+ local.async_read_complete = 0;
+ buf = local.buf;
+
+ if ((int)length < 0)
+ complete = (local.buf->op_ret <= 0);
+ else {
+ local.read_bytes += local.buf->op_ret;
+ complete = ((local.read_bytes == length) || (local.buf->op_ret <= 0));
+ }
+ }
+ pthread_mutex_unlock (&local.lock);
+
+ if (local.op_ret > 0) {
+ unsigned long check = 0;
+ for (i = 0; i < buf->count; i++) {
+ buffer *nw_write_buf = buffer_init ();
+
+ check += buf->vector[i].iov_len;
+
+ nw_write_buf->used = nw_write_buf->size = buf->vector[i].iov_len + 1;
+ nw_write_buf->ptr = buf->vector[i].iov_base;
+
+ // buffer_copy_memory (nw_write_buf, buf->vector[i].iov_base, buf->vector[i].iov_len + 1);
+ offset += local.op_ret;
+ chunkqueue_append_buffer_weak (cq, nw_write_buf);
+ }
+
+ network_backend_write (srv, con, con->fd, cq);
+
+ if (chunkqueue_written (cq) != local.op_ret) {
+ mod_glusterfs_chunkqueue *gf_cq;
+ glusterfs_chunk->file.start = offset;
+ if ((int)glusterfs_chunk->file.length > 0)
+ glusterfs_chunk->file.length -= local.read_bytes;
+
+ gf_cq = calloc (1, sizeof (*gf_cq));
+ /* ERR_ABORT (gf_cq); */
+ gf_cq->cq = cq;
+ gf_cq->buf = buf;
+ gf_cq->length = local.op_ret;
+ glusterfs_chunk->file.mmap.start = (char *)gf_cq;
+ return local.read_bytes;
+ }
+
+ for (c = cq->first ; c; c = c->next)
+ c->mem->ptr = NULL;
+
+ chunkqueue_reset (cq);
+ }
+
+ glusterfs_free (buf);
+ } while (!complete);
+
+ chunkqueue_free (cq);
+ glusterfs_close (fd);
+
+ if (local.op_ret < 0)
+ con->http_status = 500;
+
+ return (local.op_ret < 0 ? HANDLER_FINISHED : HANDLER_GO_ON);
+}
+
+int mod_glusterfs_network_backend_write(struct server *srv, connection *con, int fd, chunkqueue *cq)
+{
+ chunk *c, *prev, *first;
+ int chunks_written = 0;
+ int error = 0;
+
+ for (first = prev = c = cq->first; c; c = c->next, chunks_written++) {
+
+ if (c->type == MEM_CHUNK && c->mem->used && !c->mem->ptr) {
+ if (cq->first != c) {
+ prev->next = NULL;
+
+ /* call stored network_backend_write */
+ network_backend_write (srv, con, fd, cq);
+
+ prev->next = c;
+ }
+ cq->first = c->next;
+
+ if (c->file.fd < 0) {
+ error = HANDLER_ERROR;
+ break;
+ }
+
+ if (c->file.mmap.start) {
+ chunk *tmp;
+ mod_glusterfs_chunkqueue *gf_cq = (mod_glusterfs_chunkqueue *)c->file.mmap.start;
+
+ network_backend_write (srv, con, fd, gf_cq->cq);
+
+ if ((size_t)chunkqueue_written (gf_cq->cq) != gf_cq->length) {
+ cq->first = first;
+ return chunks_written;
+ }
+ for (tmp = gf_cq->cq->first ; tmp; tmp = tmp->next)
+ tmp->mem->ptr = NULL;
+
+ chunkqueue_free (gf_cq->cq);
+ glusterfs_free (gf_cq->buf);
+ free (gf_cq);
+ c->file.mmap.start = NULL;
+ }
+
+ mod_glusterfs_read_async (srv, con, c); //c->file.fd, c->file.start, -1);//c->file.length);
+ if (c->file.mmap.start) {
+ /* pending chunkqueue from mod_glusterfs_read_async to be written to network */
+ cq->first = first;
+ return chunks_written;
+ }
+
+ buffer_free (c->mem);
+ c->mem = NULL;
+
+ c->type = FILE_CHUNK;
+ c->offset = c->file.length = 0;
+ c->file.name = NULL;
+
+ if (first == c)
+ first = c->next;
+
+ if (cq->last == c)
+ cq->last = NULL;
+
+ prev->next = c->next;
+
+ free(c);
+ }
+ prev = c;
+ }
+
+ network_backend_write (srv, con, fd, cq);
+
+ cq->first = first;
+
+ return chunks_written;
+}
+
+int chunkqueue_append_glusterfs_file (connection *con, long fd, off_t offset, off_t len)
+{
+ chunk *c = NULL;
+ c = chunkqueue_get_append_tempfile (con->write_queue);
+
+ if (c->file.is_temp) {
+ close (c->file.fd);
+ unlink (c->file.name->ptr);
+ }
+
+ c->type = MEM_CHUNK;
+
+ c->mem = buffer_init ();
+ c->mem->used = len + 1;
+ c->mem->ptr = NULL;
+ c->offset = 0;
+
+ /* buffer_copy_string_buffer (c->file.name, fn); */
+ buffer_free (c->file.name);
+
+ /* fd returned by libglusterfsclient is a pointer */
+ c->file.name = (buffer *)fd;
+ c->file.start = offset;
+ c->file.length = len;
+
+ //c->file.fd = fd;
+ c->file.mmap.start = NULL;
+ return 0;
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_glusterfs_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+ /* ERR_ABORT (p); */
+ network_backend_write = NULL;
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_glusterfs_free) {
+ plugin_data *p = p_d;
+
+ UNUSED (srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free (s->logfile);
+ buffer_free (s->loglevel);
+ buffer_free (s->specfile);
+ buffer_free (s->prefix);
+ buffer_free (s->xattr_file_size);
+ buffer_free (s->document_root);
+ array_free (s->exclude_exts);
+
+ free (s);
+ }
+ free (p->config_storage);
+ }
+ buffer_free (p->range_buf);
+
+ free (p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_glusterfs_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "glusterfs.logfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.loglevel", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.volume-specfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.cache-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+
+ /*TODO: get the prefix from config_conext and remove glusterfs.prefix from conf file */
+ { "glusterfs.prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.xattr-interface-size-limit", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+ /* ERR_ABORT (p->config_storage);*/
+ p->range_buf = buffer_init ();
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ /* ERR_ABORT (s); */
+ s->logfile = buffer_init ();
+ s->loglevel = buffer_init ();
+ s->specfile = buffer_init ();
+ s->document_root = buffer_init ();
+ s->exclude_exts = array_init ();
+ s->prefix = buffer_init ();
+ s->xattr_file_size = buffer_init ();
+
+ cv[0].destination = s->logfile;
+ cv[1].destination = s->loglevel;
+ cv[2].destination = s->specfile;
+ cv[3].destination = &s->cache_timeout;
+ cv[4].destination = s->exclude_exts;
+ cv[5].destination = s->prefix;
+ cv[6].destination = s->xattr_file_size;
+ cv[7].destination = s->document_root;
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_FINISHED;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+
+static int mod_glusterfs_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s;
+
+ /* skip the first, the global context */
+ /* glusterfs related config can only occur inside $HTTP["url"] == "<glusterfs-prefix>" */
+ p->conf.logfile = NULL;
+ p->conf.loglevel = NULL;
+ p->conf.specfile = NULL;
+ p->conf.cache_timeout = 0;
+ p->conf.exclude_exts = NULL;
+ p->conf.prefix = NULL;
+ p->conf.xattr_file_size = NULL;
+ p->conf.document_root = NULL;
+
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.logfile"))) {
+ PATCH (logfile);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.loglevel"))) {
+ PATCH (loglevel);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.volume-specfile"))) {
+ PATCH (specfile);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.cache-timeout"))) {
+ PATCH (cache_timeout);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.exclude-extensions"))) {
+ PATCH (exclude_exts);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.prefix"))) {
+ PATCH (prefix);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.xattr-interface-size-limit"))) {
+ PATCH (xattr_file_size);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.document-root"))) {
+ PATCH (document_root);
+ }
+ }
+ }
+ return 0;
+}
+
+#undef PATCH
+
+static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
+ int multipart = 0;
+ int error;
+ off_t start, end;
+ const char *s, *minus;
+ char *boundary = "fkj49sn38dcn3";
+ data_string *ds;
+ stat_cache_entry *sce = NULL;
+ buffer *content_type = NULL;
+ size_t size = 0;
+ mod_glusterfs_ctx_t *ctx = con->plugin_ctx[p->id];
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr) {
+ size = atoi (p->conf.xattr_file_size->ptr);
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ SEGFAULT();
+ }
+
+ start = 0;
+ end = sce->st.st_size - 1;
+
+ con->response.content_length = 0;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
+ content_type = ds->value;
+ }
+
+ for (s = con->request.http_range, error = 0;
+ !error && *s && NULL != (minus = strchr(s, '-')); ) {
+ char *err;
+ off_t la, le;
+
+ if (s == minus) {
+ /* -<stop> */
+
+ le = strtoll(s, &err, 10);
+
+ if (le == 0) {
+ /* RFC 2616 - 14.35.1 */
+
+ con->http_status = 416;
+ error = 1;
+ } else if (*err == '\0') {
+ /* end */
+ s = err;
+
+ end = sce->st.st_size - 1;
+ start = sce->st.st_size + le;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = sce->st.st_size - 1;
+ start = sce->st.st_size + le;
+ } else {
+ error = 1;
+ }
+
+ } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
+ /* <start>- */
+
+ la = strtoll(s, &err, 10);
+
+ if (err == minus) {
+ /* ok */
+
+ if (*(err + 1) == '\0') {
+ s = err + 1;
+
+ end = sce->st.st_size - 1;
+ start = la;
+
+ } else if (*(err + 1) == ',') {
+ multipart = 1;
+ s = err + 2;
+
+ end = sce->st.st_size - 1;
+ start = la;
+ } else {
+ error = 1;
+ }
+ } else {
+ /* error */
+ error = 1;
+ }
+ } else {
+ /* <start>-<stop> */
+
+ la = strtoll(s, &err, 10);
+
+ if (err == minus) {
+ le = strtoll(minus+1, &err, 10);
+
+ /* RFC 2616 - 14.35.1 */
+ if (la > le) {
+ error = 1;
+ }
+
+ if (*err == '\0') {
+ /* ok, end*/
+ s = err;
+
+ end = le;
+ start = la;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = le;
+ start = la;
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ }
+
+ if (!error) {
+ if (start < 0) start = 0;
+
+ /* RFC 2616 - 14.35.1 */
+ if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
+
+ if (start > sce->st.st_size - 1) {
+ error = 1;
+
+ con->http_status = 416;
+ }
+ }
+
+ if (!error) {
+ if (multipart) {
+ /* write boundary-header */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string(b, "\r\n--");
+ buffer_append_string(b, boundary);
+
+ /* write Content-Range */
+ buffer_append_string(b, "\r\nContent-Range: bytes ");
+ buffer_append_off_t(b, start);
+ buffer_append_string(b, "-");
+ buffer_append_off_t(b, end);
+ buffer_append_string(b, "/");
+ buffer_append_off_t(b, sce->st.st_size);
+
+ buffer_append_string(b, "\r\nContent-Type: ");
+ buffer_append_string_buffer(b, content_type);
+
+ /* write END-OF-HEADER */
+ buffer_append_string(b, "\r\n\r\n");
+
+ con->response.content_length += b->used - 1;
+
+ }
+
+ /* path = con->physical.path->ptr + p->conf.prefix->used - 1 + con->physical.doc_root->used - 1; */
+ /*
+ fd = glusterfs_open (p->conf.handle, path, O_RDONLY);
+ if (fd < 0)
+ return HANDLER_ERROR;
+ */
+ /* fn = buffer_init_string (path); */
+ if ((size_t)sce->st.st_size > size) {
+ chunkqueue_append_glusterfs_file(con, ctx->fd, start, end - start + 1);
+ } else {
+ if (!start) {
+ buffer *mem = buffer_init ();
+ mem->ptr = ctx->buf;
+ mem->used = mem->size = sce->st.st_size + 1;
+ http_chunk_append_buffer (srv, con, mem);
+ ctx->buf = NULL;
+ } else {
+ chunkqueue_append_mem (con->write_queue, ((char *)ctx->buf) + start, end - start + 1);
+ }
+ }
+
+ con->response.content_length += end - start + 1;
+ }
+ }
+
+ if (ctx->buf) {
+ free (ctx->buf);
+ ctx->buf = NULL;
+ }
+
+ /* something went wrong */
+ if (error) return -1;
+
+ if (multipart) {
+ /* add boundary end */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string_len(b, "\r\n--", 4);
+ buffer_append_string(b, boundary);
+ buffer_append_string_len(b, "--\r\n", 4);
+
+ con->response.content_length += b->used - 1;
+
+ /* set header-fields */
+
+ buffer_copy_string(p->range_buf, "multipart/byteranges; boundary=");
+ buffer_append_string(p->range_buf, boundary);
+
+ /* overwrite content-type */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
+ } else {
+ /* add Content-Range-header */
+
+ buffer_copy_string(p->range_buf, "bytes ");
+ buffer_append_off_t(p->range_buf, start);
+ buffer_append_string(p->range_buf, "-");
+ buffer_append_off_t(p->range_buf, end);
+ buffer_append_string(p->range_buf, "/");
+ buffer_append_off_t(p->range_buf, sce->st.st_size);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
+ }
+
+ /* ok, the file is set-up */
+ return 0;
+}
+
+PHYSICALPATH_FUNC(mod_glusterfs_handle_physical) {
+ plugin_data *p = p_d;
+ stat_cache_entry *sce;
+ mod_glusterfs_ctx_t *plugin_ctx = NULL;
+ size_t size = 0;
+
+ if (con->http_status != 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+
+ /*
+ network_backend_write = srv->network_backend_write;
+ srv->network_backend_write = mod_glusterfs_network_backend_write;
+ */
+
+ switch (con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ break;
+
+ default:
+ return HANDLER_GO_ON;
+ }
+
+ mod_glusterfs_patch_connection(srv, con, p);
+ if (!p->conf.prefix || p->conf.prefix->used == 0) {
+ return HANDLER_GO_ON;
+ }
+
+ if (!p->conf.document_root || p->conf.document_root->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "glusterfs.document-root is not specified");
+ con->http_status = 500;
+ return HANDLER_FINISHED;
+ }
+
+ if (p->conf.handle <= 0) {
+ glusterfs_init_ctx_t ctx;
+
+ if (!p->conf.specfile || p->conf.specfile->used == 0) {
+ return HANDLER_GO_ON;
+ }
+ memset (&ctx, 0, sizeof (ctx));
+
+ ctx.specfile = p->conf.specfile->ptr;
+ ctx.logfile = p->conf.logfile->ptr;
+ ctx.loglevel = p->conf.loglevel->ptr;
+ ctx.lookup_timeout = ctx.stat_timeout = p->conf.cache_timeout;
+
+ p->conf.handle = (long)glusterfs_init (&ctx);
+
+ if (p->conf.handle <= 0) {
+ con->http_status = 500;
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "glusterfs initialization failed, please check your configuration. Glusterfs logfile ", p->conf.logfile, "might contain details");
+ return HANDLER_FINISHED;
+ }
+ }
+
+ size = 0;
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if (!con->plugin_ctx[p->id]) {
+/* FIXME: what if multiple files are requested from a single connection? */
+/* TODO: check whether this works fine for HTTP protocol 1.1 */
+
+ buffer *tmp_buf = buffer_init_buffer (con->physical.basedir);
+
+ plugin_ctx = calloc (1, sizeof (*plugin_ctx));
+ /* ERR_ABORT (plugin_ctx); */
+ con->plugin_ctx[p->id] = plugin_ctx;
+
+ buffer_append_string_buffer (tmp_buf, p->conf.prefix);
+ buffer_path_simplify (tmp_buf, tmp_buf);
+
+ plugin_ctx->prefix = tmp_buf->used - 1;
+ if (tmp_buf->ptr[plugin_ctx->prefix - 1] == '/')
+ plugin_ctx->prefix--;
+
+ buffer_free (tmp_buf);
+ } else
+ /*FIXME: error!! error!! */
+ plugin_ctx = con->plugin_ctx[p->id];
+
+
+ if (size)
+ {
+ plugin_ctx->buf = MALLOC (size);
+ /* ERR_ABORT (plugin_ctx->buf); */
+ }
+
+ plugin_ctx->glusterfs_path = buffer_init ();
+ buffer_copy_string_buffer (plugin_ctx->glusterfs_path, p->conf.document_root);
+ buffer_append_string (plugin_ctx->glusterfs_path, "/");
+ buffer_append_string (plugin_ctx->glusterfs_path, con->physical.path->ptr + plugin_ctx->prefix);
+ buffer_path_simplify (plugin_ctx->glusterfs_path, plugin_ctx->glusterfs_path);
+
+ if (glusterfs_stat_cache_get_entry (srv, con, (libglusterfs_handle_t )p->conf.handle, plugin_ctx->glusterfs_path, con->physical.path, plugin_ctx->buf, size, &sce) == HANDLER_ERROR) {
+ if (errno == ENOENT)
+ con->http_status = 404;
+ else
+ con->http_status = 403;
+
+ free (plugin_ctx->buf);
+ buffer_free (plugin_ctx->glusterfs_path);
+ plugin_ctx->glusterfs_path = NULL;
+ plugin_ctx->buf = NULL;
+
+ free (plugin_ctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+ }
+
+ if (!(S_ISREG (sce->st.st_mode) && (size_t)sce->st.st_size <= size)) {
+ free (plugin_ctx->buf);
+ plugin_ctx->buf = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static int http_chunk_append_len(server *srv, connection *con, size_t len) {
+ size_t i, olen = len, j;
+ buffer *b;
+
+ b = srv->tmp_chunk_len;
+
+ if (len == 0) {
+ buffer_copy_string(b, "0");
+ } else {
+ for (i = 0; i < 8 && len; i++) {
+ len >>= 4;
+ }
+
+ /* i is the number of hex digits we have */
+ buffer_prepare_copy(b, i + 1);
+
+ for (j = i-1, len = olen; j+1 > 0; j--) {
+ b->ptr[j] = (len & 0xf) + (((len & 0xf) <= 9) ? '0' : 'a' - 10);
+ len >>= 4;
+ }
+ b->used = i;
+ b->ptr[b->used++] = '\0';
+ }
+
+ buffer_append_string(b, "\r\n");
+ chunkqueue_append_buffer(con->write_queue, b);
+
+ return 0;
+}
+
+int http_chunk_append_glusterfs_file_chunk(server *srv, connection *con, long fd, off_t offset, off_t len) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len);
+ }
+
+ chunkqueue_append_glusterfs_file (con, fd, offset, len);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+int http_chunk_append_glusterfs_mem(server *srv, connection *con, const char * mem, size_t len) {
+ chunkqueue *cq = NULL;
+ buffer *buf = NULL;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (len == 0) {
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ chunkqueue_append_mem(cq, "0\r\n\r\n", 5 + 1);
+ } else {
+ chunkqueue_append_mem(cq, "", 1);
+ }
+ return 0;
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len - 1);
+ }
+
+ buf = buffer_init ();
+
+ buf->used = len + 1;
+ buf->ptr = (char *)mem;
+ chunkqueue_append_buffer_weak (cq, buf);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+
+
+URIHANDLER_FUNC(mod_glusterfs_subrequest) {
+ plugin_data *p = p_d;
+ stat_cache_entry *sce = NULL;
+ int s_len;
+ char allow_caching = 1;
+ size_t size = 0;
+ mod_glusterfs_ctx_t *ctx = con->plugin_ctx[p->id];
+
+ /* someone else has done a decision for us */
+ if (con->http_status != 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ /* someone else has handled this request */
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+
+ /* we only handle GET, POST and HEAD */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ break;
+ default:
+ return HANDLER_GO_ON;
+ }
+
+ mod_glusterfs_patch_connection(srv, con, p);
+
+ if (!p->conf.prefix || !p->conf.prefix->used)
+ return HANDLER_GO_ON;
+
+ s_len = con->uri.path->used - 1;
+ /* ignore certain extensions */
+ /*
+ for (k = 0; k < p->conf.exclude_exts->used; k++) {
+ data_string *ds;
+ ds = (data_string *)p->conf.exclude_exts->data[k];
+
+ if (ds->value->used == 0) continue;
+
+ if (!strncmp (ds->value->ptr, con->uri.path->ptr, strlen (ds->value->ptr)))
+ break;
+ }
+
+ if (k == p->conf.exclude_exts->used) {
+ return HANDLER_GO_ON;
+ }
+ */
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- serving file from glusterfs");
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ con->http_status = 403;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", con->physical.path);
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+ }
+
+ if (con->uri.path->ptr[s_len] == '/' || !S_ISREG(sce->st.st_mode)) {
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if ((size_t)sce->st.st_size > size) {
+ ctx->fd = glusterfs_open ((libglusterfs_handle_t ) ((unsigned long)p->conf.handle), ctx->glusterfs_path->ptr, O_RDONLY, 0);
+
+ if (((long)ctx->fd) == 0) {
+ con->http_status = 403;
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+ }
+
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+
+ /* we only handline regular files */
+#ifdef HAVE_LSTAT
+ if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
+ con->http_status = 403;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+#endif
+ if (!S_ISREG(sce->st.st_mode)) {
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", sce->name);
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+ }
+
+ /* mod_compress might set several data directly, don't overwrite them */
+
+ /* set response content-type, if not set already */
+
+ if (NULL == array_get_element(con->response.headers, "Content-Type")) {
+ if (buffer_is_empty(sce->content_type)) {
+ /* we are setting application/octet-stream, but also announce that
+ * this header field might change in the seconds few requests
+ *
+ * This should fix the aggressive caching of FF and the script download
+ * seen by the first installations
+ */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
+
+ allow_caching = 0;
+ } else {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+ }
+ }
+
+ if (con->conf.range_requests) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
+ }
+
+ /* TODO: Allow Cachable requests */
+#if 0
+ if (allow_caching) {
+ if (p->conf.etags_used && con->etag_flags != 0 && !buffer_is_empty(sce->etag)) {
+ if (NULL == array_get_element(con->response.headers, "ETag")) {
+ /* generate e-tag */
+ etag_mutate(con->physical.etag, sce->etag);
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
+ }
+ }
+
+ /* prepare header */
+ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+ } else {
+ mtime = ds->value;
+ }
+
+ if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+ }
+#endif
+
+ /*TODO: Read about etags */
+ if (con->request.http_range && con->conf.range_requests) {
+ int do_range_request = 1;
+ data_string *ds = NULL;
+ buffer *mtime = NULL;
+ /* check if we have a conditional GET */
+
+ /* prepare header */
+ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+ } else {
+ mtime = ds->value;
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
+ /* if the value is the same as our ETag, we do a Range-request,
+ * otherwise a full 200 */
+
+ if (ds->value->ptr[0] == '"') {
+ /**
+ * client wants a ETag
+ */
+ if (!con->physical.etag) {
+ do_range_request = 0;
+ } else if (!buffer_is_equal(ds->value, con->physical.etag)) {
+ do_range_request = 0;
+ }
+ } else if (!mtime) {
+ /**
+ * we don't have a Last-Modified and can match the If-Range:
+ *
+ * sending all
+ */
+ do_range_request = 0;
+ } else if (!buffer_is_equal(ds->value, mtime)) {
+ do_range_request = 0;
+ }
+ }
+
+ if (do_range_request) {
+ /* content prepared, I'm done */
+ con->file_finished = 1;
+
+ if (0 == http_response_parse_range(srv, con, p)) {
+ con->http_status = 206;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* if we are still here, prepare body */
+
+ /* we add it here for all requests
+ * the HEAD request will drop it afterwards again
+ */
+ /*TODO check whether 1 should be subtracted */
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if (size < (size_t)sce->st.st_size) {
+ http_chunk_append_glusterfs_file_chunk (srv, con, ctx->fd, 0, sce->st.st_size);
+ } else {
+ http_chunk_append_glusterfs_mem (srv, con, ctx->buf, sce->st.st_size);
+ }
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+}
+
+#if 0
+URIHANDLER_FUNC(mod_glusterfs_request_done)
+{
+ mod_glusterfs_read_buf_t *cur = first, *prev;
+ while (cur) {
+ prev = cur;
+ glusterfs_free (cur->buf);
+ cur = cur->next;
+ free (prev);
+ }
+ first = NULL
+ }
+#endif
+
+/* this function is called at dlopen() time and inits the callbacks */
+CONNECTION_FUNC(mod_glusterfs_connection_reset)
+{
+ (void) p_d;
+ (void) con;
+ if (!network_backend_write)
+ network_backend_write = srv->network_backend_write;
+
+ srv->network_backend_write = mod_glusterfs_network_backend_write;
+
+ return HANDLER_GO_ON;
+}
+
+int mod_glusterfs_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("glusterfs");
+ p->init = mod_glusterfs_init;
+ p->handle_physical = mod_glusterfs_handle_physical;
+ p->handle_subrequest_start = mod_glusterfs_subrequest;
+ // p->handle_request_done = mod_glusterfs_request_done;
+ p->set_defaults = mod_glusterfs_set_defaults;
+ p->connection_reset = mod_glusterfs_connection_reset;
+ p->cleanup = mod_glusterfs_free;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+
+/* mod_glusterfs_stat_cache */
+static stat_cache_entry * stat_cache_entry_init(void) {
+ stat_cache_entry *sce = NULL;
+
+ sce = calloc(1, sizeof(*sce));
+ /* ERR_ABORT (sce); */
+
+ sce->name = buffer_init();
+ sce->etag = buffer_init();
+ sce->content_type = buffer_init();
+
+ return sce;
+}
+
+#ifdef HAVE_FAM_H
+static fam_dir_entry * fam_dir_entry_init(void) {
+ fam_dir_entry *fam_dir = NULL;
+
+ fam_dir = calloc(1, sizeof(*fam_dir));
+ /* ERR_ABORT (fam_dir); */
+
+ fam_dir->name = buffer_init();
+
+ return fam_dir;
+}
+
+static void fam_dir_entry_free(void *data) {
+ fam_dir_entry *fam_dir = data;
+
+ if (!fam_dir) return;
+
+ FAMCancelMonitor(fam_dir->fc, fam_dir->req);
+
+ buffer_free(fam_dir->name);
+ free(fam_dir->req);
+
+ free(fam_dir);
+}
+#endif
+
+#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
+
+/* the famous DJB hash function for strings */
+static uint32_t hashme(buffer *str) {
+ uint32_t hash = 5381;
+ const char *s;
+ for (s = str->ptr; *s; s++) {
+ hash = ((hash << 5) + hash) + *s;
+ }
+
+ hash &= ~(1 << 31); /* strip the highest bit */
+
+ return hash;
+}
+
+
+#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
+
+/***
+ *
+ *
+ *
+ * returns:
+ * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
+ * - HANDLER_ERROR on stat() failed -> see errno for problem
+ */
+
+handler_t glusterfs_stat_cache_get_entry(server *srv,
+ connection *con,
+ libglusterfs_handle_t handle,
+ buffer *glusterfs_path,
+ buffer *name,
+ void *buf,
+ size_t size,
+ stat_cache_entry **ret_sce)
+{
+#ifdef HAVE_FAM_H
+ fam_dir_entry *fam_dir = NULL;
+ int dir_ndx = -1;
+ splay_tree *dir_node = NULL;
+#endif
+ stat_cache_entry *sce = NULL;
+ stat_cache *sc;
+ struct stat st;
+ size_t k;
+#ifdef DEBUG_STAT_CACHE
+ size_t i;
+#endif
+ int file_ndx;
+ splay_tree *file_node = NULL;
+
+ *ret_sce = NULL;
+ memset (&st, 0, sizeof (st));
+
+ /*
+ * 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);
+
+ file_ndx = hashme(sc->hash_key);
+ sc->files = splaytree_splay(sc->files, file_ndx);
+
+#ifdef DEBUG_STAT_CACHE
+ for (i = 0; i < ctrl.used; i++) {
+ if (ctrl.ptr[i] == file_ndx) break;
+ }
+#endif
+
+ if (sc->files && (sc->files->key == file_ndx)) {
+#ifdef DEBUG_STAT_CACHE
+ /* it was in the cache */
+ assert(i < ctrl.used);
+#endif
+
+ /* we have seen this file already and
+ * don't stat() it again in the same second */
+
+ file_node = sc->files;
+
+ sce = file_node->data;
+
+ /* check if the name is the same, we might have a collision */
+
+ if (buffer_is_equal(name, sce->name)) {
+ if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
+ if (sce->stat_ts == srv->cur_ts && !buf) {
+ *ret_sce = sce;
+ return HANDLER_GO_ON;
+ }
+ }
+ } else {
+ /* oops, a collision,
+ *
+ * file_node is used by the FAM check below to see if we know this file
+ * and if we can save a stat().
+ *
+ * BUT, the sce is not reset here as the entry into the cache is ok, we
+ * it is just not pointing to our requested file.
+ *
+ * */
+
+ file_node = NULL;
+ }
+ } else {
+#ifdef DEBUG_STAT_CACHE
+ if (i != ctrl.used) {
+ fprintf(stderr, "%s.%d: %08x was already inserted but not found in cache, %s\n", __FILE__, __LINE__, file_ndx, name->ptr);
+ }
+ assert(i == ctrl.used);
+#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
+ *
+ * */
+ if (-1 == glusterfs_lookup(handle, glusterfs_path->ptr, buf, size, &st)) {
+ return HANDLER_ERROR;
+ }
+
+ if (NULL == sce) {
+ int osize = 0;
+
+ if (sc->files) {
+ osize = sc->files->size;
+ }
+
+ sce = stat_cache_entry_init();
+ buffer_copy_string_buffer(sce->name, name);
+
+ sc->files = splaytree_insert(sc->files, file_ndx, sce);
+#ifdef DEBUG_STAT_CACHE
+ if (ctrl.size == 0) {
+ ctrl.size = 16;
+ ctrl.used = 0;
+ ctrl.ptr = MALLOC(ctrl.size * sizeof(*ctrl.ptr));
+ /* ERR_ABORT (ctrl.ptr); */
+ } else if (ctrl.size == ctrl.used) {
+ ctrl.size += 16;
+ ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr));
+ /* ERR_ABORT (ctrl.ptr); */
+ }
+
+ ctrl.ptr[ctrl.used++] = file_ndx;
+
+ assert(sc->files);
+ assert(sc->files->data == sce);
+ assert(osize + 1 == splaytree_size(sc->files));
+#endif
+ }
+
+ sce->st = st;
+ sce->stat_ts = srv->cur_ts;
+
+ /* 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
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "found symlink", 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);
+
+ 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);
+#ifdef HAVE_XATTR
+ if (con->conf.use_xattr && buffer_is_empty(sce->content_type)) {
+ stat_cache_attr_get(sce->content_type, name->ptr);
+ }
+#endif
+ } else if (S_ISDIR(st.st_mode)) {
+ etag_create(sce->etag, &(sce->st), con->etag_flags);
+ }
+
+#ifdef HAVE_FAM_H
+ if (sc->fam &&
+ (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM)) {
+ /* is this directory already registered ? */
+ if (!dir_node) {
+ fam_dir = fam_dir_entry_init();
+ fam_dir->fc = sc->fam;
+
+ buffer_copy_string_buffer(fam_dir->name, sc->dir_name);
+
+ fam_dir->version = 1;
+
+ fam_dir->req = calloc(1, sizeof(FAMRequest));
+ /* ERR_ABORT (fam_dir->req); */
+
+ if (0 != FAMMonitorDirectory(sc->fam, fam_dir->name->ptr,
+ fam_dir->req, fam_dir)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsbs",
+ "monitoring dir failed:",
+ fam_dir->name,
+ "file:", name,
+ FamErrlist[FAMErrno]);
+
+ fam_dir_entry_free(fam_dir);
+ } else {
+ int osize = 0;
+
+ if (sc->dirs) {
+ osize = sc->dirs->size;
+ }
+
+ sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir);
+ assert(sc->dirs);
+ assert(sc->dirs->data == fam_dir);
+ assert(osize == (sc->dirs->size - 1));
+ }
+ } else {
+ fam_dir = dir_node->data;
+ }
+
+ /* bind the fam_fc to the stat() cache entry */
+
+ if (fam_dir) {
+ sce->dir_version = fam_dir->version;
+ sce->dir_ndx = dir_ndx;
+ }
+ }
+#endif
+
+ *ret_sce = sce;
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * 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
+ */
+
+static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
+ stat_cache_entry *sce;
+
+ if (!t) return 0;
+
+ stat_cache_tag_old_entries(srv, t->left, keys, ndx);
+ stat_cache_tag_old_entries(srv, t->right, keys, ndx);
+
+ sce = t->data;
+
+ if (srv->cur_ts - sce->stat_ts > 2) {
+ keys[(*ndx)++] = t->key;
+ }
+
+ return 0;
+}
diff --git a/mod_glusterfs/lighttpd/1.4/mod_glusterfs.h b/mod_glusterfs/lighttpd/1.4/mod_glusterfs.h
new file mode 100644
index 000000000..5dc65cbcf
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.4/mod_glusterfs.h
@@ -0,0 +1,29 @@
+/*
+ Copyright (c) 2008 Z RESEARCH, Inc. <http://www.zresearch.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MOD_GLUSTERFS_FILE_CACHE_H_
+#define _MOD_GLUSTERFS_FILE_CACHE_H_
+
+#include "stat_cache.h"
+#include <libglusterfsclient.h>
+#include "base.h"
+
+handler_t glusterfs_stat_cache_get_entry(server *srv, connection *con, libglusterfs_handle_t handle, buffer *glusterfs_path, buffer *name, void *buf, size_t size, stat_cache_entry **fce);
+
+#endif
diff --git a/mod_glusterfs/lighttpd/1.5/Makefile.am b/mod_glusterfs/lighttpd/1.5/Makefile.am
new file mode 100644
index 000000000..eda329111
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.5/Makefile.am
@@ -0,0 +1,3 @@
+EXTRA_DIST = Makefile.am.diff mod_glusterfs.c mod_glusterfs.h README.txt
+
+CLEANFILES =
diff --git a/mod_glusterfs/lighttpd/1.5/Makefile.am.diff b/mod_glusterfs/lighttpd/1.5/Makefile.am.diff
new file mode 100644
index 000000000..375696b5d
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.5/Makefile.am.diff
@@ -0,0 +1,29 @@
+--- lighttpd-1.4.19/src/Makefile.am 2008-04-16 18:42:18.000000000 +0400
++++ lighttpd-1.4.19.mod/src/Makefile.am 2008-04-16 18:41:11.000000000 +0400
+@@ -1,4 +1,4 @@
+-AM_CFLAGS = $(FAM_CFLAGS)
++AM_CFLAGS = $(FAM_CFLAGS) -D_FILE_OFFSET_BITS=64 -D__USE_FILE_OFFSET64
+
+ noinst_PROGRAMS=proc_open lemon # simple-fcgi #graphic evalo bench ajp ssl error_test adserver gen-license
+ sbin_PROGRAMS=lighttpd lighttpd-angel
+@@ -241,6 +241,11 @@
+ mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+ mod_accesslog_la_LIBADD = $(common_libadd)
+
++lib_LTLIBRARIES += mod_glusterfs.la
++mod_glusterfs_la_SOURCES = mod_glusterfs.c
++mod_glusterfs_la_CFLAGS = $(AM_CFLAGS)
++mod_glusterfs_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined -lglusterfsclient -lpthread
++mod_glusterfs_la_LIBADD = $(common_libadd)
+
+ hdr = server.h buffer.h network.h log.h keyvalue.h \
+ response.h request.h fastcgi.h chunk.h \
+@@ -254,7 +259,7 @@
+ configparser.h mod_ssi_exprparser.h \
+ sys-mmap.h sys-socket.h mod_cml.h mod_cml_funcs.h \
+ splaytree.h proc_open.h status_counter.h \
+- mod_magnet_cache.h
++ mod_magnet_cache.h mod_glusterfs.h
+
+ DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\"" -DSBIN_DIR="\"$(sbindir)\""
+
diff --git a/mod_glusterfs/lighttpd/1.5/README.txt b/mod_glusterfs/lighttpd/1.5/README.txt
new file mode 100644
index 000000000..bdbdfffbc
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.5/README.txt
@@ -0,0 +1,57 @@
+Introduction
+============
+mod_glusterfs is a module written for lighttpd to speed up the access of files present on glusterfs. mod_glusterfs uses libglusterfsclient library provided for glusterfs and hence can be used without fuse (File System in User Space).
+
+Usage
+=====
+To use mod_glusterfs with lighttpd-1.5, copy mod_glusterfs.c and mod_glusterfs.h into src/ of lighttpd-1.5 source tree, and apply the Makefile.am.diff to src/Makefile.am. Re-run ./autogen.sh on the top level of the lighttpd-1.5 build tree and recompile.
+
+# cp mod_glusterfs.[ch] /home/glusterfs/lighttpd-1.5/src/
+# cp Makefile.am.diff /home/glusterfs/lighttpd-1.5/
+# cd /home/glusterfs/lighttpd-1.5
+# patch -p1 < Makefile.am.diff
+# ./autogen.sh
+# ./configure
+# make
+# make install
+
+Configuration
+=============
+* mod_glusterfs should be listed at the begining of the list server.modules in lighttpd.conf.
+
+Below is a snippet from lighttpd.conf concerning to mod_glusterfs.
+
+$HTTP["url"] =~ "^/glusterfs" {
+ glusterfs.prefix = "/glusterfs"
+ glusterfs.logfile = "/var/log/glusterfs-logfile"
+ glusterfs.document-root = "/home/glusterfs/document-root"
+ glusterfs.volume-specfile = "/etc/glusterfs/glusterfs.vol"
+ glusterfs.loglevel = "error"
+ glusterfs.cache-timeout = 300
+ glusterfs.xattr-interface-size-limit = "65536"
+}
+
+* $HTTP["url"] =~ "^/glusterfs"
+ A perl style regular expression used to match against the url. If regular expression matches the url, the url is handled by mod_glusterfs. Note that the pattern given here should match glusterfs.prefix.
+
+* glusterfs.prefix (COMPULSORY)
+ A string to be present at the starting of the file path in the url so that the file would be handled by glusterfs.
+ Eg., A GET request on the url http://www.example.com/glusterfs-prefix/some-dir/example-file will result in fetching of the file "/some-dir/example-file" from glusterfs mount if glusterfs.prefix is set to "/glusterfs-prefix".
+
+* glusterfs.volume-specfile (COMPULSORY)
+ Path to the the glusterfs volume specification file.
+
+* glusterfs.logfile (COMPULSORY)
+ Path to the glusterfs logfile.
+
+* glusterfs.loglevel (OPTIONAL, default = warning)
+ Allowed values are critical, error, warning, debug, none in the decreasing order of severity of error conditions.
+
+* glusterfs.cache-timeout (OPTIONAL, default = 0)
+ Timeout values for glusterfs stat and lookup cache.
+
+* glusterfs.document-root (COMPULSORY)
+ An absolute path, relative to which all the files are fetched from glusterfs.
+
+* glusterfs.xattr-interface-size-limit (OPTIONAL, default = 0)
+ Files with sizes upto and including this value are fetched through the extended attribute interface of glusterfs rather than the usual open-read-close set of operations. For files of small sizes, it is recommended to use extended attribute interface.
diff --git a/mod_glusterfs/lighttpd/1.5/mod_glusterfs.c b/mod_glusterfs/lighttpd/1.5/mod_glusterfs.c
new file mode 100644
index 000000000..4329640e2
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.5/mod_glusterfs.c
@@ -0,0 +1,1476 @@
+/*
+ Copyright (c) 2008 Z RESEARCH, Inc. <http://www.zresearch.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "stat_cache.h"
+#include "mod_glusterfs.h"
+#include "etag.h"
+#include "response.h"
+
+#include "fdevent.h"
+#include "joblist.h"
+#include "http_req_range.h"
+#include "connections.h"
+#include "configfile.h"
+
+#include <libglusterfsclient.h>
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+#ifdef HAVE_FAM_H
+# include <fam.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
+
+#if 0
+/* enables debug code for testing if all nodes in the stat-cache as accessable */
+#define DEBUG_STAT_CACHE
+#endif
+
+#ifdef HAVE_LSTAT
+#undef HAVE_LSTAT
+#endif
+
+#define GLUSTERFS_FILE_CHUNK (FILE_CHUNK + 1)
+
+/* Keep this value large. Each glusterfs_async_read of GLUSTERFS_CHUNK_SIZE results in a network_backend_write of the read data*/
+
+#define GLUSTERFS_CHUNK_SIZE 8192
+
+/**
+ * this is a staticfile for a lighttpd plugin
+ *
+ */
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *logfile;
+ buffer *loglevel;
+ buffer *specfile;
+ buffer *prefix;
+ buffer *xattr_file_size;
+ buffer *document_root;
+ array *exclude_exts;
+ unsigned short cache_timeout;
+
+ /* FIXME: its a pointer, hence cant be short */
+ unsigned long handle;
+} plugin_config;
+
+static network_status_t (*network_backend_write)(struct server *srv, connection *con, iosocket *sock, chunkqueue *cq);
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *range_buf;
+ plugin_config **config_storage;
+ http_req_range *ranges;
+ plugin_config conf;
+} plugin_data;
+
+typedef struct glusterfs_async_local {
+ int op_ret;
+ int op_errno;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ connection *con;
+ server *srv;
+ plugin_data *p;
+
+ union {
+ struct {
+ char async_read_complete;
+ off_t length;
+ size_t read_bytes;
+ glusterfs_read_buf_t *buf;
+ }readv;
+
+ struct {
+ buffer *name;
+ buffer *hash_key;
+ size_t size;
+ }lookup;
+ }fop;
+} glusterfs_async_local_t;
+
+typedef struct {
+ unsigned long fd;
+ buffer *glusterfs_path;
+ void *buf;
+ off_t response_content_length;
+ int prefix;
+}mod_glusterfs_ctx_t;
+
+typedef struct {
+ chunkqueue *cq;
+ glusterfs_read_buf_t *buf;
+ size_t length;
+}mod_glusterfs_chunkqueue;
+
+#ifdef HAVE_FAM_H
+typedef struct {
+ FAMRequest *req;
+ FAMConnection *fc;
+
+ buffer *name;
+
+ int version;
+} fam_dir_entry;
+#endif
+
+/* 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 DEBUG_STAT_CACHE
+typedef struct {
+ int *ptr;
+
+ size_t used;
+ size_t size;
+} fake_keys;
+
+static fake_keys ctrl;
+#endif
+
+static stat_cache_entry *
+stat_cache_entry_init(void)
+{
+ stat_cache_entry *sce = NULL;
+
+ sce = calloc(1, sizeof(*sce));
+ /* ERR_ABORT (sce); */
+
+ sce->name = buffer_init();
+ sce->etag = buffer_init();
+ sce->content_type = buffer_init();
+
+ return sce;
+}
+
+int chunkqueue_append_glusterfs_mem (chunkqueue *cq, const char * mem, size_t len) {
+ buffer *buf = NULL;
+
+ buf = chunkqueue_get_append_buffer (cq);
+
+ if (buf->ptr)
+ free (buf->ptr);
+
+ buf->used = len + 1;
+ buf->ptr = (char *)mem;
+ buf->size = len;
+
+ return 0;
+}
+
+static int
+glusterfs_lookup_async_cbk (int op_ret,
+ int op_errno,
+ void *buf,
+ struct stat *st,
+ void *cbk_data)
+{
+ glusterfs_async_local_t *local = cbk_data;
+
+ mod_glusterfs_ctx_t *ctx = NULL;
+ ctx = local->con->plugin_ctx[local->p->id];
+
+ assert (ctx->buf== buf);
+
+ if (op_ret || !(S_ISREG (st->st_mode) && (size_t)st->st_size <= local->fop.lookup.size)) {
+
+ free (ctx->buf);
+ ctx->buf = NULL;
+
+ if (op_ret) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ free (ctx);
+ local->con->plugin_ctx[local->p->id] = NULL;
+
+ if (op_errno == ENOENT)
+ local->con->http_status = 404;
+ else
+ local->con->http_status = 403;
+ }
+ }
+
+ if (!op_ret) {
+ stat_cache_entry *sce = NULL;
+ stat_cache *sc = local->srv->stat_cache;
+
+ sce = (stat_cache_entry *)g_hash_table_lookup(sc->files, local->fop.lookup.hash_key);
+
+ if (!sce) {
+ sce = stat_cache_entry_init();
+
+ buffer_copy_string_buffer(sce->name, local->fop.lookup.name);
+ g_hash_table_insert(sc->files, buffer_init_string(BUF_STR(local->fop.lookup.hash_key)), sce);
+ }
+
+ sce->state = STAT_CACHE_ENTRY_STAT_FINISHED;
+ sce->stat_ts = time (NULL);
+ memcpy (&sce->st, st, sizeof (*st));
+ }
+
+ g_async_queue_push (local->srv->joblist_queue, local->con);
+ /*
+ joblist_append (local->srv, local->con);
+ kill (getpid(), SIGUSR1);
+ */
+ free (local);
+ return 0;
+}
+
+static handler_t
+glusterfs_stat_cache_get_entry_async (server *srv,
+ connection *con,
+ plugin_data *p,
+ buffer *glusterfs_path,
+ buffer *name,
+ void *buf,
+ size_t size,
+ stat_cache_entry **ret_sce)
+{
+ stat_cache_entry *sce = NULL;
+ stat_cache *sc;
+ glusterfs_async_local_t *local = NULL;
+
+ *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);
+
+ 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 &&
+ !buf) {
+ /* verify that this entry is still fresh */
+
+ *ret_sce = sce;
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+
+ /*
+ * *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 */
+
+ local = calloc (1, sizeof (*local));
+ /* ERR_ABORT (local); */
+ local->con = con;
+ local->srv = srv;
+ local->p = p;
+ local->fop.lookup.name = buffer_init_buffer (name);
+ local->fop.lookup.hash_key = buffer_init_buffer (sc->hash_key);
+ local->fop.lookup.size = size;
+
+ if (glusterfs_lookup_async ((libglusterfs_handle_t )p->conf.handle, glusterfs_path->ptr, buf, size, glusterfs_lookup_async_cbk, (void *) local)) {
+ free (local);
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+int
+mod_glusterfs_readv_async_cbk (glusterfs_read_buf_t *buf,
+ void *cbk_data)
+{
+ glusterfs_async_local_t *local = cbk_data;
+ pthread_mutex_lock (&local->lock);
+ {
+ local->fop.readv.async_read_complete = 1;
+ local->fop.readv.buf = buf;
+
+ pthread_cond_signal (&local->cond);
+ }
+ pthread_mutex_unlock (&local->lock);
+
+ return 0;
+}
+
+network_status_t
+mod_glusterfs_read_async (server *srv, connection *con, chunk *glusterfs_chunk)
+{
+ glusterfs_async_local_t local;
+ off_t end = 0;
+ int nbytes;
+ int complete;
+ chunkqueue *cq = NULL;
+ chunk *c = NULL;
+ off_t offset = glusterfs_chunk->file.start;
+ size_t length = glusterfs_chunk->file.length;
+ unsigned long fd = (unsigned long)glusterfs_chunk->file.name;
+ network_status_t ret;
+
+ pthread_cond_init (&local.cond, NULL);
+ pthread_mutex_init (&local.lock, NULL);
+
+ //local.fd = fd;
+ memset (&local, 0, sizeof (local));
+
+ if (length > 0)
+ end = offset + length;
+
+ cq = chunkqueue_init ();
+ if (!cq) {
+ con->http_status = 500;
+ return NETWORK_STATUS_FATAL_ERROR;
+ }
+
+ do {
+ glusterfs_read_buf_t *buf;
+ int i;
+ if (length > 0) {
+ nbytes = end - offset;
+ if (nbytes > GLUSTERFS_CHUNK_SIZE)
+ nbytes = GLUSTERFS_CHUNK_SIZE;
+ } else
+ nbytes = GLUSTERFS_CHUNK_SIZE;
+
+ glusterfs_read_async(fd,
+ nbytes,
+ offset,
+ mod_glusterfs_readv_async_cbk,
+ (void *)&local);
+
+ pthread_mutex_lock (&local.lock);
+ {
+ while (!local.fop.readv.async_read_complete) {
+ pthread_cond_wait (&local.cond, &local.lock);
+ }
+
+ local.op_ret = local.fop.readv.buf->op_ret;
+ local.op_errno = local.fop.readv.buf->op_errno;
+
+ local.fop.readv.async_read_complete = 0;
+ buf = local.fop.readv.buf;
+
+ if ((int)length < 0)
+ complete = (local.fop.readv.buf->op_ret <= 0);
+ else {
+ local.fop.readv.read_bytes += local.fop.readv.buf->op_ret;
+ complete = ((local.fop.readv.read_bytes == length) || (local.fop.readv.buf->op_ret <= 0));
+ }
+ }
+ pthread_mutex_unlock (&local.lock);
+
+ if (local.op_ret > 0) {
+ for (i = 0; i < buf->count; i++) {
+ buffer *nw_write_buf = chunkqueue_get_append_buffer (cq);
+
+ nw_write_buf->used = nw_write_buf->size = buf->vector[i].iov_len + 1;
+ nw_write_buf->ptr = buf->vector[i].iov_base;
+
+ // buffer_copy_memory (nw_write_buf, buf->vector[i].iov_base, buf->vector[i].iov_len + 1);
+ offset += local.op_ret;
+ }
+
+ ret = network_backend_write (srv, con, con->sock, cq);
+
+ if (chunkqueue_written (cq) != local.op_ret) {
+ mod_glusterfs_chunkqueue *gf_cq;
+ glusterfs_chunk->file.start = offset;
+ if ((int)glusterfs_chunk->file.length > 0)
+ glusterfs_chunk->file.length -= local.fop.readv.read_bytes;
+
+ gf_cq = calloc (1, sizeof (*gf_cq));
+ /* ERR_ABORT (qf_cq); */
+ gf_cq->cq = cq;
+ gf_cq->buf = buf;
+ gf_cq->length = local.op_ret;
+ glusterfs_chunk->file.mmap.start = (char *)gf_cq;
+ return ret;
+ }
+
+ for (c = cq->first ; c; c = c->next)
+ c->mem->ptr = NULL;
+
+ chunkqueue_reset (cq);
+ }
+
+ glusterfs_free (buf);
+ } while (!complete);
+
+ chunkqueue_free (cq);
+ glusterfs_close (fd);
+
+ if (local.op_ret < 0)
+ con->http_status = 500;
+
+ return (local.op_ret < 0 ? NETWORK_STATUS_FATAL_ERROR : NETWORK_STATUS_SUCCESS);
+}
+
+network_status_t mod_glusterfs_network_backend_write(struct server *srv, connection *con, iosocket *sock, chunkqueue *cq)
+{
+ chunk *c, *prev, *first;
+ int chunks_written = 0;
+ int error = 0;
+ network_status_t ret;
+
+ for (first = prev = c = cq->first; c; c = c->next, chunks_written++) {
+
+ if (c->type == MEM_CHUNK && c->mem->used && !c->mem->ptr) {
+ if (cq->first != c) {
+ prev->next = NULL;
+
+ /* call stored network_backend_write */
+ ret = network_backend_write (srv, con, sock, cq);
+
+ prev->next = c;
+ if (ret != NETWORK_STATUS_SUCCESS) {
+ cq->first = first;
+ return ret;
+ }
+ }
+ cq->first = c->next;
+
+ if (c->file.fd < 0) {
+ error = HANDLER_ERROR;
+ break;
+ }
+
+ if (c->file.mmap.start) {
+ chunk *tmp;
+ size_t len;
+ mod_glusterfs_chunkqueue *gf_cq = (mod_glusterfs_chunkqueue *)c->file.mmap.start;
+
+ ret = network_backend_write (srv, con, sock, gf_cq->cq);
+
+ if ((len = (size_t)chunkqueue_written (gf_cq->cq)) != gf_cq->length) {
+ gf_cq->length -= len;
+ cq->first = first;
+ chunkqueue_remove_finished_chunks (gf_cq->cq);
+ return ret;
+ }
+
+ for (tmp = gf_cq->cq->first ; tmp; tmp = tmp->next)
+ tmp->mem->ptr = NULL;
+
+ chunkqueue_free (gf_cq->cq);
+ glusterfs_free (gf_cq->buf);
+ free (gf_cq);
+ c->file.mmap.start = NULL;
+ }
+
+ ret = mod_glusterfs_read_async (srv, con, c); //c->file.fd, c->file.start, -1);//c->file.length);
+ if (c->file.mmap.start) {
+ /* pending chunkqueue from mod_glusterfs_read_async to be written to network */
+ cq->first = first;
+ return ret;
+ }
+
+ buffer_free (c->mem);
+ c->mem = NULL;
+
+ c->type = FILE_CHUNK;
+ c->offset = c->file.length = 0;
+ c->file.name = NULL;
+
+ if (first == c)
+ first = c->next;
+
+ if (cq->last == c)
+ cq->last = NULL;
+
+ prev->next = c->next;
+
+ free(c);
+ }
+ prev = c;
+ }
+
+ ret = network_backend_write (srv, con, sock, cq);
+
+ cq->first = first;
+
+ return ret;
+}
+
+#if 0
+int chunkqueue_append_glusterfs_file (chunkqueue *cq, unsigned long fd, off_t offset, off_t len)
+{
+ chunk *c = NULL;
+ c = chunkqueue_get_append_tempfile (cq);
+
+ if (c->file.is_temp) {
+ close (c->file.fd);
+ unlink (c->file.name->ptr);
+ }
+
+ c->type = MEM_CHUNK;
+
+ c->mem = buffer_init ();
+ c->mem->used = len + 1;
+ c->mem->ptr = NULL;
+ c->offset = 0;
+
+ /* buffer_copy_string_buffer (c->file.name, fn); */
+ c->file.start = offset;
+ c->file.length = len;
+ /* buffer_free (c->file.name); */
+
+ /* identify chunk as glusterfs related */
+ c->file.mmap.start = MAP_FAILED;
+ /* c->file.mmap.length = c->file.mmap.offset = len;*/
+
+ return 0;
+}
+#endif
+
+int chunkqueue_append_dummy_mem_chunk (chunkqueue *cq, off_t len)
+{
+ chunk *c = NULL;
+ c = chunkqueue_get_append_tempfile (cq);
+
+ if (c->file.is_temp) {
+ close (c->file.fd);
+ unlink (c->file.name->ptr);
+ c->file.is_temp = 0;
+ }
+
+ c->type = MEM_CHUNK;
+
+ c->mem->used = len + 1;
+ c->offset = len;
+ c->mem->ptr = NULL;
+
+ return 0;
+}
+
+int chunkqueue_append_glusterfs_file (chunkqueue *cq, unsigned long fd, off_t offset, off_t len)
+{
+ chunk *c = NULL;
+ c = chunkqueue_get_append_tempfile (cq);
+
+ if (c->file.is_temp) {
+ close (c->file.fd);
+ unlink (c->file.name->ptr);
+ c->file.is_temp = 0;
+ }
+
+ c->type = MEM_CHUNK;
+
+ c->mem = buffer_init ();
+ c->mem->used = len + 1;
+ c->mem->ptr = NULL;
+ c->offset = 0;
+
+ /* buffer_copy_string_buffer (c->file.name, fn); */
+ buffer_free (c->file.name);
+
+ /* fd returned by libglusterfsclient is a pointer */
+ c->file.name = (buffer *)fd;
+ c->file.start = offset;
+ c->file.length = len;
+
+ //c->file.fd = fd;
+ c->file.mmap.start = NULL;
+ return 0;
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_glusterfs_init) {
+ plugin_data *p;
+
+ UNUSED (srv);
+ p = calloc(1, sizeof(*p));
+ /* ERR_ABORT (p); */
+ network_backend_write = NULL;
+ p->ranges = http_request_range_init();
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_glusterfs_free) {
+ plugin_data *p = p_d;
+
+ UNUSED (srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free (s->logfile);
+ buffer_free (s->loglevel);
+ buffer_free (s->specfile);
+ buffer_free (s->prefix);
+ buffer_free (s->xattr_file_size);
+ buffer_free (s->document_root);
+ array_free (s->exclude_exts);
+
+ free (s);
+ }
+ free (p->config_storage);
+ }
+ buffer_free (p->range_buf);
+ http_request_range_free (p->ranges);
+
+ free (p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_glusterfs_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "glusterfs.logfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.loglevel", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "glusterfs.volume-specfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "glusterfs.cache-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+
+ /*TODO: get the prefix from config_conext and remove glusterfs.prefix from conf file */
+ { "glusterfs.prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.xattr-interface-size-limit", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { "glusterfs.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+ /* ERR_ABORT (p->config_storage); */
+ p->range_buf = buffer_init ();
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ /* ERR_ABORT (s); */
+ s->logfile = buffer_init ();
+ s->loglevel = buffer_init ();
+ s->specfile = buffer_init ();
+ s->exclude_exts = array_init ();
+ s->prefix = buffer_init ();
+ s->xattr_file_size = buffer_init ();
+ s->document_root = buffer_init ();
+
+ cv[0].destination = s->logfile;
+ cv[1].destination = s->loglevel;
+ cv[2].destination = s->specfile;
+ cv[3].destination = &s->cache_timeout;
+ cv[4].destination = s->exclude_exts;
+ cv[5].destination = s->prefix;
+ cv[6].destination = s->xattr_file_size;
+ cv[7].destination = s->document_root;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_FINISHED;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+
+static int mod_glusterfs_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s;
+
+ p->conf.logfile = NULL;
+ p->conf.loglevel = NULL;
+ p->conf.specfile = NULL;
+ p->conf.cache_timeout = 0;
+ p->conf.exclude_exts = NULL;
+ p->conf.prefix = NULL;
+ p->conf.xattr_file_size = NULL;
+ p->conf.exclude_exts = NULL;
+
+ /* skip the first, the global context */
+ /* glusterfs related config can only occur inside $HTTP["url"] == "<glusterfs-prefix>" */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.logfile"))) {
+ PATCH (logfile);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.loglevel"))) {
+ PATCH (loglevel);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.volume-specfile"))) {
+ PATCH (specfile);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN("glusterfs.cache-timeout"))) {
+ PATCH (cache_timeout);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.exclude-extensions"))) {
+ PATCH (exclude_exts);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.prefix"))) {
+ PATCH (prefix);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.xattr-interface-size-limit"))) {
+ PATCH (xattr_file_size);
+ } else if (buffer_is_equal_string (du->key, CONST_STR_LEN ("glusterfs.document-root"))) {
+ PATCH (document_root);
+ }
+ }
+ }
+ return 0;
+}
+
+#undef PATCH
+
+static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
+ int multipart = 0;
+ char *boundary = "fkj49sn38dcn3";
+ data_string *ds;
+ stat_cache_entry *sce = NULL;
+ buffer *content_type = NULL;
+ buffer *range = NULL;
+ http_req_range *ranges, *r;
+ mod_glusterfs_ctx_t *ctx = con->plugin_ctx[p->id];
+ size_t size = 0;
+
+ if (!ctx) {
+ return -1;
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("Range")))) {
+ range = ds->value;
+ } else {
+ /* we don't have a Range header */
+
+ return -1;
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ SEGFAULT();
+ }
+
+ ctx->response_content_length = con->response.content_length = 0;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, CONST_STR_LEN("Content-Type")))) {
+ content_type = ds->value;
+ }
+
+ /* start the range-header parser
+ * bytes=<num> */
+
+ ranges = p->ranges;
+ http_request_range_reset(ranges);
+ switch (http_request_range_parse(range, ranges)) {
+ case PARSE_ERROR:
+ return -1; /* no range valid Range Header */
+ case PARSE_SUCCESS:
+ break;
+ default:
+ TRACE("%s", "foobar");
+ return -1;
+ }
+
+ if (ranges->next) {
+ multipart = 1;
+ }
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr) {
+ size = atoi (p->conf.xattr_file_size->ptr);
+ }
+
+ /* patch the '-1' */
+ for (r = ranges; r; r = r->next) {
+ if (r->start == -1) {
+ /* -<end>
+ *
+ * the last <end> bytes */
+ r->start = sce->st.st_size - r->end;
+ r->end = sce->st.st_size - 1;
+ }
+ if (r->end == -1) {
+ /* <start>-
+ * all but the first <start> bytes */
+
+ r->end = sce->st.st_size - 1;
+ }
+
+ if (r->end > sce->st.st_size - 1) {
+ /* RFC 2616 - 14.35.1
+ *
+ * if last-byte-pos not present or > size-of-file
+ * take the size-of-file
+ *
+ * */
+ r->end = sce->st.st_size - 1;
+ }
+
+ if (r->start > sce->st.st_size - 1) {
+ /* RFC 2616 - 14.35.1
+ *
+ * if first-byte-pos > file-size, 416
+ */
+
+ con->http_status = 416;
+ return -1;
+ }
+
+ if (r->start > r->end) {
+ /* RFC 2616 - 14.35.1
+ *
+ * if last-byte-pos is present, it has to be >= first-byte-pos
+ *
+ * invalid ranges have to be handle as no Range specified
+ * */
+
+ return -1;
+ }
+ }
+
+ if (r) {
+ /* we ran into an range violation */
+ return -1;
+ }
+
+ if (multipart) {
+ buffer *b;
+ for (r = ranges; r; r = r->next) {
+ /* write boundary-header */
+
+ b = chunkqueue_get_append_buffer(con->send);
+
+ buffer_copy_string(b, "\r\n--");
+ buffer_append_string(b, boundary);
+
+ /* write Content-Range */
+ buffer_append_string(b, "\r\nContent-Range: bytes ");
+ buffer_append_off_t(b, r->start);
+ buffer_append_string(b, "-");
+ buffer_append_off_t(b, r->end);
+ buffer_append_string(b, "/");
+ buffer_append_off_t(b, sce->st.st_size);
+
+ buffer_append_string(b, "\r\nContent-Type: ");
+ buffer_append_string_buffer(b, content_type);
+
+ /* write END-OF-HEADER */
+ buffer_append_string(b, "\r\n\r\n");
+
+ con->response.content_length += b->used - 1;
+ ctx->response_content_length += b->used - 1;
+ con->send->bytes_in += b->used - 1;
+
+ if ((size_t)sce->st.st_size > size) {
+ chunkqueue_append_glusterfs_file(con->send_raw, ctx->fd, r->start, r->end - r->start + 1);
+ con->send_raw->bytes_in += (r->end - r->start + 1);
+ chunkqueue_append_dummy_mem_chunk (con->send, r->end - r->start + 1);
+ } else {
+ chunkqueue_append_mem (con->send, ((char *)ctx->buf) + r->start, r->end - r->start + 1);
+ free (ctx->buf);
+ ctx->buf = NULL;
+ }
+
+ con->response.content_length += r->end - r->start + 1;
+ ctx->response_content_length += r->end - r->start + 1;
+ con->send->bytes_in += r->end - r->start + 1;
+ }
+
+ /* add boundary end */
+ b = chunkqueue_get_append_buffer(con->send);
+
+ buffer_copy_string_len(b, "\r\n--", 4);
+ buffer_append_string(b, boundary);
+ buffer_append_string_len(b, "--\r\n", 4);
+
+ con->response.content_length += b->used - 1;
+ ctx->response_content_length += b->used - 1;
+ con->send->bytes_in += b->used - 1;
+
+ /* set header-fields */
+
+ buffer_copy_string(p->range_buf, "multipart/byteranges; boundary=");
+ buffer_append_string(p->range_buf, boundary);
+
+ /* overwrite content-type */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
+
+ } else {
+ r = ranges;
+
+ chunkqueue_append_glusterfs_file(con->send_raw, ctx->fd, r->start, r->end - r->start + 1);
+ con->send_raw->bytes_in += (r->end - r->start + 1);
+ chunkqueue_append_dummy_mem_chunk (con->send, r->end - r->start + 1);
+ con->response.content_length += r->end - r->start + 1;
+ ctx->response_content_length += r->end - r->start + 1;
+ con->send->bytes_in += r->end - r->start + 1;
+
+ buffer_copy_string(p->range_buf, "bytes ");
+ buffer_append_off_t(p->range_buf, r->start);
+ buffer_append_string(p->range_buf, "-");
+ buffer_append_off_t(p->range_buf, r->end);
+ buffer_append_string(p->range_buf, "/");
+ buffer_append_off_t(p->range_buf, sce->st.st_size);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
+ }
+
+ /* ok, the file is set-up */
+ return 0;
+}
+
+PHYSICALPATH_FUNC(mod_glusterfs_handle_physical) {
+ plugin_data *p = p_d;
+ stat_cache_entry *sce;
+ size_t size = 0;
+ handler_t ret = 0;
+ mod_glusterfs_ctx_t *plugin_ctx = NULL;
+
+ if (con->http_status != 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+
+ /*
+ network_backend_write = srv->network_backend_write;
+ srv->network_backend_write = mod_glusterfs_network_backend_write;
+ */
+
+ switch (con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ break;
+
+ default:
+ return HANDLER_GO_ON;
+ }
+
+ mod_glusterfs_patch_connection(srv, con, p);
+
+ if (!p->conf.prefix || !p->conf.prefix->ptr) {
+ return HANDLER_GO_ON;
+ }
+
+ if (!p->conf.document_root || p->conf.document_root->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "glusterfs.document-root is not specified");
+ con->http_status = 500;
+ return HANDLER_FINISHED;
+ }
+
+ if (p->conf.handle <= 0) {
+ glusterfs_init_ctx_t ctx;
+
+ if (!p->conf.specfile || p->conf.specfile->used == 0) {
+ return HANDLER_GO_ON;
+ }
+ memset (&ctx, 0, sizeof (ctx));
+
+ ctx.specfile = p->conf.specfile->ptr;
+ ctx.logfile = p->conf.logfile->ptr;
+ ctx.loglevel = p->conf.loglevel->ptr;
+ ctx.lookup_timeout = ctx.stat_timeout = p->conf.cache_timeout;
+
+ p->conf.handle = (unsigned long)glusterfs_init (&ctx);
+
+ if (p->conf.handle <= 0) {
+ con->http_status = 500;
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "glusterfs initialization failed, please check your configuration. Glusterfs logfile ", p->conf.logfile, "might contain details");
+ return HANDLER_FINISHED;
+ }
+ }
+
+ size = 0;
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if (!con->plugin_ctx[p->id]) {
+ buffer *tmp_buf = buffer_init_buffer (con->physical.basedir);
+
+ plugin_ctx = calloc (1, sizeof (*plugin_ctx));
+ /* ERR_ABORT (plugin_ctx); */
+ con->plugin_ctx[p->id] = plugin_ctx;
+
+ buffer_append_string_buffer (tmp_buf, p->conf.prefix);
+ buffer_path_simplify (tmp_buf, tmp_buf);
+
+ plugin_ctx->prefix = tmp_buf->used - 1;
+ if (tmp_buf->ptr[plugin_ctx->prefix - 1] == '/')
+ plugin_ctx->prefix--;
+
+ buffer_free (tmp_buf);
+ } else
+ /*FIXME: error!! error!! */
+ plugin_ctx = con->plugin_ctx[p->id];
+
+
+ if (size)
+ {
+ plugin_ctx->buf = MALLOC (size);
+ /* ERR_ABORT (plugin_ctx->buf); */
+ }
+
+ plugin_ctx->glusterfs_path = buffer_init ();
+ buffer_copy_string_buffer (plugin_ctx->glusterfs_path, p->conf.document_root);
+ buffer_append_string (plugin_ctx->glusterfs_path, "/");
+ buffer_append_string (plugin_ctx->glusterfs_path, con->physical.path->ptr + plugin_ctx->prefix);
+ buffer_path_simplify (plugin_ctx->glusterfs_path, plugin_ctx->glusterfs_path);
+
+ ret = glusterfs_stat_cache_get_entry_async (srv, con, p, plugin_ctx->glusterfs_path, con->physical.path, plugin_ctx->buf, size, &sce);
+
+ if (ret == HANDLER_ERROR) {
+ free (plugin_ctx->buf);
+ plugin_ctx->buf = NULL;
+
+ buffer_free (plugin_ctx->glusterfs_path);
+ plugin_ctx->glusterfs_path = NULL;
+
+ free (plugin_ctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ con->http_status = 500;
+ ret = HANDLER_FINISHED;
+ }
+
+ return ret;
+}
+
+URIHANDLER_FUNC(mod_glusterfs_subrequest) {
+ plugin_data *p = p_d;
+ stat_cache_entry *sce = NULL;
+ int s_len;
+ unsigned long fd;
+ char allow_caching = 1;
+ size_t size = 0;
+ mod_glusterfs_ctx_t *ctx = con->plugin_ctx[p->id];
+
+ /* someone else has done a decision for us */
+ if (con->http_status != 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ /* someone else has handled this request */
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+
+ /* we only handle GET, POST and HEAD */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ break;
+ default:
+ return HANDLER_GO_ON;
+ }
+
+ mod_glusterfs_patch_connection(srv, con, p);
+
+ if (!p->conf.prefix || !p->conf.prefix->ptr)
+ return HANDLER_GO_ON;
+
+ if (!ctx) {
+ con->http_status = 500;
+ return HANDLER_FINISHED;
+ }
+
+ s_len = con->uri.path->used - 1;
+ /* ignore certain extensions */
+ /*
+ for (k = 0; k < p->conf.exclude_exts->used; k++) {
+ data_string *ds;
+ ds = (data_string *)p->conf.exclude_exts->data[k];
+
+ if (ds->value->used == 0) continue;
+
+ if (!strncmp (ds->value->ptr, con->uri.path->ptr, strlen (ds->value->ptr)))
+ break;
+ }
+
+ if (k == p->conf.exclude_exts->used) {
+ return HANDLER_GO_ON;
+ }
+ */
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- serving file from glusterfs");
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ con->http_status = 403;
+
+ /* this might happen if the sce is removed from stat-cache after a successful glusterfs_lookup */
+ if (ctx) {
+ if (ctx->buf) {
+ free (ctx->buf);
+ ctx->buf = NULL;
+ }
+
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ if (con->uri.path->ptr[s_len] == '/' || !S_ISREG(sce->st.st_mode)) {
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if ((size_t)sce->st.st_size > size) {
+
+ fd = glusterfs_open ((libglusterfs_handle_t ) ((unsigned long)p->conf.handle), ctx->glusterfs_path->ptr, O_RDONLY, 0);
+
+ if (!fd) {
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+ ctx->fd = fd;
+ }
+
+ /* we only handline regular files */
+#ifdef HAVE_LSTAT
+ if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
+ con->http_status = 403;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_FINISHED;
+ }
+#endif
+ if (!S_ISREG(sce->st.st_mode)) {
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", sce->name);
+ }
+
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* mod_compress might set several data directly, don't overwrite them */
+
+ /* set response content-type, if not set already */
+
+ if (NULL == array_get_element(con->response.headers, CONST_STR_LEN("Content-Type"))) {
+ if (buffer_is_empty(sce->content_type)) {
+ /* we are setting application/octet-stream, but also announce that
+ * this header field might change in the seconds few requests
+ *
+ * This should fix the aggressive caching of FF and the script download
+ * seen by the first installations
+ */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
+
+ allow_caching = 0;
+ } else {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+ }
+ }
+
+ if (con->conf.range_requests) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
+ }
+
+ /* TODO: Allow Cachable requests */
+#if 0
+ if (allow_caching) {
+ if (p->conf.etags_used && con->etag_flags != 0 && !buffer_is_empty(sce->etag)) {
+ if (NULL == array_get_element(con->response.headers, "ETag")) {
+ /* generate e-tag */
+ etag_mutate(con->physical.etag, sce->etag);
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
+ }
+ }
+
+ /* prepare header */
+ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+ } else {
+ mtime = ds->value;
+ }
+
+ if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+#endif
+
+ /*TODO: Read about etags */
+ if (NULL != array_get_element(con->request.headers, CONST_STR_LEN("Range")) && con->conf.range_requests) {
+ int do_range_request = 1;
+ data_string *ds = NULL;
+ buffer *mtime = NULL;
+ /* check if we have a conditional GET */
+
+ /* prepare header */
+ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, CONST_STR_LEN("Last-Modified")))) {
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+ } else {
+ mtime = ds->value;
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("If-Range")))) {
+ /* if the value is the same as our ETag, we do a Range-request,
+ * otherwise a full 200 */
+
+ if (ds->value->ptr[0] == '"') {
+ /**
+ * client wants a ETag
+ */
+ if (!con->physical.etag) {
+ do_range_request = 0;
+ } else if (!buffer_is_equal(ds->value, con->physical.etag)) {
+ do_range_request = 0;
+ }
+ } else if (!mtime) {
+ /**
+ * we don't have a Last-Modified and can match the If-Range:
+ *
+ * sending all
+ */
+ do_range_request = 0;
+ } else if (!buffer_is_equal(ds->value, mtime)) {
+ do_range_request = 0;
+ }
+ }
+
+ if (do_range_request) {
+ /* content prepared, I'm done */
+ con->send->is_closed = 1;
+
+ if (0 == http_response_parse_range(srv, con, p)) {
+ con->http_status = 206;
+ }
+ if (ctx) {
+ if (ctx->glusterfs_path) {
+ buffer_free (ctx->glusterfs_path);
+ ctx->glusterfs_path = NULL;
+ }
+ free (ctx);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* if we are still here, prepare body */
+
+ /* we add it here for all requests
+ * the HEAD request will drop it afterwards again
+ */
+
+ if (p->conf.xattr_file_size && p->conf.xattr_file_size->ptr)
+ size = atoi (p->conf.xattr_file_size->ptr);
+
+ if (size < (size_t)sce->st.st_size) {
+ chunkqueue_append_glusterfs_file (con->send_raw, fd, 0, sce->st.st_size);
+ con->send_raw->bytes_in += sce->st.st_size;
+ chunkqueue_append_dummy_mem_chunk (con->send, sce->st.st_size);
+ } else {
+ if (!ctx->buf) {
+ con->http_status = 404;
+ return HANDLER_ERROR;
+ }
+ chunkqueue_append_glusterfs_mem (con->send, ctx->buf, sce->st.st_size);
+ ctx->buf = NULL;
+ }
+ ctx->response_content_length = con->response.content_length = sce->st.st_size;
+
+ con->send->is_closed = 1;
+ con->send->bytes_in = sce->st.st_size;
+
+ return HANDLER_FINISHED;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+CONNECTION_FUNC(mod_glusterfs_connection_reset)
+{
+ (void) p_d;
+ (void) con;
+ if (!network_backend_write)
+ network_backend_write = srv->network_backend_write;
+
+ srv->network_backend_write = mod_glusterfs_network_backend_write;
+
+ return HANDLER_GO_ON;
+}
+
+URIHANDLER_FUNC(mod_glusterfs_response_done) {
+ plugin_data *p = p_d;
+ UNUSED (srv);
+ mod_glusterfs_ctx_t *ctx = con->plugin_ctx[p->id];
+
+ con->plugin_ctx[p->id] = NULL;
+ if (ctx->glusterfs_path) {
+ free (ctx->glusterfs_path);
+ }
+
+ free (ctx);
+ return HANDLER_GO_ON;
+}
+
+int mod_glusterfs_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("glusterfs");
+ p->init = mod_glusterfs_init;
+ p->handle_physical = mod_glusterfs_handle_physical;
+ p->handle_start_backend = mod_glusterfs_subrequest;
+ p->handle_response_done = mod_glusterfs_response_done;
+ p->set_defaults = mod_glusterfs_set_defaults;
+ p->connection_reset = mod_glusterfs_connection_reset;
+ p->cleanup = mod_glusterfs_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/mod_glusterfs/lighttpd/1.5/mod_glusterfs.h b/mod_glusterfs/lighttpd/1.5/mod_glusterfs.h
new file mode 100644
index 000000000..b765936d6
--- /dev/null
+++ b/mod_glusterfs/lighttpd/1.5/mod_glusterfs.h
@@ -0,0 +1,29 @@
+/*
+ Copyright (c) 2008 Z RESEARCH, Inc. <http://www.zresearch.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MOD_GLUSTERFS_FILE_CACHE_H_
+#define _MOD_GLUSTERFS_FILE_CACHE_H_
+
+#include "stat_cache.h"
+#include <libglusterfsclient.h>
+#include "base.h"
+
+handler_t glusterfs_stat_cache_get_entry(server *srv, connection *con, libglusterfs_handle_t handle, buffer *glusterfs_path, buffer *name, void *buf, size_t size, stat_cache_entry **fce);
+
+#endif
diff --git a/mod_glusterfs/lighttpd/Makefile.am b/mod_glusterfs/lighttpd/Makefile.am
new file mode 100644
index 000000000..c934412b3
--- /dev/null
+++ b/mod_glusterfs/lighttpd/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = 1.4 1.5
+
+CLEANFILES =