summaryrefslogtreecommitdiffstats
path: root/modules/dav/fs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-07 02:04:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-07 02:04:06 +0000
commit5dff2d61cc1c27747ee398e04d8e02843aabb1f8 (patch)
treea67c336b406c8227bac912beb74a1ad3cdc55100 /modules/dav/fs
parentInitial commit. (diff)
downloadapache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.tar.xz
apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.zip
Adding upstream version 2.4.38.upstream/2.4.38
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--modules/dav/fs/Makefile.in3
-rw-r--r--modules/dav/fs/NWGNUmakefile269
-rw-r--r--modules/dav/fs/config6.m423
-rw-r--r--modules/dav/fs/dbm.c771
-rw-r--r--modules/dav/fs/lock.c1445
-rw-r--r--modules/dav/fs/mod_dav_fs.c108
-rw-r--r--modules/dav/fs/mod_dav_fs.dep203
-rw-r--r--modules/dav/fs/mod_dav_fs.dsp135
-rw-r--r--modules/dav/fs/mod_dav_fs.mak407
-rw-r--r--modules/dav/fs/repos.c2254
-rw-r--r--modules/dav/fs/repos.h84
11 files changed, 5702 insertions, 0 deletions
diff --git a/modules/dav/fs/Makefile.in b/modules/dav/fs/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/fs/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/fs/NWGNUmakefile b/modules/dav/fs/NWGNUmakefile
new file mode 100644
index 0000000..e402428
--- /dev/null
+++ b/modules/dav/fs/NWGNUmakefile
@@ -0,0 +1,269 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(AP_WORK)/modules/dav/main \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddavfs
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV FileSystem Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav_fs.o \
+ $(OBJDIR)/dbm.o \
+ $(OBJDIR)/lock.o \
+ $(OBJDIR)/repos.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_dav \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @../main/dav.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_fs_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/fs/config6.m4 b/modules/dav/fs/config6.m4
new file mode 100644
index 0000000..dd26ec8
--- /dev/null
+++ b/modules/dav/fs/config6.m4
@@ -0,0 +1,23 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/fs)
+
+dav_fs_objects="mod_dav_fs.lo dbm.lo lock.lo repos.lo"
+
+if test "x$enable_dav" != "x"; then
+ dav_fs_enable=$enable_dav
+else
+ dav_fs_enable=$dav_enable
+fi
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main DAV module
+ dav_fs_objects="$dav_fs_objects ../main/mod_dav.la"
+ ;;
+esac
+
+APACHE_MODULE(dav_fs, DAV provider for the filesystem. --enable-dav also enables mod_dav_fs., $dav_fs_objects, , $dav_fs_enable,,dav)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/fs/dbm.c b/modules/dav/fs/dbm.c
new file mode 100644
index 0000000..821168e
--- /dev/null
+++ b/modules/dav/fs/dbm.c
@@ -0,0 +1,771 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - Database support using DBM-style databases,
+** part of the filesystem repository implementation
+*/
+
+/*
+** This implementation uses a SDBM database per file and directory to
+** record the properties. These databases are kept in a subdirectory (of
+** the directory in question or the directory that holds the file in
+** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
+** database is equivalent to the target filename, and is
+** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
+*/
+
+#include "apr_strings.h"
+#include "apr_file_io.h"
+
+#include "apr_dbm.h"
+
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h" /* for ntohs and htons */
+
+#include "mod_dav.h"
+#include "repos.h"
+#include "http_log.h"
+#include "http_main.h" /* for ap_server_conf */
+
+APLOG_USE_MODULE(dav_fs);
+
+struct dav_db {
+ apr_pool_t *pool;
+ apr_dbm_t *file;
+
+ /* when used as a property database: */
+
+ int version; /* *minor* version of this db */
+
+ dav_buffer ns_table; /* table of namespace URIs */
+ short ns_count; /* number of entries in table */
+ int ns_table_dirty; /* ns_table was modified */
+ apr_hash_t *uri_index; /* map URIs to (1-based) table indices */
+
+ dav_buffer wb_key; /* work buffer for dav_gdbm_key */
+
+ apr_datum_t iter; /* iteration key */
+};
+
+/* -------------------------------------------------------------------------
+ *
+ * GENERIC DBM ACCESS
+ *
+ * For the most part, this just uses the APR DBM functions. They are wrapped
+ * a bit with some error handling (using the mod_dav error functions).
+ */
+
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2)
+{
+ if (fname == NULL)
+ fname = DAV_FS_STATE_FILE_FOR_DIR;
+
+ apr_dbm_get_usednames(p, fname, state1, state2);
+}
+
+static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
+ apr_status_t status)
+{
+ int errcode;
+ const char *errstr;
+ dav_error *err;
+ char errbuf[200];
+
+ if (status == APR_SUCCESS)
+ return NULL;
+
+ p = db ? db->pool : p;
+
+ /* There might not be a <db> if we had problems creating it. */
+ if (db == NULL) {
+ errcode = 1;
+ errstr = "Could not open property database.";
+ if (APR_STATUS_IS_EDSOOPEN(status))
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, ap_server_conf, APLOGNO(00576)
+ "The DBM driver could not be loaded");
+ }
+ else {
+ (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
+ errstr = apr_pstrdup(p, errbuf);
+ }
+
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
+ return err;
+}
+
+/* ensure that our state subdirectory is present */
+/* ### does this belong here or in dav_fs_repos.c ?? */
+void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
+{
+ const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* just try to make it, ignoring any resulting errors */
+ (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
+}
+
+/* dav_dbm_open_direct: Opens a *dbm database specified by path.
+ * ro = boolean read-only flag.
+ */
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb)
+{
+ apr_status_t status;
+ apr_dbm_t *file = NULL;
+
+ *pdb = NULL;
+
+ if ((status = apr_dbm_open(&file, pathname,
+ ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, p))
+ != APR_SUCCESS
+ && !ro) {
+ /* ### do something with 'status' */
+
+ /* we can't continue if we couldn't open the file
+ and we need to write */
+ return dav_fs_dbm_error(NULL, p, status);
+ }
+
+ /* may be NULL if we tried to open a non-existent db as read-only */
+ if (file != NULL) {
+ /* we have an open database... return it */
+ *pdb = apr_pcalloc(p, sizeof(**pdb));
+ (*pdb)->pool = p;
+ (*pdb)->file = file;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
+ int ro, dav_db **pdb)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *pathname;
+
+ /* Get directory and filename for resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ /* If not opening read-only, ensure the state dir exists */
+ if (!ro) {
+ /* ### what are the perf implications of always checking this? */
+ dav_fs_ensure_state_dir(p, dirpath);
+ }
+
+ pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
+ fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
+ NULL);
+
+ /* ### readers cannot open while a writer has this open; we should
+ ### perform a few retries with random pauses. */
+
+ /* ### do we need to deal with the umask? */
+
+ return dav_dbm_open_direct(p, pathname, ro, pdb);
+}
+
+void dav_dbm_close(dav_db *db)
+{
+ apr_dbm_close(db->file);
+}
+
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
+{
+ apr_status_t status;
+
+ if (!key.dptr) {
+ /* no key could be created (namespace not known) => no value */
+ memset(pvalue, 0, sizeof(*pvalue));
+ status = APR_SUCCESS;
+ } else {
+ status = apr_dbm_fetch(db->file, key, pvalue);
+ }
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
+{
+ apr_status_t status = apr_dbm_store(db->file, key, value);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
+{
+ apr_status_t status = apr_dbm_delete(db->file, key);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+int dav_dbm_exists(dav_db *db, apr_datum_t key)
+{
+ return apr_dbm_exists(db->file, key);
+}
+
+static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_firstkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_nextkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
+{
+ apr_dbm_freedatum(db->file, data);
+}
+
+/* -------------------------------------------------------------------------
+ *
+ * PROPERTY DATABASE FUNCTIONS
+ */
+
+
+#define DAV_GDBM_NS_KEY "METADATA"
+#define DAV_GDBM_NS_KEY_LEN 8
+
+typedef struct {
+ unsigned char major;
+#define DAV_DBVSN_MAJOR 4
+ /*
+ ** V4 -- 0.9.9 ..
+ ** Prior versions could have keys or values with invalid
+ ** namespace prefixes as a result of the xmlns="" form not
+ ** resetting the default namespace to be "no namespace". The
+ ** namespace would be set to "" which is invalid; it should
+ ** be set to "no namespace".
+ **
+ ** V3 -- 0.9.8
+ ** Prior versions could have values with invalid namespace
+ ** prefixes due to an incorrect mapping of input to propdb
+ ** namespace indices. Version bumped to obsolete the old
+ ** values.
+ **
+ ** V2 -- 0.9.7
+ ** This introduced the xml:lang value into the property value's
+ ** record in the propdb.
+ **
+ ** V1 -- .. 0.9.6
+ ** Initial version.
+ */
+
+
+ unsigned char minor;
+#define DAV_DBVSN_MINOR 0
+
+ short ns_count;
+
+} dav_propdb_metadata;
+
+struct dav_deadprop_rollback {
+ apr_datum_t key;
+ apr_datum_t value;
+};
+
+struct dav_namespace_map {
+ int *ns_map;
+};
+
+/*
+** Internal function to build a key
+**
+** WARNING: returns a pointer to a "static" buffer holding the key. The
+** value must be copied or no longer used if this function is
+** called again.
+*/
+static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
+{
+ char nsbuf[20];
+ apr_size_t l_ns, l_name = strlen(name->name);
+ apr_datum_t key = { 0 };
+
+ /*
+ * Convert namespace ID to a string. "no namespace" is an empty string,
+ * so the keys will have the form ":name". Otherwise, the keys will
+ * have the form "#:name".
+ */
+ if (*name->ns == '\0') {
+ nsbuf[0] = '\0';
+ l_ns = 0;
+ }
+ else {
+ long ns_id = (long)apr_hash_get(db->uri_index, name->ns,
+ APR_HASH_KEY_STRING);
+
+
+ if (ns_id == 0) {
+ /* the namespace was not found(!) */
+ return key; /* zeroed */
+ }
+
+ l_ns = apr_snprintf(nsbuf, sizeof(nsbuf), "%ld", ns_id - 1);
+ }
+
+ /* assemble: #:name */
+ dav_set_bufsize(db->pool, &db->wb_key, l_ns + 1 + l_name + 1);
+ memcpy(db->wb_key.buf, nsbuf, l_ns);
+ db->wb_key.buf[l_ns] = ':';
+ memcpy(&db->wb_key.buf[l_ns + 1], name->name, l_name + 1);
+
+ /* build the database key */
+ key.dsize = l_ns + 1 + l_name + 1;
+ key.dptr = db->wb_key.buf;
+
+ return key;
+}
+
+static void dav_append_prop(apr_pool_t *pool,
+ const char *name, const char *value,
+ apr_text_header *phdr)
+{
+ const char *s;
+ const char *lang = value;
+
+ /* skip past the xml:lang value */
+ value += strlen(lang) + 1;
+
+ if (*value == '\0') {
+ /* the property is an empty value */
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s/>" DEBUG_CR, name);
+ }
+ }
+ else if (*lang != '\0') {
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
+ name+1, lang, value, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
+ name, lang, value, name);
+ }
+ }
+ else if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
+ }
+
+ apr_text_append(pool, phdr, s);
+}
+
+static dav_error * dav_propdb_open(apr_pool_t *pool,
+ const dav_resource *resource, int ro,
+ dav_db **pdb)
+{
+ dav_db *db;
+ dav_error *err;
+ apr_datum_t key;
+ apr_datum_t value = { 0 };
+
+ *pdb = NULL;
+
+ /*
+ ** Return if an error occurred, or there is no database.
+ **
+ ** NOTE: db could be NULL if we attempted to open a readonly
+ ** database that doesn't exist. If we require read/write
+ ** access, then a database was created and opened.
+ */
+ if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
+ || db == NULL)
+ return err;
+
+ db->uri_index = apr_hash_make(pool);
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ if (value.dptr == NULL) {
+ dav_propdb_metadata m = {
+ DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
+ };
+
+ /*
+ ** If there is no METADATA key, then the database may be
+ ** from versions 0.9.0 .. 0.9.4 (which would be incompatible).
+ ** These can be identified by the presence of an NS_TABLE entry.
+ */
+ key.dptr = "NS_TABLE";
+ key.dsize = 8;
+ if (dav_dbm_exists(db, key)) {
+ dav_dbm_close(db);
+
+ /* call it a major version error */
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+
+ /* initialize a new metadata structure */
+ dav_set_bufsize(pool, &db->ns_table, sizeof(m));
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+ }
+ else {
+ dav_propdb_metadata m;
+ long ns;
+ const char *uri;
+
+ dav_set_bufsize(pool, &db->ns_table, value.dsize);
+ memcpy(db->ns_table.buf, value.dptr, value.dsize);
+
+ memcpy(&m, value.dptr, sizeof(m));
+ if (m.major != DAV_DBVSN_MAJOR) {
+ dav_dbm_close(db);
+
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+ db->version = m.minor;
+ db->ns_count = ntohs(m.ns_count);
+
+ dav_dbm_freedatum(db, value);
+
+ /* create db->uri_index */
+ for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+ ns++ < db->ns_count;
+ uri += strlen(uri) + 1) {
+
+ /* we must copy the key, in case ns_table.buf moves */
+ apr_hash_set(db->uri_index,
+ apr_pstrdup(pool, uri), APR_HASH_KEY_STRING,
+ (void *)ns);
+ }
+ }
+
+ *pdb = db;
+ return NULL;
+}
+
+static void dav_propdb_close(dav_db *db)
+{
+
+ if (db->ns_table_dirty) {
+ dav_propdb_metadata m;
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+
+ value.dptr = db->ns_table.buf;
+ value.dsize = db->ns_table.cur_len;
+
+ /* fill in the metadata that we store into the prop db. */
+ m.major = DAV_DBVSN_MAJOR;
+ m.minor = db->version; /* ### keep current minor version? */
+ m.ns_count = htons(db->ns_count);
+
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+
+ err = dav_dbm_store(db, key, value);
+ if (err != NULL)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, err->aprerr, ap_server_conf,
+ APLOGNO(00577) "Error writing propdb: %s", err->desc);
+ }
+
+ dav_dbm_close(db);
+}
+
+static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
+{
+ int ns;
+ const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ /* within the prop values, we use "ns%d" for prefixes... register them */
+ for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) {
+
+ /* Empty URIs signify the empty namespace. These do not get a
+ namespace prefix. when we generate the value, we will simply
+ leave off the prefix, which is defined by mod_dav to be the
+ empty namespace. */
+ if (*uri == '\0')
+ continue;
+
+ /* ns_table.buf can move, so copy its value (we want the values to
+ last as long as the provided dav_xmlns_info). */
+ dav_xmlns_add(xi,
+ apr_psprintf(xi->pool, "ns%d", ns),
+ apr_pstrdup(xi->pool, uri));
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_output_value(dav_db *db,
+ const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr,
+ int *found)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+ dav_error *err;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr == NULL) {
+ *found = 0;
+ return NULL;
+ }
+ *found = 1;
+
+ dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
+
+ dav_dbm_freedatum(db, value);
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_map_namespaces(
+ dav_db *db,
+ const apr_array_header_t *namespaces,
+ dav_namespace_map **mapping)
+{
+ dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
+ int i;
+ int *pmap;
+ const char **puri;
+
+ /*
+ ** Iterate over the provided namespaces. If a namespace already appears
+ ** in our internal map of URI -> ns_id, then store that in the map. If
+ ** we don't know the namespace yet, then add it to the map and to our
+ ** table of known namespaces.
+ */
+ m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
+ for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
+ i-- > 0;
+ ++puri, ++pmap) {
+
+ const char *uri = *puri;
+ apr_size_t uri_len = strlen(uri);
+ long ns_id = (long)apr_hash_get(db->uri_index, uri, uri_len);
+
+ if (ns_id == 0) {
+ dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
+ memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
+ db->ns_table.cur_len += uri_len + 1;
+
+ /* copy the uri in case the passed-in namespaces changes in
+ some way. */
+ apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
+ (void *)((long)(db->ns_count + 1)));
+
+ db->ns_table_dirty = 1;
+
+ *pmap = db->ns_count++;
+ }
+ else {
+ *pmap = ns_id - 1;
+ }
+ }
+
+ *mapping = m;
+ return NULL;
+}
+
+static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name,
+ const apr_xml_elem *elem,
+ dav_namespace_map *mapping)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+
+ /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
+
+ /* ### use a db- subpool for these values? clear on exit? */
+
+ /* quote all the values in the element */
+ /* ### be nice to do this without affecting the element itself */
+ /* ### of course, the cast indicates Badness is occurring here */
+ apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem);
+
+ /* generate a text blob for the xml:lang plus the contents */
+ apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
+ mapping->ns_map,
+ (const char **)&value.dptr, &value.dsize);
+
+ return dav_dbm_store(db, key, value);
+}
+
+static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_delete(db, key);
+}
+
+static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_exists(db, key);
+}
+
+static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
+{
+ const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ while (ns_id--)
+ p += strlen(p) + 1;
+
+ return p;
+}
+
+static void dav_set_name(dav_db *db, dav_prop_name *pname)
+{
+ const char *s = db->iter.dptr;
+
+ if (s == NULL) {
+ pname->ns = pname->name = NULL;
+ }
+ else if (*s == ':') {
+ pname->ns = "";
+ pname->name = s + 1;
+ }
+ else {
+ int id = atoi(s);
+
+ pname->ns = dav_get_ns_table_uri(db, id);
+ if (s[1] == ':') {
+ pname->name = s + 2;
+ }
+ else {
+ pname->name = ap_strchr_c(s + 2, ':') + 1;
+ }
+ }
+}
+
+static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ /* free the previous key. note: if the loop is aborted, then the DBM
+ will toss the key (via pool cleanup) */
+ if (db->iter.dptr != NULL)
+ dav_dbm_freedatum(db, db->iter);
+
+ if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_get_rollback(dav_db *db,
+ const dav_prop_name *name,
+ dav_deadprop_rollback **prollback)
+{
+ dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key = dav_build_key(db, name);
+ rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
+ rb->key.dsize = key.dsize;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr != NULL) {
+ rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
+ rb->value.dsize = value.dsize;
+ }
+
+ *prollback = rb;
+ return NULL;
+}
+
+static dav_error * dav_propdb_apply_rollback(dav_db *db,
+ dav_deadprop_rollback *rollback)
+{
+ if (!rollback) {
+ return NULL; /* no rollback, nothing to do */
+ }
+
+ if (rollback->value.dptr == NULL) {
+ /* don't fail if the thing isn't really there. */
+ (void) dav_dbm_delete(db, rollback->key);
+ return NULL;
+ }
+
+ return dav_dbm_store(db, rollback->key, rollback->value);
+}
+
+const dav_hooks_db dav_hooks_db_dbm =
+{
+ dav_propdb_open,
+ dav_propdb_close,
+ dav_propdb_define_namespaces,
+ dav_propdb_output_value,
+ dav_propdb_map_namespaces,
+ dav_propdb_store,
+ dav_propdb_remove,
+ dav_propdb_exists,
+ dav_propdb_first_name,
+ dav_propdb_next_name,
+ dav_propdb_get_rollback,
+ dav_propdb_apply_rollback,
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/lock.c b/modules/dav/fs/lock.c
new file mode 100644
index 0000000..c058e2e
--- /dev/null
+++ b/modules/dav/fs/lock.c
@@ -0,0 +1,1445 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem lock implementation
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_uuid.h"
+
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_log.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* ---------------------------------------------------------------
+**
+** Lock database primitives
+**
+*/
+
+/*
+** LOCK DATABASES
+**
+** Lockdiscovery information is stored in the single lock database specified
+** by the DAVLockDB directive. Information about this db is stored in the
+** global server configuration.
+**
+** KEY
+**
+** The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
+** followed by the full path. The key_type DAV_TYPE_INODE is not used anymore.
+**
+** VALUE
+**
+** The value consists of a list of elements.
+** DIRECT LOCK: [char (DAV_LOCK_DIRECT),
+** char (dav_lock_scope),
+** char (dav_lock_type),
+** int depth,
+** time_t expires,
+** apr_uuid_t locktoken,
+** char[] owner,
+** char[] auth_user]
+**
+** INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
+** apr_uuid_t locktoken,
+** time_t expires,
+** apr_size_t key_size,
+** char[] key]
+** The key is to the collection lock that resulted in this indirect lock
+*/
+
+#define DAV_TRUE 1
+#define DAV_FALSE 0
+
+#define DAV_CREATE_LIST 23
+#define DAV_APPEND_LIST 24
+
+/* Stored lock_discovery prefix */
+#define DAV_LOCK_DIRECT 1
+#define DAV_LOCK_INDIRECT 2
+
+/*
+ * not used anymore
+ * #define DAV_TYPE_INODE 10
+ */
+#define DAV_TYPE_FNAME 11
+
+
+/* ack. forward declare. */
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf);
+
+/*
+** Use the opaquelock scheme for locktokens
+*/
+struct dav_locktoken {
+ apr_uuid_t uuid;
+};
+#define dav_compare_locktoken(plt1, plt2) \
+ memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
+
+
+/* #################################################################
+** ### keep these structures (internal) or move fully to dav_lock?
+*/
+
+/*
+** We need to reliably size the fixed-length portion of
+** dav_lock_discovery; best to separate it into another
+** struct for a convenient sizeof, unless we pack lock_discovery.
+*/
+typedef struct dav_lock_discovery_fixed
+{
+ char scope;
+ char type;
+ int depth;
+ time_t timeout;
+} dav_lock_discovery_fixed;
+
+typedef struct dav_lock_discovery
+{
+ struct dav_lock_discovery_fixed f;
+
+ dav_locktoken *locktoken;
+ const char *owner; /* owner field from activelock */
+ const char *auth_user; /* authenticated user who created the lock */
+ struct dav_lock_discovery *next;
+} dav_lock_discovery;
+
+/* Indirect locks represent locks inherited from containing collections.
+ * They reference the lock token for the collection the lock is
+ * inherited from. A lock provider may also define a key to the
+ * inherited lock, for fast datbase lookup. The key is opaque outside
+ * the lock provider.
+ */
+typedef struct dav_lock_indirect
+{
+ dav_locktoken *locktoken;
+ apr_datum_t key;
+ struct dav_lock_indirect *next;
+ time_t timeout;
+} dav_lock_indirect;
+
+/* ################################################################# */
+
+
+/*
+** Stored direct lock info - full lock_discovery length:
+** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
+*/
+#define dav_size_direct(a) ( 1 + sizeof(dav_lock_discovery_fixed) \
+ + sizeof(apr_uuid_t) \
+ + ((a)->owner ? strlen((a)->owner) : 0) \
+ + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
+ + 2)
+
+/* Stored indirect lock info - lock token and apr_datum_t */
+#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
+ + sizeof(time_t) \
+ + sizeof((a)->key.dsize) + (a)->key.dsize)
+
+/*
+** The lockdb structure.
+**
+** The <db> field may be NULL, meaning one of two things:
+** 1) That we have not actually opened the underlying database (yet). The
+** <opened> field should be false.
+** 2) We opened it readonly and it wasn't present.
+**
+** The delayed opening (determined by <opened>) makes creating a lockdb
+** quick, while deferring the underlying I/O until it is actually required.
+**
+** We export the notion of a lockdb, but hide the details of it. Most
+** implementations will use a database of some kind, but it is certainly
+** possible that alternatives could be used.
+*/
+struct dav_lockdb_private
+{
+ request_rec *r; /* for accessing the uuid state */
+ apr_pool_t *pool; /* a pool to use */
+ const char *lockdb_path; /* where is the lock database? */
+
+ int opened; /* we opened the database */
+ dav_db *db; /* if non-NULL, the lock database */
+};
+typedef struct
+{
+ dav_lockdb pub;
+ dav_lockdb_private priv;
+} dav_lockdb_combined;
+
+/*
+** The private part of the lock structure.
+*/
+struct dav_lock_private
+{
+ apr_datum_t key; /* key into the lock database */
+};
+typedef struct
+{
+ dav_lock pub;
+ dav_lock_private priv;
+ dav_locktoken token;
+} dav_lock_combined;
+
+/*
+** This must be forward-declared so the open_lockdb function can use it.
+*/
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+
+/* internal function for creating locks */
+static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
+ const dav_locktoken *locktoken)
+{
+ dav_lock_combined *comb;
+
+ comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
+ comb->pub.rectype = DAV_LOCKREC_DIRECT;
+ comb->pub.info = &comb->priv;
+ comb->priv.key = key;
+
+ if (locktoken == NULL) {
+ comb->pub.locktoken = &comb->token;
+ apr_uuid_get(&comb->token.uuid);
+ }
+ else {
+ comb->pub.locktoken = locktoken;
+ }
+
+ return &comb->pub;
+}
+
+/*
+** dav_fs_parse_locktoken
+**
+** Parse an opaquelocktoken URI into a locktoken.
+*/
+static dav_error * dav_fs_parse_locktoken(
+ apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p)
+{
+ dav_locktoken *locktoken;
+
+ if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
+ return dav_new_error(p,
+ HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
+ "The lock token uses an unknown State-token "
+ "format and could not be parsed.");
+ }
+ char_token += 16;
+
+ locktoken = apr_pcalloc(p, sizeof(*locktoken));
+ if (apr_uuid_parse(&locktoken->uuid, char_token)) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
+ "The opaquelocktoken has an incorrect format "
+ "and could not be parsed.");
+ }
+
+ *locktoken_p = locktoken;
+ return NULL;
+}
+
+/*
+** dav_fs_format_locktoken
+**
+** Generate the URI for a locktoken
+*/
+static const char *dav_fs_format_locktoken(
+ apr_pool_t *p,
+ const dav_locktoken *locktoken)
+{
+ char buf[APR_UUID_FORMATTED_LENGTH + 1];
+
+ apr_uuid_format(buf, &locktoken->uuid);
+ return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
+}
+
+/*
+** dav_fs_compare_locktoken
+**
+** Determine whether two locktokens are the same
+*/
+static int dav_fs_compare_locktoken(
+ const dav_locktoken *lt1,
+ const dav_locktoken *lt2)
+{
+ return dav_compare_locktoken(lt1, lt2);
+}
+
+/*
+** dav_fs_really_open_lockdb:
+**
+** If the database hasn't been opened yet, then open the thing.
+*/
+static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
+{
+ dav_error *err;
+
+ if (lockdb->info->opened)
+ return NULL;
+
+ err = dav_dbm_open_direct(lockdb->info->pool,
+ lockdb->info->lockdb_path,
+ lockdb->ro,
+ &lockdb->info->db);
+ if (err != NULL) {
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_OPENDB,
+ "Could not open the lock database.",
+ err);
+ }
+
+ /* all right. it is opened now. */
+ lockdb->info->opened = 1;
+
+ return NULL;
+}
+
+/*
+** dav_fs_open_lockdb:
+**
+** "open" the lock database, as specified in the global server configuration.
+** If force is TRUE, then the database is opened now, rather than lazily.
+**
+** Note that only one can be open read/write.
+*/
+static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
+ dav_lockdb **lockdb)
+{
+ dav_lockdb_combined *comb;
+
+ comb = apr_pcalloc(r->pool, sizeof(*comb));
+ comb->pub.hooks = &dav_hooks_locks_fs;
+ comb->pub.ro = ro;
+ comb->pub.info = &comb->priv;
+ comb->priv.r = r;
+ comb->priv.pool = r->pool;
+
+ comb->priv.lockdb_path = dav_get_lockdb_path(r);
+ if (comb->priv.lockdb_path == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_NO_DB, 0,
+ "A lock database was not specified with the "
+ "DAVLockDB directive. One must be specified "
+ "to use the locking functionality.");
+ }
+
+ /* done initializing. return it. */
+ *lockdb = &comb->pub;
+
+ if (force) {
+ /* ### add a higher-level comment? */
+ return dav_fs_really_open_lockdb(*lockdb);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_close_lockdb:
+**
+** Close it. Duh.
+*/
+static void dav_fs_close_lockdb(dav_lockdb *lockdb)
+{
+ if (lockdb->info->db != NULL)
+ dav_dbm_close(lockdb->info->db);
+}
+
+/*
+** dav_fs_build_key: Given a resource, return a apr_datum_t key
+** to look up lock information for this file.
+*/
+static apr_datum_t dav_fs_build_key(apr_pool_t *p,
+ const dav_resource *resource)
+{
+ const char *pathname = dav_fs_pathname(resource);
+ apr_datum_t key;
+
+ /* ### does this allocation have a proper lifetime? need to check */
+ /* ### can we use a buffer for this? */
+
+ /* size is TYPE + pathname + null */
+ key.dsize = strlen(pathname) + 2;
+ key.dptr = apr_palloc(p, key.dsize);
+ *key.dptr = DAV_TYPE_FNAME;
+ memcpy(key.dptr + 1, pathname, key.dsize - 1);
+ if (key.dptr[key.dsize - 2] == '/')
+ key.dptr[--key.dsize - 1] = '\0';
+ return key;
+}
+
+/*
+** dav_fs_lock_expired: return 1 (true) if the given timeout is in the past
+** or present (the lock has expired), or 0 (false) if in the future
+** (the lock has not yet expired).
+*/
+static int dav_fs_lock_expired(time_t expires)
+{
+ return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
+}
+
+/*
+** dav_fs_save_lock_record: Saves the lock information specified in the
+** direct and indirect lock lists about path into the lock database.
+** If direct and indirect == NULL, the key is removed.
+*/
+static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ dav_lock_discovery *direct,
+ dav_lock_indirect *indirect)
+{
+ dav_error *err;
+ apr_datum_t val = { 0 };
+ char *ptr;
+ dav_lock_discovery *dp = direct;
+ dav_lock_indirect *ip = indirect;
+
+#if DAV_DEBUG
+ if (lockdb->ro) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: the lockdb was opened "
+ "readonly, but an attempt to save locks was "
+ "performed.");
+ }
+#endif
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /* If nothing to save, delete key */
+ if (dp == NULL && ip == NULL) {
+ /* don't fail if the key is not present */
+ /* ### but what about other errors? */
+ (void) dav_dbm_delete(lockdb->info->db, key);
+ return NULL;
+ }
+
+ while(dp) {
+ val.dsize += dav_size_direct(dp);
+ dp = dp->next;
+ }
+ while(ip) {
+ val.dsize += dav_size_indirect(ip);
+ ip = ip->next;
+ }
+
+ /* ### can this be apr_palloc() ? */
+ /* ### hmmm.... investigate the use of a buffer here */
+ ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
+ dp = direct;
+ ip = indirect;
+
+ while(dp) {
+ *ptr++ = DAV_LOCK_DIRECT; /* Direct lock - lock_discovery struct follows */
+ memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
+ ptr += sizeof(dp->f);
+ memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
+ ptr += sizeof(*dp->locktoken);
+ if (dp->owner == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
+ ptr += strlen(dp->owner) + 1;
+ }
+ if (dp->auth_user == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
+ ptr += strlen(dp->auth_user) + 1;
+ }
+
+ dp = dp->next;
+ }
+
+ while(ip) {
+ *ptr++ = DAV_LOCK_INDIRECT; /* Indirect lock prefix */
+ memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken)); /* Locktoken */
+ ptr += sizeof(*ip->locktoken);
+ memcpy(ptr, &ip->timeout, sizeof(ip->timeout)); /* Expire time */
+ ptr += sizeof(ip->timeout);
+ memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize)); /* Size of key */
+ ptr += sizeof(ip->key.dsize);
+ memcpy(ptr, ip->key.dptr, ip->key.dsize); /* Key data */
+ ptr += ip->key.dsize;
+ ip = ip->next;
+ }
+
+ if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) {
+ /* ### more details? add an error_id? */
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Could not save lock information.",
+ err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_load_lock_record: Reads lock information about key from lock db;
+** creates linked lists of the direct and indirect locks.
+**
+** If add_method = DAV_APPEND_LIST, the result will be appended to the
+** head of the direct and indirect lists supplied.
+**
+** Passive lock removal: If lock has timed out, it will not be returned.
+** ### How much "logging" does RFC 2518 require?
+*/
+static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ int add_method,
+ dav_lock_discovery **direct,
+ dav_lock_indirect **indirect)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ apr_size_t offset = 0;
+ int need_save = DAV_FALSE;
+ apr_datum_t val = { 0 };
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+ dav_buffer buf = { 0 };
+
+ if (add_method != DAV_APPEND_LIST) {
+ *direct = NULL;
+ *indirect = NULL;
+ }
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
+ return err;
+
+ if (!val.dsize)
+ return NULL;
+
+ while (offset < val.dsize) {
+ switch (*(val.dptr + offset++)) {
+ case DAV_LOCK_DIRECT:
+ /* Create and fill a dav_lock_discovery structure */
+
+ dp = apr_pcalloc(p, sizeof(*dp));
+ memcpy(dp, val.dptr + offset, sizeof(dp->f));
+ offset += sizeof(dp->f);
+ dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
+ offset += sizeof(*dp->locktoken);
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->owner = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->owner) + 1;
+ }
+
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->auth_user = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->auth_user) + 1;
+ }
+
+ if (!dav_fs_lock_expired(dp->f.timeout)) {
+ dp->next = *direct;
+ *direct = dp;
+ }
+ else {
+ need_save = DAV_TRUE;
+
+ /* Remove timed-out locknull fm .locknull list */
+ if (*key.dptr == DAV_TYPE_FNAME) {
+ const char *fname = key.dptr + 1;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+
+ /* if we don't see the file, then it's a locknull */
+ rv = apr_stat(&finfo, fname, APR_FINFO_MIN | APR_FINFO_LINK, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+ }
+ }
+ }
+ break;
+
+ case DAV_LOCK_INDIRECT:
+ /* Create and fill a dav_lock_indirect structure */
+
+ ip = apr_pcalloc(p, sizeof(*ip));
+ ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
+ offset += sizeof(*ip->locktoken);
+ memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
+ offset += sizeof(ip->timeout);
+ memcpy(&ip->key.dsize, val.dptr + offset, sizeof(ip->key.dsize)); /* length of datum */
+ offset += sizeof(ip->key.dsize);
+ ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
+ offset += ip->key.dsize;
+
+ if (!dav_fs_lock_expired(ip->timeout)) {
+ ip->next = *indirect;
+ *indirect = ip;
+ }
+ else {
+ need_save = DAV_TRUE;
+ /* A locknull resource will never be locked indirectly */
+ }
+
+ break;
+
+ default:
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* ### should use a computed_desc and insert corrupt token data */
+ --offset;
+ return dav_new_error(p,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ apr_psprintf(p,
+ "The lock database was found to "
+ "be corrupt. offset %"
+ APR_SIZE_T_FMT ", c=%02x",
+ offset, val.dptr[offset]));
+ }
+ }
+
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* Clean up this record if we found expired locks */
+ /*
+ ** ### shouldn't do this if we've been opened READONLY. elide the
+ ** ### timed-out locks from the response, but don't save that info back
+ */
+ if (need_save == DAV_TRUE) {
+ return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
+ }
+
+ return NULL;
+}
+
+/* resolve <indirect>, returning <*direct> */
+static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
+ dav_lock_indirect *indirect,
+ dav_lock_discovery **direct,
+ dav_lock_discovery **ref_dp,
+ dav_lock_indirect **ref_ip)
+{
+ dav_error *err;
+ dav_lock_discovery *dir;
+ dav_lock_indirect *ind;
+
+ if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
+ DAV_CREATE_LIST,
+ &dir, &ind)) != NULL) {
+ /* ### insert a higher-level description? */
+ return err;
+ }
+ if (ref_dp != NULL) {
+ *ref_dp = dir;
+ *ref_ip = ind;
+ }
+
+ for (; dir != NULL; dir = dir->next) {
+ if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
+ *direct = dir;
+ return NULL;
+ }
+ }
+
+ /* No match found (but we should have found one!) */
+
+ /* ### use a different description and/or error ID? */
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ "The lock database was found to be corrupt. "
+ "An indirect lock's direct lock could not "
+ "be found.");
+}
+
+/* ---------------------------------------------------------------
+**
+** Property-related lock functions
+**
+*/
+
+/*
+** dav_fs_get_supportedlock: Returns a static string for all supportedlock
+** properties. I think we save more returning a static string than
+** constructing it every time, though it might look cleaner.
+*/
+static const char *dav_fs_get_supportedlock(const dav_resource *resource)
+{
+ static const char supported[] = DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR;
+
+ return supported;
+}
+
+/* ---------------------------------------------------------------
+**
+** General lock functions
+**
+*/
+
+/* ---------------------------------------------------------------
+**
+** Functions dealing with lock-null resources
+**
+*/
+
+/*
+** dav_fs_load_locknull_list: Returns a dav_buffer dump of the locknull file
+** for the given directory.
+*/
+static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t finfo;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ dav_buffer_init(p, pbuf, dirpath);
+
+ if (pbuf->buf[pbuf->cur_len - 1] == '/')
+ pbuf->buf[--pbuf->cur_len] = '\0';
+
+ dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
+
+ /* reset this in case we leave w/o reading into the buffer */
+ pbuf->cur_len = 0;
+
+ if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ p) != APR_SUCCESS) {
+ return NULL;
+ }
+
+ rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
+ if (rv != APR_SUCCESS) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Opened but could not stat file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ if (finfo.size != (apr_size_t)finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ apr_psprintf(p,
+ "Opened but rejected huge file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ amt = (apr_size_t)finfo.size;
+ dav_set_bufsize(p, pbuf, amt);
+ if ((rv = apr_file_read(file, pbuf->buf, &amt)) != APR_SUCCESS
+ || amt != finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Failure reading locknull file "
+ "for %s", dirpath));
+
+ /* just in case the caller disregards the returned error */
+ pbuf->cur_len = 0;
+ goto loaderror;
+ }
+
+ loaderror:
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_save_locknull_list: Saves contents of pbuf into the
+** locknull file for dirpath.
+*/
+static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ const char *pathname;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ if (pbuf->buf == NULL)
+ return NULL;
+
+ dav_fs_ensure_state_dir(p, dirpath);
+ pathname = apr_pstrcat(p,
+ dirpath,
+ dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
+ DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
+ NULL);
+
+ if (pbuf->cur_len == 0) {
+ /* delete the file if cur_len == 0 */
+ if ((rv = apr_file_remove(pathname, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error removing %s", pathname));
+ }
+ return NULL;
+ }
+
+ if ((rv = apr_file_open(&file, pathname,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error opening %s for writing",
+ pathname));
+ }
+
+ amt = pbuf->cur_len;
+ if ((rv = apr_file_write_full(file, pbuf->buf, amt, &amt)) != APR_SUCCESS
+ || amt != pbuf->cur_len) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error writing %" APR_SIZE_T_FMT
+ " bytes to %s",
+ pbuf->cur_len, pathname));
+ }
+
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_remove_locknull_member: Removes filename from the locknull list
+** for directory path.
+*/
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf)
+{
+ dav_error *err;
+ apr_size_t len;
+ apr_size_t scanlen;
+ char *scan;
+ const char *scanend;
+ char *dirpath = apr_pstrdup(p, filename);
+ char *fname = strrchr(dirpath, '/');
+ int dirty = 0;
+
+ if (fname != NULL)
+ *fname++ = '\0';
+ else
+ fname = dirpath;
+ len = strlen(fname) + 1;
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+
+ for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
+ scan < scanend;
+ scan += scanlen) {
+ scanlen = strlen(scan) + 1;
+ if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
+ pbuf->cur_len -= scanlen;
+ memmove(scan, scan + scanlen, scanend - (scan + scanlen));
+ dirty = 1;
+ break;
+ }
+ }
+
+ if (dirty) {
+ if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: used by dav_fs_repos.c */
+dav_error * dav_fs_get_locknull_members(
+ const dav_resource *resource,
+ dav_buffer *pbuf)
+{
+ const char *dirpath;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, NULL);
+ return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
+}
+
+/* ### fold into append_lock? */
+/* ### take an optional buf parameter? */
+static dav_error * dav_fs_add_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ apr_pool_t *p = lockdb->info->pool;
+ const char *dirpath;
+ const char *fname;
+ dav_error *err;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not load .locknull file.", err);
+ }
+
+ dav_buffer_append(p, &buf, fname);
+ buf.cur_len++; /* we want the null-term here */
+
+ if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not save .locknull file.", err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_remove_locknull_state: Given a request, check to see if r->filename
+** is/was a lock-null resource. If so, return it to an existant state, i.e.
+** remove it from the list in the appropriate .DAV/locknull file.
+*/
+static dav_error * dav_fs_remove_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ dav_error *err;
+ apr_pool_t *p = lockdb->info->pool;
+ const char *pathname = dav_fs_pathname(resource);
+
+ if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock)
+{
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *lock = dav_fs_alloc_lock(lockdb,
+ key,
+ NULL);
+
+ (*lock)->is_locknull = !resource->exists;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ apr_datum_t key;
+ dav_error *err;
+ dav_lock *lock = NULL;
+ dav_lock *newlock;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+#if DAV_DEBUG
+ if (calltype == DAV_GETLOCKS_COMPLETE) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
+ "is not yet supported");
+ }
+#endif
+
+ key = dav_fs_build_key(p, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ /* copy all direct locks to the result list */
+ for (; dp != NULL; dp = dp->next) {
+ newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ /* copy all the indirect locks to the result list. resolve as needed. */
+ for (; ip != NULL; ip = ip->next) {
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
+ newlock->is_locknull = !resource->exists;
+
+ if (calltype == DAV_GETLOCKS_RESOLVED) {
+ if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+ }
+ else {
+ /* DAV_GETLOCKS_PARTIAL */
+ newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ *locks = lock;
+ return NULL;
+}
+
+static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ *lock = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ for (; dp != NULL; dp = dp->next) {
+ if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ return NULL;
+ }
+ }
+
+ for (; ip != NULL; ip = ip->next) {
+ if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+
+ /* ### nobody uses the resolving right now! */
+ if (partial_ok) {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+ else {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT;
+ if ((err = dav_fs_resolve(lockdb, ip, &dp,
+ NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ }
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present)
+{
+ dav_error *err;
+ apr_datum_t key;
+
+ *locks_present = 0;
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### insert a higher-level error description */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *locks_present = dav_dbm_exists(lockdb->info->db, key);
+
+ return NULL;
+}
+
+static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ dav_lock_indirect *ip;
+ dav_lock_discovery *dp;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /*
+ ** ### when we store the lock more directly, we need to update
+ ** ### lock->rectype and lock->is_locknull
+ */
+
+ if (make_indirect) {
+ for (; lock != NULL; lock = lock->next) {
+
+ /* ### this works for any <lock> rectype */
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->timeout = lock->timeout;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ else {
+ for (; lock != NULL; lock = lock->next) {
+ /* create and link in the right kind of lock */
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
+
+ newd->f.scope = lock->scope;
+ newd->f.type = lock->type;
+ newd->f.depth = lock->depth;
+ newd->f.timeout = lock->timeout;
+ /* ### shut off the const warning for now */
+ newd->locktoken = (dav_locktoken *)lock->locktoken;
+ newd->owner = lock->owner;
+ newd->auth_user = lock->auth_user;
+ newd->next = dp;
+ dp = newd;
+ }
+ else {
+ /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
+
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ }
+
+ if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /* we have a special list for recording locknull resources */
+ /* ### ack! this can add two copies to the locknull list */
+ if (!resource->exists
+ && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ dav_error *err;
+ dav_buffer buf = { 0 };
+ dav_lock_discovery *dh = NULL;
+ dav_lock_indirect *ih = NULL;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ if (locktoken != NULL) {
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dprev = NULL;
+ dav_lock_indirect *ip;
+ dav_lock_indirect *iprev = NULL;
+
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dh, &ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ for (dp = dh; dp != NULL; dp = dp->next) {
+ if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
+ if (dprev)
+ dprev->next = dp->next;
+ else
+ dh = dh->next;
+ }
+ dprev = dp;
+ }
+
+ for (ip = ih; ip != NULL; ip = ip->next) {
+ if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
+ if (iprev)
+ iprev->next = ip->next;
+ else
+ ih = ih->next;
+ }
+ iprev = ip;
+ }
+
+ }
+
+ /* save the modified locks, or remove all locks (dh=ih=NULL). */
+ if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /*
+ ** If this resource is a locknull resource AND no more locks exist,
+ ** then remove the locknull member.
+ **
+ ** Note: remove_locknull_state() attempts to convert a locknull member
+ ** to a real member. In this case, all locks are gone, so the
+ ** locknull resource returns to the null state (ie. doesn't exist),
+ ** so there is no need to update the lockdb (and it won't find
+ ** any because a precondition is that none exist).
+ */
+ if (!resource->exists && dh == NULL && ih == NULL
+ && (err = dav_fs_remove_locknull_member(lockdb->info->pool,
+ dav_fs_pathname(resource),
+ &buf)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static int dav_fs_do_refresh(dav_lock_discovery *dp,
+ const dav_locktoken_list *ltl,
+ time_t new_time)
+{
+ int dirty = 0;
+
+ for (; ltl != NULL; ltl = ltl->next) {
+ if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
+ {
+ dp->f.timeout = new_time;
+ dirty = 1;
+ break;
+ }
+ }
+
+ return dirty;
+}
+
+static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dp_scan;
+ dav_lock_indirect *ip;
+ int dirty = 0;
+ dav_lock *newlock;
+
+ *locks = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* ### we should be refreshing direct AND (resolved) indirect locks! */
+
+ /* refresh all of the direct locks on this resource */
+ for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ dirty = 1;
+ }
+ }
+
+ /* if we refreshed any locks, then save them back. */
+ if (dirty
+ && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* for each indirect lock, find its direct lock and refresh it. */
+ for (; ip != NULL; ip = ip->next) {
+ dav_lock_discovery *ref_dp;
+ dav_lock_indirect *ref_ip;
+
+ if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
+ &ref_dp, &ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ /* save the (resolved) direct lock back */
+ if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
+ ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+const dav_hooks_locks dav_hooks_locks_fs =
+{
+ dav_fs_get_supportedlock,
+ dav_fs_parse_locktoken,
+ dav_fs_format_locktoken,
+ dav_fs_compare_locktoken,
+ dav_fs_open_lockdb,
+ dav_fs_close_lockdb,
+ dav_fs_remove_locknull_state,
+ dav_fs_create_lock,
+ dav_fs_get_locks,
+ dav_fs_find_lock,
+ dav_fs_has_locks,
+ dav_fs_append_locks,
+ dav_fs_remove_lock,
+ dav_fs_refresh_locks,
+ NULL, /* lookup_resource */
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/mod_dav_fs.c b/modules/dav/fs/mod_dav_fs.c
new file mode 100644
index 0000000..addfd7e
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.c
@@ -0,0 +1,108 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_strings.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+/* per-server configuration */
+typedef struct {
+ const char *lockdb_path;
+
+} dav_fs_server_conf;
+
+extern module AP_MODULE_DECLARE_DATA dav_fs_module;
+
+const char *dav_get_lockdb_path(const request_rec *r)
+{
+ dav_fs_server_conf *conf;
+
+ conf = ap_get_module_config(r->server->module_config, &dav_fs_module);
+ return conf->lockdb_path;
+}
+
+static void *dav_fs_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ return apr_pcalloc(p, sizeof(dav_fs_server_conf));
+}
+
+static void *dav_fs_merge_server_config(apr_pool_t *p,
+ void *base, void *overrides)
+{
+ dav_fs_server_conf *parent = base;
+ dav_fs_server_conf *child = overrides;
+ dav_fs_server_conf *newconf;
+
+ newconf = apr_pcalloc(p, sizeof(*newconf));
+
+ newconf->lockdb_path =
+ child->lockdb_path ? child->lockdb_path : parent->lockdb_path;
+
+ return newconf;
+}
+
+/*
+ * Command handler for the DAVLockDB directive, which is TAKE1
+ */
+static const char *dav_fs_cmd_davlockdb(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_fs_server_conf *conf;
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_fs_module);
+ conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
+
+ if (!conf->lockdb_path) {
+ return apr_pstrcat(cmd->pool, "Invalid DAVLockDB path ",
+ arg1, NULL);
+ }
+
+ return NULL;
+}
+
+static const command_rec dav_fs_cmds[] =
+{
+ /* per server */
+ AP_INIT_TAKE1("DAVLockDB", dav_fs_cmd_davlockdb, NULL, RSRC_CONF,
+ "specify a lock database"),
+
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ dav_hook_gather_propsets(dav_fs_gather_propsets, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ dav_hook_find_liveprop(dav_fs_find_liveprop, NULL, NULL, APR_HOOK_MIDDLE);
+ dav_hook_insert_all_liveprops(dav_fs_insert_all_liveprops, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ dav_fs_register(p);
+}
+
+AP_DECLARE_MODULE(dav_fs) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ dav_fs_create_server_config, /* server config */
+ dav_fs_merge_server_config, /* merge server config */
+ dav_fs_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
diff --git a/modules/dav/fs/mod_dav_fs.dep b/modules/dav/fs/mod_dav_fs.dep
new file mode 100644
index 0000000..0d67f04
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dep
@@ -0,0 +1,203 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav_fs.mak
+
+.\dbm.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\mod_dav_fs.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\repos.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/fs/mod_dav_fs.dsp b/modules/dav/fs/mod_dav_fs.dsp
new file mode 100644
index 0000000..fbfc1e4
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dsp
@@ -0,0 +1,135 @@
+# Microsoft Developer Studio Project File - Name="mod_dav_fs" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav_fs - Win32 Release"
+# Name "mod_dav_fs - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lock.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav_fs.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\repos.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\repos.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/fs/mod_dav_fs.mak b/modules/dav/fs/mod_dav_fs.mak
new file mode 100644
index 0000000..5baff67
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.mak
@@ -0,0 +1,407 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav_fs.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav_fs - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav_fs - Win32 Release" && "$(CFG)" != "mod_dav_fs - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dav - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\main\Release\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Release\mod_dav_fs.so.manifest mt.exe -manifest .\Release\mod_dav_fs.so.manifest -outputresource:.\Release\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dav - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\main\Debug\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Debug\mod_dav_fs.so.manifest mt.exe -manifest .\Debug\mod_dav_fs.so.manifest -outputresource:.\Debug\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav_fs.dep")
+!INCLUDE "mod_dav_fs.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav_fs.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release" || "$(CFG)" == "mod_dav_fs - Win32 Debug"
+SOURCE=.\dbm.c
+
+"$(INTDIR)\dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lock.c
+
+"$(INTDIR)\lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav_fs.c
+
+"$(INTDIR)\mod_dav_fs.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\repos.c
+
+"$(INTDIR)\repos.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"mod_dav - Win32 Release" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release"
+ cd "..\fs"
+
+"mod_dav - Win32 ReleaseCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"mod_dav - Win32 Debug" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug"
+ cd "..\fs"
+
+"mod_dav - Win32 DebugCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/fs/repos.c b/modules/dav/fs/repos.c
new file mode 100644
index 0000000..6a5ff76
--- /dev/null
+++ b/modules/dav/fs/repos.c
@@ -0,0 +1,2254 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem-based repository provider
+*/
+
+#include "apr.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
+#include "http_request.h" /* for ap_update_mtime() */
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* to assist in debugging mod_dav's GET handling */
+#define DEBUG_GET_HANDLER 0
+
+#define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
+
+/* context needed to identify a resource */
+struct dav_resource_private {
+ apr_pool_t *pool; /* memory storage pool associated with request */
+ const char *pathname; /* full pathname to resource */
+ apr_finfo_t finfo; /* filesystem info */
+ request_rec *r;
+};
+
+/* private context for doing a filesystem walk */
+typedef struct {
+ /* the input walk parameters */
+ const dav_walk_params *params;
+
+ /* reused as we walk */
+ dav_walk_resource wres;
+
+ dav_resource res1;
+ dav_resource_private info1;
+ dav_buffer path1;
+ dav_buffer uri_buf;
+
+ /* MOVE/COPY need a secondary path */
+ dav_resource res2;
+ dav_resource_private info2;
+ dav_buffer path2;
+
+ dav_buffer locknull_buf;
+
+} dav_fs_walker_context;
+
+typedef struct {
+ int is_move; /* is this a MOVE? */
+ dav_buffer work_buf; /* handy buffer for copymove_file() */
+
+ /* CALLBACK: this is a secondary resource managed specially for us */
+ const dav_resource *res_dst;
+
+ /* copied from dav_walk_params (they are invariant across the walk) */
+ const dav_resource *root;
+ apr_pool_t *pool;
+
+} dav_fs_copymove_walk_ctx;
+
+/* an internal WALKTYPE to walk hidden files (the .DAV directory) */
+#define DAV_WALKTYPE_HIDDEN 0x4000
+
+/* an internal WALKTYPE to call collections (again) after their contents */
+#define DAV_WALKTYPE_POSTFIX 0x8000
+
+#define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
+
+
+/* pull this in from the other source file */
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+/* forward-declare the hook structures */
+static const dav_hooks_repository dav_hooks_repository_fs;
+static const dav_hooks_liveprop dav_hooks_liveprop_fs;
+
+/*
+** The namespace URIs that we use. This list and the enumeration must
+** stay in sync.
+*/
+static const char * const dav_fs_namespace_uris[] =
+{
+ "DAV:",
+ "http://apache.org/dav/props/",
+
+ NULL /* sentinel */
+};
+enum {
+ DAV_FS_URI_DAV, /* the DAV: namespace URI */
+ DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
+};
+
+/*
+** Does this platform support an executable flag?
+**
+** ### need a way to portably abstract this query
+**
+** DAV_FINFO_MASK gives the appropriate mask to use for the stat call
+** used to get file attributes.
+*/
+#ifndef WIN32
+#define DAV_FS_HAS_EXECUTABLE
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME | \
+ APR_FINFO_PROT)
+#else
+/* as above, but without APR_FINFO_PROT */
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME)
+#endif
+
+/*
+** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
+*/
+#define DAV_PROPID_FS_executable 1
+
+/*
+ * prefix for temporary files
+ */
+#define DAV_FS_TMP_PREFIX ".davfs.tmp"
+
+static const dav_liveprop_spec dav_fs_props[] =
+{
+ /* standard DAV properties */
+ {
+ DAV_FS_URI_DAV,
+ "creationdate",
+ DAV_PROPID_creationdate,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getcontentlength",
+ DAV_PROPID_getcontentlength,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getetag",
+ DAV_PROPID_getetag,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getlastmodified",
+ DAV_PROPID_getlastmodified,
+ 0
+ },
+
+ /* our custom properties */
+ {
+ DAV_FS_URI_MYPROPS,
+ "executable",
+ DAV_PROPID_FS_executable,
+ 0 /* handled special in dav_fs_is_writable */
+ },
+
+ { 0 } /* sentinel */
+};
+
+static const dav_liveprop_group dav_fs_liveprop_group =
+{
+ dav_fs_props,
+ dav_fs_namespace_uris,
+ &dav_hooks_liveprop_fs
+};
+
+
+/* define the dav_stream structure for our use */
+struct dav_stream {
+ apr_pool_t *p;
+ apr_file_t *f;
+ const char *pathname; /* we may need to remove it at close time */
+ char *temppath;
+ int unlink_on_error;
+};
+
+/* returns an appropriate HTTP status code given an APR status code for a
+ * failed I/O operation. ### use something besides 500? */
+#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
+ APR_STATUS_IS_ENOENT(e) ? HTTP_CONFLICT : \
+ HTTP_INTERNAL_SERVER_ERROR)
+
+/* forward declaration for internal treewalkers */
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response);
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response);
+
+/* --------------------------------------------------------------------
+**
+** PRIVATE REPOSITORY FUNCTIONS
+*/
+static request_rec *dav_fs_get_request_rec(const dav_resource *resource)
+{
+ return resource->info->r;
+}
+
+apr_pool_t *dav_fs_pool(const dav_resource *resource)
+{
+ return resource->info->pool;
+}
+
+const char *dav_fs_pathname(const dav_resource *resource)
+{
+ return resource->info->pathname;
+}
+
+dav_error * dav_fs_dir_file_name(
+ const dav_resource *resource,
+ const char **dirpath_p,
+ const char **fname_p)
+{
+ dav_resource_private *ctx = resource->info;
+
+ if (resource->collection) {
+ *dirpath_p = ctx->pathname;
+ if (fname_p != NULL)
+ *fname_p = NULL;
+ }
+ else {
+ const char *testpath, *rootpath;
+ char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ apr_size_t dirlen = strlen(dirpath);
+ apr_status_t rv = APR_SUCCESS;
+
+ testpath = dirpath;
+ if (dirlen > 0) {
+ rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
+ }
+
+ /* remove trailing slash from dirpath, unless it's a root path
+ */
+ if ((rv == APR_SUCCESS && testpath && *testpath)
+ || rv == APR_ERELATIVE) {
+ if (dirpath[dirlen - 1] == '/') {
+ dirpath[dirlen - 1] = '\0';
+ }
+ }
+
+ /* ###: Looks like a response could be appropriate
+ *
+ * APR_SUCCESS here tells us the dir is a root
+ * APR_ERELATIVE told us we had no root (ok)
+ * APR_EINCOMPLETE an incomplete testpath told us
+ * there was no -file- name here!
+ * APR_EBADPATH or other errors tell us this file
+ * path is undecipherable
+ */
+
+ if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
+ *dirpath_p = dirpath;
+ if (fname_p != NULL)
+ *fname_p = ctx->pathname + dirlen;
+ }
+ else {
+ return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "An incomplete/bad path was found in "
+ "dav_fs_dir_file_name.");
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: picked up from ap_gm_timestr_822() */
+/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
+static void dav_format_time(int style, apr_time_t sec, char *buf, apr_size_t buflen)
+{
+ apr_time_exp_t tms;
+
+ /* ### what to do if fails? */
+ (void) apr_time_exp_gmt(&tms, sec);
+
+ if (style == DAV_STYLE_ISO8601) {
+ /* ### should we use "-00:00" instead of "Z" ?? */
+
+ /* 20 chars plus null term */
+ apr_snprintf(buf, buflen, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
+ tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+ return;
+ }
+
+ /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
+
+ /* 29 chars plus null term */
+ apr_snprintf(buf, buflen, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
+ apr_day_snames[tms.tm_wday],
+ tms.tm_mday, apr_month_snames[tms.tm_mon],
+ tms.tm_year + 1900,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+}
+
+/* Copy or move src to dst; src_finfo is used to propagate permissions
+ * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
+ * exists. */
+static dav_error * dav_fs_copymove_file(
+ int is_move,
+ apr_pool_t * p,
+ const char *src,
+ const char *dst,
+ const apr_finfo_t *src_finfo,
+ const apr_finfo_t *dst_finfo,
+ dav_buffer *pbuf)
+{
+ dav_buffer work_buf = { 0 };
+ apr_file_t *inf = NULL;
+ apr_file_t *outf = NULL;
+ apr_status_t status;
+ apr_fileperms_t perms;
+
+ if (pbuf == NULL)
+ pbuf = &work_buf;
+
+ /* Determine permissions to use for destination */
+ if (src_finfo && src_finfo->valid & APR_FINFO_PROT
+ && src_finfo->protection & APR_UEXECUTE) {
+ perms = src_finfo->protection;
+
+ if (dst_finfo != NULL) {
+ /* chmod it if it already exist */
+ if ((status = apr_file_perms_set(dst, perms)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set permissions on destination");
+ }
+ }
+ }
+ else {
+ perms = APR_OS_DEFAULT;
+ }
+
+ dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
+
+ if ((status = apr_file_open(&inf, src, APR_READ | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not open file for reading");
+ }
+
+ /* ### do we need to deal with the umask? */
+ status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BINARY, perms, p);
+ if (status != APR_SUCCESS) {
+ apr_file_close(inf);
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not open file for writing");
+ }
+
+ while (1) {
+ apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
+
+ status = apr_file_read(inf, pbuf->buf, &len);
+ if (status != APR_SUCCESS && status != APR_EOF) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after read "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not read input file");
+ }
+
+ if (status == APR_EOF)
+ break;
+
+ /* write any bytes that were read */
+ status = apr_file_write_full(outf, pbuf->buf, len, NULL);
+ if (status != APR_SUCCESS) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after write "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not write output file");
+ }
+ }
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if (is_move && (status = apr_file_remove(src, p)) != APR_SUCCESS) {
+ dav_error *err;
+ apr_status_t lcl_status;
+
+ if (APR_STATUS_IS_ENOENT(status)) {
+ /*
+ * Something is wrong here but the result is what we wanted.
+ * We definitely should not remove the destination file.
+ */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ apr_psprintf(p, "Could not remove source "
+ "file %s after move to %s. The "
+ "server may be in an "
+ "inconsistent state.", src, dst));
+ return err;
+ }
+ else if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK. this creates an inconsistency. do more!? */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, lcl_status,
+ "Could not remove source or destination "
+ "file. Server is now in an inconsistent "
+ "state.");
+ }
+
+ /* ### use something besides 500? */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove source file after move. "
+ "Destination was removed to ensure consistency.");
+ return err;
+ }
+
+ return NULL;
+}
+
+/* copy/move a file from within a state dir to another state dir */
+/* ### need more buffers to replace the pool argument */
+static dav_error * dav_fs_copymove_state(
+ int is_move,
+ apr_pool_t * p,
+ const char *src_dir, const char *src_file,
+ const char *dst_dir, const char *dst_file,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t src_finfo; /* finfo for source file */
+ apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
+ apr_status_t rv;
+ const char *src;
+ const char *dst;
+
+ /* build the propset pathname for the source file */
+ src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
+
+ /* the source file doesn't exist */
+ rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ return NULL;
+ }
+
+ /* build the pathname for the destination state dir */
+ dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* ensure that it exists */
+ rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EEXIST(rv)) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not create internal state directory");
+ }
+ }
+
+ /* get info about the state directory */
+ rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ /* Ack! Where'd it go? */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "State directory disappeared");
+ }
+
+ /* The mkdir() may have failed because a *file* exists there already */
+ if (dst_state_finfo.filetype != APR_DIR) {
+ /* ### try to recover by deleting this file? (and mkdir again) */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "State directory is actually a file");
+ }
+
+ /* append the target file to the state directory pathname */
+ dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
+
+ /* copy/move the file now */
+ if (is_move) {
+ /* try simple rename first */
+ rv = apr_file_rename(src, dst, p);
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+ if (rv != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not move state file.");
+ }
+ }
+ else
+ {
+ /* gotta copy (and delete) */
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+
+ return NULL;
+}
+
+static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
+ const dav_resource *src,
+ const dav_resource *dst,
+ dav_buffer *pbuf)
+{
+ const char *src_dir;
+ const char *src_file;
+ const char *src_state1;
+ const char *src_state2;
+ const char *dst_dir;
+ const char *dst_file;
+ const char *dst_state1;
+ const char *dst_state2;
+ dav_error *err;
+
+ /* Get directory and filename for resources */
+ /* ### should test these result values... */
+ (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
+ (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
+
+ /* Get the corresponding state files for each resource */
+ dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
+ dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
+#if DAV_DEBUG
+ if ((src_state2 != NULL && dst_state2 == NULL) ||
+ (src_state2 == NULL && dst_state2 != NULL)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: dav_dbm_get_statefiles() "
+ "returned inconsistent results.");
+ }
+#endif
+
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state1,
+ dst_dir, dst_state1,
+ pbuf);
+
+ if (err == NULL && src_state2 != NULL) {
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state2,
+ dst_dir, dst_state2,
+ pbuf);
+
+ if (err != NULL) {
+ /* ### CRAP. inconsistency. */
+ /* ### should perform some cleanup at the target if we still
+ ### have the original files */
+
+ /* Change the error to reflect the bad server state. */
+ err->status = HTTP_INTERNAL_SERVER_ERROR;
+ err->desc =
+ "Could not fully copy/move the properties. "
+ "The server is now in an inconsistent state.";
+ }
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *state1;
+ const char *state2;
+ const char *pathname;
+ apr_status_t status;
+
+ /* Get directory, filename, and state-file names for the resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+ dav_dbm_get_statefiles(p, fname, &state1, &state2);
+
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state1,
+ NULL);
+
+ /* note: we may get ENOENT if the state dir is not present */
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove properties.");
+ }
+
+ if (state2 != NULL) {
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state2,
+ NULL);
+
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ /* ### CRAP. only removed half. */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not fully remove properties. "
+ "The server is now in an inconsistent "
+ "state.");
+ }
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------
+**
+** REPOSITORY HOOK FUNCTIONS
+*/
+
+static dav_error * dav_fs_get_resource(
+ request_rec *r,
+ const char *root_dir,
+ const char *label,
+ int use_checked_in,
+ dav_resource **result_resource)
+{
+ dav_resource_private *ctx;
+ dav_resource *resource;
+ char *s;
+ char *filename;
+ apr_size_t len;
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->finfo = r->finfo;
+ ctx->r = r;
+
+ /* ### this should go away */
+ ctx->pool = r->pool;
+
+ /* Preserve case on OSes which fold canonical filenames */
+#if 0
+ /* ### not available in Apache 2.0 yet */
+ filename = r->case_preserved_filename;
+#else
+ filename = r->filename;
+#endif
+
+ /*
+ ** If there is anything in the path_info, then this indicates that the
+ ** entire path was not used to specify the file/dir. We want to append
+ ** it onto the filename so that we get a "valid" pathname for null
+ ** resources.
+ */
+ s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
+
+ /* make sure the pathname does not have a trailing "/" */
+ len = strlen(s);
+ if (len > 1 && s[len - 1] == '/') {
+ s[len - 1] = '\0';
+ }
+ ctx->pathname = s;
+
+ /* Create resource descriptor */
+ resource = apr_pcalloc(r->pool, sizeof(*resource));
+ resource->type = DAV_RESOURCE_TYPE_REGULAR;
+ resource->info = ctx;
+ resource->hooks = &dav_hooks_repository_fs;
+ resource->pool = r->pool;
+
+ /* make sure the URI does not have a trailing "/" */
+ len = strlen(r->uri);
+ if (len > 1 && r->uri[len - 1] == '/') {
+ s = apr_pstrmemdup(r->pool, r->uri, len-1);
+ resource->uri = s;
+ }
+ else {
+ resource->uri = r->uri;
+ }
+
+ if (r->finfo.filetype != APR_NOFILE) {
+ resource->exists = 1;
+ resource->collection = r->finfo.filetype == APR_DIR;
+
+ /* unused info in the URL will indicate a null resource */
+
+ if (r->path_info != NULL && *r->path_info != '\0') {
+ if (resource->collection) {
+ /* only a trailing "/" is allowed */
+ if (*r->path_info != '/' || r->path_info[1] != '\0') {
+
+ /*
+ ** This URL/filename represents a locknull resource or
+ ** possibly a destination of a MOVE/COPY
+ */
+ resource->exists = 0;
+ resource->collection = 0;
+ }
+ }
+ else
+ {
+ /*
+ ** The base of the path refers to a file -- nothing should
+ ** be in path_info. The resource is simply an error: it
+ ** can't be a null or a locknull resource.
+ */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "The URL contains extraneous path "
+ "components. The resource could not "
+ "be identified.");
+ }
+
+ /* retain proper integrity across the structures */
+ if (!resource->exists) {
+ ctx->finfo.filetype = APR_NOFILE;
+ }
+ }
+ }
+
+ *result_resource = resource;
+ return NULL;
+}
+
+static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
+ dav_resource **result_parent)
+{
+ dav_resource_private *ctx = resource->info;
+ dav_resource_private *parent_ctx;
+ dav_resource *parent_resource;
+ apr_status_t rv;
+ char *dirpath;
+ const char *testroot;
+ const char *testpath;
+
+ /* If we're at the root of the URL space, then there is no parent. */
+ if (strcmp(resource->uri, "/") == 0) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* If given resource is root, then there is no parent.
+ * Unless we can retrieve the filepath root, this is
+ * intendend to fail. If we split the root and
+ * no path info remains, then we also fail.
+ */
+ testpath = ctx->pathname;
+ rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
+ if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
+ || !testpath || !*testpath) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
+
+ /* ### this should go away */
+ parent_ctx->pool = ctx->pool;
+
+ dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
+ dirpath[strlen(dirpath) - 1] = '\0';
+ parent_ctx->pathname = dirpath;
+
+ parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
+ parent_resource->info = parent_ctx;
+ parent_resource->collection = 1;
+ parent_resource->hooks = &dav_hooks_repository_fs;
+ parent_resource->pool = resource->pool;
+
+ if (resource->uri != NULL) {
+ char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
+ if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
+ uri[strlen(uri) - 1] = '\0';
+ parent_resource->uri = uri;
+ }
+
+ rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
+ APR_FINFO_NORM, ctx->pool);
+ if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
+ parent_resource->exists = 1;
+ }
+
+ *result_parent = parent_resource;
+ return NULL;
+}
+
+static int dav_fs_is_same_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ if ((ctx1->finfo.filetype != APR_NOFILE) && (ctx2->finfo.filetype != APR_NOFILE)
+ && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
+ return ctx1->finfo.inode == ctx2->finfo.inode;
+ }
+ else {
+ return strcmp(ctx1->pathname, ctx2->pathname) == 0;
+ }
+}
+
+static int dav_fs_is_parent_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+ apr_size_t len1 = strlen(ctx1->pathname);
+ apr_size_t len2;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ /* it is safe to use ctx2 now */
+ len2 = strlen(ctx2->pathname);
+
+ return (len2 > len1
+ && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
+ && ctx2->pathname[len1] == '/');
+}
+
+static apr_status_t tmpfile_cleanup(void *data)
+{
+ dav_stream *ds = data;
+ if (ds->temppath) {
+ apr_file_remove(ds->temppath, ds->p);
+ }
+ return APR_SUCCESS;
+}
+
+/* custom mktemp that creates the file with APR_OS_DEFAULT permissions */
+static apr_status_t dav_fs_mktemp(apr_file_t **fp, char *templ, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int num = ((getpid() << 7) + (apr_uintptr_t)templ % (1 << 16) ) %
+ ( 1 << 23 ) ;
+ char *numstr = templ + strlen(templ) - 6;
+
+ ap_assert(numstr >= templ);
+
+ do {
+ num = (num + 1) % ( 1 << 23 );
+ apr_snprintf(numstr, 7, "%06x", num);
+ rv = apr_file_open(fp, templ,
+ APR_WRITE | APR_CREATE | APR_BINARY | APR_EXCL,
+ APR_OS_DEFAULT, p);
+ } while (APR_STATUS_IS_EEXIST(rv));
+
+ return rv;
+}
+
+static dav_error * dav_fs_open_stream(const dav_resource *resource,
+ dav_stream_mode mode,
+ dav_stream **stream)
+{
+ apr_pool_t *p = resource->info->pool;
+ dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
+ apr_int32_t flags;
+ apr_status_t rv;
+
+ switch (mode) {
+ default:
+ flags = APR_READ | APR_BINARY;
+ break;
+
+ case DAV_MODE_WRITE_TRUNC:
+ flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
+ break;
+ case DAV_MODE_WRITE_SEEKABLE:
+ flags = APR_WRITE | APR_CREATE | APR_BINARY;
+ break;
+ }
+
+ ds->p = p;
+ ds->pathname = resource->info->pathname;
+ ds->temppath = NULL;
+ ds->unlink_on_error = 0;
+
+ if (mode == DAV_MODE_WRITE_TRUNC) {
+ ds->temppath = apr_pstrcat(p, ap_make_dirstr_parent(p, ds->pathname),
+ DAV_FS_TMP_PREFIX "XXXXXX", NULL);
+ rv = dav_fs_mktemp(&ds->f, ds->temppath, ds->p);
+ apr_pool_cleanup_register(p, ds, tmpfile_cleanup,
+ apr_pool_cleanup_null);
+ }
+ else if (mode == DAV_MODE_WRITE_SEEKABLE) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags | APR_FOPEN_EXCL,
+ APR_OS_DEFAULT, ds->p);
+ if (rv == APR_SUCCESS) {
+ /* we have created a new file */
+ ds->unlink_on_error = 1;
+ }
+ else if (APR_STATUS_IS_EEXIST(rv)) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT,
+ ds->p);
+ }
+ }
+ else {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
+ }
+
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ "An error occurred while opening a resource.");
+ }
+
+ /* (APR registers cleanups for the fd with the pool) */
+
+ *stream = ds;
+ return NULL;
+}
+
+static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
+{
+ apr_status_t rv;
+
+ apr_file_close(stream->f);
+
+ if (!commit) {
+ if (stream->temppath) {
+ apr_pool_cleanup_run(stream->p, stream, tmpfile_cleanup);
+ }
+ else if (stream->unlink_on_error) {
+ if ((rv = apr_file_remove(stream->pathname, stream->p))
+ != APR_SUCCESS) {
+ /* ### use a better description? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ rv,
+ "There was a problem removing (rolling "
+ "back) the resource "
+ "when it was being closed.");
+ }
+ }
+ }
+ else if (stream->temppath) {
+ rv = apr_file_rename(stream->temppath, stream->pathname, stream->p);
+ if (rv) {
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "There was a problem writing the file "
+ "atomically after writes.");
+ }
+ apr_pool_cleanup_kill(stream->p, stream, tmpfile_cleanup);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_write_stream(dav_stream *stream,
+ const void *buf, apr_size_t bufsize)
+{
+ apr_status_t status;
+
+ status = apr_file_write_full(stream->f, buf, bufsize, NULL);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to write to "
+ "this resource.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "An error occurred while writing to a "
+ "resource.");
+ }
+ return NULL;
+}
+
+static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
+{
+ apr_status_t status;
+
+ if ((status = apr_file_seek(stream->f, APR_SET, &abs_pos))
+ != APR_SUCCESS) {
+ /* ### should check whether apr_file_seek set abs_pos was set to the
+ * correct position? */
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not seek to specified position in the "
+ "resource.");
+ }
+ return NULL;
+}
+
+
+#if DEBUG_GET_HANDLER
+
+/* only define set_headers() and deliver() for debug purposes */
+
+
+static dav_error * dav_fs_set_headers(request_rec *r,
+ const dav_resource *resource)
+{
+ /* ### this function isn't really used since we have a get_pathname */
+ if (!resource->exists)
+ return NULL;
+
+ /* make sure the proper mtime is in the request record */
+ ap_update_mtime(r, resource->info->finfo.mtime);
+
+ /* ### note that these use r->filename rather than <resource> */
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+
+ /* we accept byte-ranges */
+ ap_set_accept_ranges(r);
+
+ /* set up the Content-Length header */
+ ap_set_content_length(r, resource->info->finfo.size);
+
+ /* ### how to set the content type? */
+ /* ### until this is resolved, the Content-Type header is busted */
+
+ return NULL;
+}
+
+static dav_error * dav_fs_deliver(const dav_resource *resource,
+ ap_filter_t *output)
+{
+ apr_pool_t *pool = resource->pool;
+ apr_bucket_brigade *bb;
+ apr_file_t *fd;
+ apr_status_t status;
+ apr_bucket *bkt;
+
+ /* Check resource type */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_VERSION
+ && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "Cannot GET this type of resource.");
+ }
+ if (resource->collection) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "There is no default response to GET for a "
+ "collection.");
+ }
+
+ if ((status = apr_file_open(&fd, resource->info->pathname,
+ APR_READ | APR_BINARY, 0,
+ pool)) != APR_SUCCESS) {
+ return dav_new_error(pool, HTTP_FORBIDDEN, 0, status,
+ "File permissions deny server access.");
+ }
+
+ bb = apr_brigade_create(pool, output->c->bucket_alloc);
+
+ apr_brigade_insert_file(bb, fd, 0, resource->info->finfo.size, pool);
+
+ bkt = apr_bucket_eos_create(output->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+
+ if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
+ return dav_new_error(pool, AP_FILTER_ERROR, 0, status,
+ "Could not write contents to filter.");
+ }
+
+ return NULL;
+}
+
+#endif /* DEBUG_GET_HANDLER */
+
+
+static dav_error * dav_fs_create_collection(dav_resource *resource)
+{
+ dav_resource_private *ctx = resource->info;
+ apr_status_t status;
+
+ status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to create "
+ "this collection.");
+ }
+ else if (APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(ctx->pool, HTTP_CONFLICT, 0, status,
+ "Cannot create collection; intermediate "
+ "collection does not exist.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### refine this error message? */
+ return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status,
+ "Unable to create collection.");
+ }
+
+ /* update resource state to show it exists as a collection */
+ resource->exists = 1;
+ resource->collection = 1;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
+ int calltype)
+{
+ apr_status_t status;
+ dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
+ dav_resource_private *srcinfo = wres->resource->info;
+ dav_resource_private *dstinfo = ctx->res_dst->info;
+ dav_error *err = NULL;
+
+ if (wres->resource->collection) {
+ if (calltype == DAV_CALLTYPE_POSTFIX) {
+ /* Postfix call for MOVE. delete the source dir.
+ * Note: when copying, we do not enable the postfix-traversal.
+ */
+ /* ### we are ignoring any error here; what should we do? */
+ (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
+ }
+ else {
+ /* copy/move of a collection. Create the new, target collection */
+ if ((status = apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
+ ctx->pool)) != APR_SUCCESS) {
+ /* ### assume it was a permissions problem */
+ /* ### need a description here */
+ err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+ }
+ }
+ else {
+ err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
+ srcinfo->pathname, dstinfo->pathname,
+ &srcinfo->finfo,
+ ctx->res_dst->exists ? &dstinfo->finfo : NULL,
+ &ctx->work_buf);
+ /* ### push a higher-level description? */
+ }
+
+ /*
+ ** If we have a "not so bad" error, then it might need to go into a
+ ** multistatus response.
+ **
+ ** For a MOVE, it will always go into the multistatus. It could be
+ ** that everything has been moved *except* for the root. Using a
+ ** multistatus (with no errors for the other resources) will signify
+ ** this condition.
+ **
+ ** For a COPY, we are traversing in a prefix fashion. If the root fails,
+ ** then we can just bail out now.
+ */
+ if (err != NULL
+ && !ap_is_HTTP_SERVER_ERROR(err->status)
+ && (ctx->is_move
+ || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, err->status, NULL);
+
+ /* the error is in the multistatus now. do not stop the traversal. */
+ return NULL;
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_copymove_resource(
+ int is_move,
+ const dav_resource *src,
+ const dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err = NULL;
+ dav_buffer work_buf = { 0 };
+
+ *response = NULL;
+
+ /* if a collection, recursively copy/move it and its children,
+ * including the state dirs
+ */
+ if (src->collection) {
+ dav_walk_params params = { 0 };
+ dav_response *multi_status;
+
+ params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
+ params.func = dav_fs_copymove_walker;
+ params.pool = src->info->pool;
+ params.root = src;
+
+ /* params.walk_ctx is managed by dav_fs_internal_walk() */
+
+ /* postfix is needed for MOVE to delete source dirs */
+ if (is_move)
+ params.walk_type |= DAV_WALKTYPE_POSTFIX;
+
+ /* note that we return the error OR the multistatus. never both */
+
+ if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the COPY/MOVE process.");
+ }
+
+ return NULL;
+ }
+
+ /* not a collection */
+ if ((err = dav_fs_copymove_file(is_move, src->info->pool,
+ src->info->pathname, dst->info->pathname,
+ &src->info->finfo,
+ dst->exists ? &dst->info->finfo : NULL,
+ &work_buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ /* copy/move properties as well */
+ return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
+}
+
+static dav_error * dav_fs_copy_resource(
+ const dav_resource *src,
+ dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to copy_resource.");
+ }
+#endif
+
+ if ((err = dav_fs_copymove_resource(0, src, dst, depth,
+ response)) == NULL) {
+
+ /* update state of destination resource to show it exists */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ }
+
+ return err;
+}
+
+static dav_error * dav_fs_move_resource(
+ dav_resource *src,
+ dav_resource *dst,
+ dav_response **response)
+{
+ dav_resource_private *srcinfo = src->info;
+ dav_resource_private *dstinfo = dst->info;
+ dav_error *err;
+ apr_status_t rv;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to move_resource.");
+ }
+#endif
+
+
+ /* try rename first */
+ rv = apr_file_rename(srcinfo->pathname, dstinfo->pathname, srcinfo->pool);
+
+ /* if we can't simply rename, then do it the hard way... */
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
+ response)) == NULL) {
+ /* update resource states */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+ }
+
+ return err;
+ }
+
+ /* no multistatus response */
+ *response = NULL;
+
+ if (rv != APR_SUCCESS) {
+ /* ### should have a better error than this. */
+ return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not rename resource.");
+ }
+
+ /* Rename did work. Update resource states and move properties as well */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+
+ if ((err = dav_fs_copymoveset(1, src->info->pool,
+ src, dst, NULL)) == NULL) {
+ /* no error. we're done. go ahead and return now. */
+ return NULL;
+ }
+
+ /* error occurred during properties move; try to put resource back */
+ if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
+ srcinfo->pool) != APR_SUCCESS) {
+ /* couldn't put it back! */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its "
+ "properties. The resource could not be "
+ "restored to its original location. The "
+ "server is now in an inconsistent state.",
+ err);
+ }
+
+ /* update resource states again */
+ src->exists = 1;
+ src->collection = dst->collection;
+ dst->exists = 0;
+ dst->collection = 0;
+
+ /* resource moved back, but properties may be inconsistent */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its properties. "
+ "The resource was moved back to its original "
+ "location, but its properties may have been "
+ "partially moved. The server may be in an "
+ "inconsistent state.",
+ err);
+}
+
+static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_resource_private *info = wres->resource->info;
+
+ /* do not attempt to remove a null resource,
+ * or a collection with children
+ */
+ if (wres->resource->exists &&
+ (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
+ /* try to remove the resource */
+ apr_status_t result;
+
+ result = wres->resource->collection
+ ? apr_dir_remove(info->pathname, wres->pool)
+ : apr_file_remove(info->pathname, wres->pool);
+
+ /*
+ ** If an error occurred, then add it to multistatus response.
+ ** Note that we add it for the root resource, too. It is quite
+ ** possible to delete the whole darn tree, yet fail on the root.
+ **
+ ** (also: remember we are deleting via a postfix traversal)
+ */
+ if (result != APR_SUCCESS) {
+ /* ### assume there is a permissions problem */
+
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, HTTP_FORBIDDEN, NULL);
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_resource(dav_resource *resource,
+ dav_response **response)
+{
+ apr_status_t status;
+ dav_resource_private *info = resource->info;
+
+ *response = NULL;
+
+ /* if a collection, recursively remove it and its children,
+ * including the state dirs
+ */
+ if (resource->collection) {
+ dav_walk_params params = { 0 };
+ dav_error *err = NULL;
+ dav_response *multi_status;
+
+ params.walk_type = (DAV_WALKTYPE_NORMAL
+ | DAV_WALKTYPE_HIDDEN
+ | DAV_WALKTYPE_POSTFIX);
+ params.func = dav_fs_delete_walker;
+ params.pool = info->pool;
+ params.root = resource;
+
+ if ((err = dav_fs_walk(&params, DAV_INFINITY,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the deletion process.");
+ }
+
+ /* no errors... update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ return NULL;
+ }
+
+ /* not a collection; remove the file and its properties */
+ if ((status = apr_file_remove(info->pathname, info->pool)) != APR_SUCCESS) {
+ /* ### put a description in here */
+ return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+
+ /* update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ /* remove properties and return its result */
+ return dav_fs_deleteset(info->pool, resource);
+}
+
+/* ### move this to dav_util? */
+/* Walk recursively down through directories, *
+ * including lock-null resources as we go. */
+static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
+{
+ const dav_walk_params *params = fsctx->params;
+ apr_pool_t *pool = params->pool;
+ apr_status_t status;
+ dav_error *err = NULL;
+ int isdir = fsctx->res1.collection;
+ apr_finfo_t dirent;
+ apr_dir_t *dirp;
+
+ /* ensure the context is prepared properly, then call the func */
+ err = (*params->func)(&fsctx->wres,
+ isdir
+ ? DAV_CALLTYPE_COLLECTION
+ : DAV_CALLTYPE_MEMBER);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (depth == 0 || !isdir) {
+ return NULL;
+ }
+
+ /* put a trailing slash onto the directory, in preparation for appending
+ * files to it as we discovery them within the directory */
+ dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
+ fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
+ fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
+
+ /* if a secondary path is present, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
+ fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
+ fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
+ }
+
+ /* Note: the URI should ALREADY have a trailing "/" */
+
+ /* for this first pass of files, all resources exist */
+ fsctx->res1.exists = 1;
+
+ /* a file is the default; we'll adjust if we hit a directory */
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* open and scan the directory */
+ if ((status = apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
+ /* ### need a better error */
+ return dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ }
+ while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
+ apr_size_t len;
+
+ len = strlen(dirent.name);
+
+ /* avoid recursing into our current, parent, or state directories */
+ if (dirent.name[0] == '.'
+ && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
+ continue;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_AUTH) {
+ /* ### need to authorize each file */
+ /* ### example: .htaccess is normally configured to fail auth */
+
+ /* stuff in the state directory and temp files are never authorized! */
+ if (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX))) {
+ continue;
+ }
+ }
+ /* skip the state dir and temp files unless a HIDDEN is performed */
+ if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
+ && (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX)))) {
+ continue;
+ }
+
+ /* append this file onto the path buffer (copy null term) */
+ dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
+
+ status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
+ DAV_FINFO_MASK, pool);
+ if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ /* woah! where'd it go? */
+ /* ### should have a better error here */
+ err = dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ break;
+ }
+
+ /* copy the file to the URI, too. NOTE: we will pad an extra byte
+ for the trailing slash later. */
+ dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
+
+ /* if there is a secondary path, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /* ### for now, only process regular files (e.g. skip symlinks) */
+ if (fsctx->info1.finfo.filetype == APR_REG) {
+ /* call the function for the specified dir + file */
+ if ((err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_MEMBER)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+ }
+ else if (fsctx->info1.finfo.filetype == APR_DIR) {
+ apr_size_t save_path_len = fsctx->path1.cur_len;
+ apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
+ apr_size_t save_path2_len = fsctx->path2.cur_len;
+
+ /* adjust length to incorporate the subdir name */
+ fsctx->path1.cur_len += len;
+ fsctx->path2.cur_len += len;
+
+ /* adjust URI length to incorporate subdir and a slash */
+ fsctx->uri_buf.cur_len += len + 1;
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
+
+ /* switch over to a collection */
+ fsctx->res1.collection = 1;
+ fsctx->res2.collection = 1;
+
+ /* recurse on the subdir */
+ /* ### don't always want to quit on error from single child */
+ if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+
+ /* put the various information back */
+ fsctx->path1.cur_len = save_path_len;
+ fsctx->path2.cur_len = save_path2_len;
+ fsctx->uri_buf.cur_len = save_uri_len;
+
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* assert: res1.exists == 1 */
+ }
+ }
+
+ /* ### check the return value of this? */
+ apr_dir_close(dirp);
+
+ if (err != NULL)
+ return err;
+
+ if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
+ apr_size_t offset = 0;
+
+ /* null terminate the directory name */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
+
+ /* Include any lock null resources found in this collection */
+ fsctx->res1.collection = 1;
+ if ((err = dav_fs_get_locknull_members(&fsctx->res1,
+ &fsctx->locknull_buf)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* put a slash back on the end of the directory */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
+
+ /* these are all non-existant (files) */
+ fsctx->res1.exists = 0;
+ fsctx->res1.collection = 0;
+ memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
+
+ while (offset < fsctx->locknull_buf.cur_len) {
+ apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
+ dav_lock *locks = NULL;
+
+ /*
+ ** Append the locknull file to the paths and the URI. Note that
+ ** we don't have to pad the URI for a slash since a locknull
+ ** resource is not a collection.
+ */
+ dav_buffer_place_mem(pool, &fsctx->path1,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ dav_buffer_place_mem(pool, &fsctx->uri_buf,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2,
+ fsctx->locknull_buf.buf + offset,
+ len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /*
+ ** To prevent a PROPFIND showing an expired locknull
+ ** resource, query the lock database to force removal
+ ** of both the lock entry and .locknull, if necessary..
+ ** Sure, the query in PROPFIND would do this.. after
+ ** the locknull resource was already included in the
+ ** return.
+ **
+ ** NOTE: we assume the caller has opened the lock database
+ ** if they have provided DAV_WALKTYPE_LOCKNULL.
+ */
+ /* ### we should also look into opening it read-only and
+ ### eliding timed-out items from the walk, yet leaving
+ ### them in the locknull database until somebody opens
+ ### the thing writable.
+ */
+ /* ### probably ought to use has_locks. note the problem
+ ### mentioned above, though... we would traverse this as
+ ### a locknull, but then a PROPFIND would load the lock
+ ### info, causing a timeout and the locks would not be
+ ### reported. Therefore, a null resource would be returned
+ ### in the PROPFIND.
+ ###
+ ### alternative: just load unresolved locks. any direct
+ ### locks will be timed out (correct). any indirect will
+ ### not (correct; consider if a parent timed out -- the
+ ### timeout routines do not walk and remove indirects;
+ ### even the resolve func would probably fail when it
+ ### tried to find a timed-out direct lock).
+ */
+ if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
+ &locks)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* call the function for the specified dir + file */
+ if (locks != NULL &&
+ (err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_LOCKNULL)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ offset += len + 1;
+ }
+
+ /* reset the exists flag */
+ fsctx->res1.exists = 1;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
+ /* replace the dirs' trailing slashes with null terms */
+ fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
+ fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
+ if (fsctx->path2.buf != NULL) {
+ fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
+ }
+
+ /* this is a collection which exists */
+ fsctx->res1.collection = 1;
+
+ return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response)
+{
+ dav_fs_walker_context fsctx = { 0 };
+ dav_error *err;
+ dav_fs_copymove_walk_ctx cm_ctx = { 0 };
+
+#if DAV_DEBUG
+ if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
+ && params->lockdb == NULL) {
+ return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: walker called to walk locknull "
+ "resources, but a lockdb was not provided.");
+ }
+#endif
+
+ fsctx.params = params;
+ fsctx.wres.walk_ctx = params->walk_ctx;
+ fsctx.wres.pool = params->pool;
+
+ /* ### zero out versioned, working, baselined? */
+
+ fsctx.res1 = *params->root;
+ fsctx.res1.pool = params->pool;
+
+ fsctx.res1.info = &fsctx.info1;
+ fsctx.info1 = *params->root->info;
+
+ /* the pathname is stored in the path1 buffer */
+ dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
+ fsctx.info1.pathname = fsctx.path1.buf;
+
+ if (root_dst != NULL) {
+ /* internal call from the COPY/MOVE code. set it up. */
+
+ fsctx.wres.walk_ctx = &cm_ctx;
+ cm_ctx.is_move = is_move;
+ cm_ctx.res_dst = &fsctx.res2;
+ cm_ctx.root = params->root;
+ cm_ctx.pool = params->pool;
+
+ fsctx.res2 = *root_dst;
+ fsctx.res2.exists = 0;
+ fsctx.res2.collection = 0;
+ fsctx.res2.uri = NULL; /* we don't track this */
+ fsctx.res2.pool = params->pool;
+
+ fsctx.res2.info = &fsctx.info2;
+ fsctx.info2 = *root_dst->info;
+
+ /* res2 does not exist -- clear its finfo structure */
+ memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
+
+ /* the pathname is stored in the path2 buffer */
+ dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
+ fsctx.info2.pathname = fsctx.path2.buf;
+ }
+
+ /* prep the URI buffer */
+ dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
+
+ /* if we have a directory, then ensure the URI has a trailing "/" */
+ if (fsctx.res1.collection
+ && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
+
+ /* this will fall into the pad area */
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
+ }
+
+ /* the current resource's URI is stored in the uri_buf buffer */
+ fsctx.res1.uri = fsctx.uri_buf.buf;
+
+ /* point the callback's resource at our structure */
+ fsctx.wres.resource = &fsctx.res1;
+
+ /* always return the error, and any/all multistatus responses */
+ err = dav_fs_walker(&fsctx, depth);
+ *response = fsctx.wres.response;
+ return err;
+}
+
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response)
+{
+ /* always return the error, and any/all multistatus responses */
+ return dav_fs_internal_walk(params, depth, 0, NULL, response);
+}
+
+/* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
+ * for file path.
+ * ### do we need to return weak tags sometimes?
+ */
+static const char *dav_fs_getetag(const dav_resource *resource)
+{
+ dav_resource_private *ctx = resource->info;
+ /* XXX: This should really honor the FileETag setting */
+
+ if (!resource->exists)
+ return apr_pstrdup(ctx->pool, "");
+
+ if (ctx->finfo.filetype != APR_NOFILE) {
+ return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%"
+ APR_UINT64_T_HEX_FMT "\"",
+ (apr_uint64_t) ctx->finfo.size,
+ (apr_uint64_t) ctx->finfo.mtime);
+ }
+
+ return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"",
+ (apr_uint64_t) ctx->finfo.mtime);
+}
+
+static const dav_hooks_repository dav_hooks_repository_fs =
+{
+ DEBUG_GET_HANDLER, /* normally: special GET handling not required */
+ dav_fs_get_resource,
+ dav_fs_get_parent_resource,
+ dav_fs_is_same_resource,
+ dav_fs_is_parent_resource,
+ dav_fs_open_stream,
+ dav_fs_close_stream,
+ dav_fs_write_stream,
+ dav_fs_seek_stream,
+#if DEBUG_GET_HANDLER
+ dav_fs_set_headers,
+ dav_fs_deliver,
+#else
+ NULL,
+ NULL,
+#endif
+ dav_fs_create_collection,
+ dav_fs_copy_resource,
+ dav_fs_move_resource,
+ dav_fs_remove_resource,
+ dav_fs_walk,
+ dav_fs_getetag,
+ NULL,
+ dav_fs_get_request_rec,
+ dav_fs_pathname
+};
+
+static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ const char *value;
+ const char *s;
+ apr_pool_t *p = resource->info->pool;
+ const dav_liveprop_spec *info;
+ int global_ns;
+
+ /* an HTTP-date can be 29 chars plus a null term */
+ /* a 64-bit size can be 20 chars plus a null term */
+ char buf[DAV_TIMEBUF_SIZE];
+
+ /*
+ ** None of FS provider properties are defined if the resource does not
+ ** exist. Just bail for this case.
+ **
+ ** Even though we state that the FS properties are not defined, the
+ ** client cannot store dead values -- we deny that thru the is_writable
+ ** hook function.
+ */
+ if (!resource->exists)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ switch (propid) {
+ case DAV_PROPID_creationdate:
+ /*
+ ** Closest thing to a creation date. since we don't actually
+ ** perform the operations that would modify ctime (after we
+ ** create the file), then we should be pretty safe here.
+ */
+ dav_format_time(DAV_STYLE_ISO8601,
+ resource->info->finfo.ctime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_getcontentlength:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ apr_snprintf(buf, sizeof(buf), "%" APR_OFF_T_FMT, resource->info->finfo.size);
+ value = buf;
+ break;
+
+ case DAV_PROPID_getetag:
+ value = dav_fs_getetag(resource);
+ break;
+
+ case DAV_PROPID_getlastmodified:
+ dav_format_time(DAV_STYLE_RFC822,
+ resource->info->finfo.mtime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_FS_executable:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* our property, but not defined on this platform */
+ if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* the files are "ours" so we only need to check owner exec privs */
+ if (resource->info->finfo.protection & APR_UEXECUTE)
+ value = "T";
+ else
+ value = "F";
+ break;
+
+ default:
+ /* ### what the heck was this property? */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+
+ /* assert: value != NULL */
+
+ /* get the information and global NS index for the property */
+ global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+
+ /* assert: info != NULL && info->name != NULL */
+
+ /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
+
+ if (what == DAV_PROP_INSERT_VALUE) {
+ s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
+ global_ns, info->name, value, global_ns, info->name);
+ }
+ else if (what == DAV_PROP_INSERT_NAME) {
+ s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
+ }
+ else {
+ /* assert: what == DAV_PROP_INSERT_SUPPORTED */
+ s = apr_psprintf(p,
+ "<D:supported-live-property D:name=\"%s\" "
+ "D:namespace=\"%s\"/>" DEBUG_CR,
+ info->name, dav_fs_namespace_uris[info->ns]);
+ }
+ apr_text_append(p, phdr, s);
+
+ /* we inserted what was asked for */
+ return what;
+}
+
+static int dav_fs_is_writable(const dav_resource *resource, int propid)
+{
+ const dav_liveprop_spec *info;
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* if we have the executable property, and this isn't a collection,
+ then the property is writable. */
+ if (propid == DAV_PROPID_FS_executable && !resource->collection)
+ return 1;
+#endif
+
+ (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+ return info->is_writable;
+}
+
+static dav_error *dav_fs_patch_validate(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void **context,
+ int *defer_to_dead)
+{
+ const apr_text *cdata;
+ const apr_text *f_cdata;
+ char value;
+ dav_elem_private *priv = elem->priv;
+
+ if (priv->propid != DAV_PROPID_FS_executable) {
+ *defer_to_dead = 1;
+ return NULL;
+ }
+
+ if (operation == DAV_PROP_OP_DELETE) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property cannot be removed.");
+ }
+
+ cdata = elem->first_cdata.first;
+
+ /* ### hmm. this isn't actually looking at all the possible text items */
+ f_cdata = elem->first_child == NULL
+ ? NULL
+ : elem->first_child->following_cdata.first;
+
+ /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
+
+ if (cdata == NULL) {
+ if (f_cdata == NULL) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. There was no "
+ "value submitted.");
+ }
+ cdata = f_cdata;
+ }
+ else if (f_cdata != NULL)
+ goto too_long;
+
+ if (cdata->next != NULL || strlen(cdata->text) != 1)
+ goto too_long;
+
+ value = cdata->text[0];
+ if (value != 'T' && value != 'F') {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value "
+ "submitted is invalid.");
+ }
+
+ *context = (void *)((long)(value == 'T'));
+
+ return NULL;
+
+ too_long:
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value submitted "
+ "has too many characters.");
+
+}
+
+static dav_error *dav_fs_patch_exec(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void *context,
+ dav_liveprop_rollback **rollback_ctx)
+{
+ long value = context != NULL;
+ apr_fileperms_t perms = resource->info->finfo.protection;
+ apr_status_t status;
+ long old_value = (perms & APR_UEXECUTE) != 0;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* don't do anything if there is no change. no rollback info either. */
+ /* DBG2("new value=%d (old=%d)", value, old_value); */
+ if (value == old_value)
+ return NULL;
+
+ perms &= ~APR_UEXECUTE;
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set the executable flag of the "
+ "target resource.");
+ }
+
+ /* update the resource and set up the rollback context */
+ resource->info->finfo.protection = perms;
+ *rollback_ctx = (dav_liveprop_rollback *)old_value;
+
+ return NULL;
+}
+
+static void dav_fs_patch_commit(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ /* nothing to do */
+}
+
+static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
+ apr_status_t status;
+ int value = rollback_ctx != NULL;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* restore the executable bit */
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "After a failure occurred, the resource's "
+ "executable flag could not be restored.");
+ }
+
+ /* restore the resource's state */
+ resource->info->finfo.protection = perms;
+
+ return NULL;
+}
+
+
+static const dav_hooks_liveprop dav_hooks_liveprop_fs =
+{
+ dav_fs_insert_prop,
+ dav_fs_is_writable,
+ dav_fs_namespace_uris,
+ dav_fs_patch_validate,
+ dav_fs_patch_exec,
+ dav_fs_patch_commit,
+ dav_fs_patch_rollback
+};
+
+static const dav_provider dav_fs_provider =
+{
+ &dav_hooks_repository_fs,
+ &dav_hooks_db_dbm,
+ &dav_hooks_locks_fs,
+ NULL, /* vsn */
+ NULL, /* binding */
+ NULL, /* search */
+
+ NULL /* ctx */
+};
+
+void dav_fs_gather_propsets(apr_array_header_t *uris)
+{
+#ifdef DAV_FS_HAS_EXECUTABLE
+ *(const char **)apr_array_push(uris) =
+ "<http://apache.org/dav/propset/fs/1>";
+#endif
+}
+
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks)
+{
+ /* don't try to find any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return 0;
+ return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
+}
+
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr)
+{
+ /* don't insert any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return;
+
+ if (!resource->exists) {
+ /* a lock-null resource */
+ /*
+ ** ### technically, we should insert empty properties. dunno offhand
+ ** ### what part of the spec said this, but it was essentially thus:
+ ** ### "the properties should be defined, but may have no value".
+ */
+ return;
+ }
+
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
+ what, phdr);
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* Only insert this property if it is defined for this platform. */
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
+ what, phdr);
+#endif
+
+ /* ### we know the others aren't defined as liveprops */
+}
+
+void dav_fs_register(apr_pool_t *p)
+{
+ /* register the namespace URIs */
+ dav_register_liveprop_group(p, &dav_fs_liveprop_group);
+
+ /* register the repository provider */
+ dav_register_provider(p, "filesystem", &dav_fs_provider);
+}
diff --git a/modules/dav/fs/repos.h b/modules/dav/fs/repos.h
new file mode 100644
index 0000000..b164611
--- /dev/null
+++ b/modules/dav/fs/repos.h
@@ -0,0 +1,84 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file repos.h
+ * @brief Declarations for the filesystem repository implementation
+ *
+ * @addtogroup MOD_DAV
+ * @{
+ */
+
+#ifndef _DAV_FS_REPOS_H_
+#define _DAV_FS_REPOS_H_
+
+/* the subdirectory to hold all DAV-related information for a directory */
+#define DAV_FS_STATE_DIR ".DAV"
+#define DAV_FS_STATE_FILE_FOR_DIR ".state_for_dir"
+#define DAV_FS_LOCK_NULL_FILE ".locknull"
+
+
+/* ensure that our state subdirectory is present */
+void dav_fs_ensure_state_dir(apr_pool_t *p, const char *dirname);
+
+/* return the storage pool associated with a resource */
+apr_pool_t *dav_fs_pool(const dav_resource *resource);
+
+/* return the full pathname for a resource */
+const char *dav_fs_pathname(const dav_resource *resource);
+
+/* return the directory and filename for a resource */
+dav_error * dav_fs_dir_file_name(const dav_resource *resource,
+ const char **dirpath,
+ const char **fname);
+
+/* return the list of locknull members in this resource's directory */
+dav_error * dav_fs_get_locknull_members(const dav_resource *resource,
+ dav_buffer *pbuf);
+
+
+/* DBM functions used by the repository and locking providers */
+extern const dav_hooks_db dav_hooks_db_dbm;
+
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb);
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2);
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key);
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value);
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue);
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data);
+int dav_dbm_exists(dav_db *db, apr_datum_t key);
+void dav_dbm_close(dav_db *db);
+
+/* where is the lock database located? */
+const char *dav_get_lockdb_path(const request_rec *r);
+
+const dav_hooks_locks *dav_fs_get_lock_hooks(request_rec *r);
+const dav_hooks_propdb *dav_fs_get_propdb_hooks(request_rec *r);
+
+void dav_fs_gather_propsets(apr_array_header_t *uris);
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks);
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr);
+
+void dav_fs_register(apr_pool_t *p);
+
+#endif /* _DAV_FS_REPOS_H_ */
+/** @} */
+