diff options
Diffstat (limited to 'modules/dav/fs')
-rw-r--r-- | modules/dav/fs/Makefile.in | 3 | ||||
-rw-r--r-- | modules/dav/fs/NWGNUmakefile | 269 | ||||
-rw-r--r-- | modules/dav/fs/config6.m4 | 23 | ||||
-rw-r--r-- | modules/dav/fs/dbm.c | 800 | ||||
-rw-r--r-- | modules/dav/fs/lock.c | 1445 | ||||
-rw-r--r-- | modules/dav/fs/mod_dav_fs.c | 108 | ||||
-rw-r--r-- | modules/dav/fs/mod_dav_fs.dep | 203 | ||||
-rw-r--r-- | modules/dav/fs/mod_dav_fs.dsp | 135 | ||||
-rw-r--r-- | modules/dav/fs/mod_dav_fs.mak | 407 | ||||
-rw-r--r-- | modules/dav/fs/repos.c | 2269 | ||||
-rw-r--r-- | modules/dav/fs/repos.h | 84 |
11 files changed, 5746 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..347d75d --- /dev/null +++ b/modules/dav/fs/dbm.c @@ -0,0 +1,800 @@ +/* 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 "apr_version.h" +#if !APR_VERSION_AT_LEAST(2,0,0) +#include "apu_version.h" +#endif + +#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) +{ +#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) + const apr_dbm_driver_t *driver; + const apu_err_t *err; +#endif + apr_dbm_t *file = NULL; + apr_status_t status; + + *pdb = NULL; + +#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) + if ((status = apr_dbm_get_driver(&driver, NULL, &err, p)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(10289) + "mod_dav_fs: The DBM library '%s' could not be loaded: %s", + err->reason, err->msg); + return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 1, status, + "Could not load library for property database."); + } + if ((status = apr_dbm_open2(&file, driver, pathname, + ro ? APR_DBM_READONLY : APR_DBM_RWCREATE, + APR_OS_DEFAULT, p)) + != APR_SUCCESS && !ro) { + return dav_fs_dbm_error(NULL, p, status); + } +#else + 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); + } +#endif + + /* 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_pstrcat(pool, "<", name+1, "/>" DEBUG_CR, NULL); + } + else { + s = apr_pstrcat(pool, "<ns", name, "/>" DEBUG_CR, NULL); + } + } + else if (*lang != '\0') { + if (*name == ':') { + /* "no namespace" case */ + s = apr_pstrcat(pool, "<", name+1, " xml:lang=\"", + lang, "\">", value, "</", name+1, ">" DEBUG_CR, + NULL); + } + else { + s = apr_pstrcat(pool, "<ns", name, " xml:lang=\"", + lang, "\">", value, "</ns", name, ">" DEBUG_CR, + NULL); + } + } + else if (*name == ':') { + /* "no namespace" case */ + s = apr_pstrcat(pool, "<", name+1, ">", value, "</", name+1, ">" + DEBUG_CR, NULL); + } + else { + s = apr_pstrcat(pool, "<ns", name, ">", value, "</ns", name, ">" + DEBUG_CR, NULL); + } + + 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..ef18c4a --- /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 existent 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..d38868c --- /dev/null +++ b/modules/dav/fs/repos.c @@ -0,0 +1,2269 @@ +/* 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); + if (rv != APR_SUCCESS) { + return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv, + apr_psprintf(p, "Could not open an existing " + "resource for writing: %s.", + ds->pathname)); + } + } + } + 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, + apr_psprintf(p, "Could not open an existing " + "resource for reading: %s.", + ds->pathname)); + } + } + + if (rv != APR_SUCCESS) { + return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv, + apr_psprintf(p, "An error occurred while opening " + "a resource for writing: %s.", + ds->pathname)); + } + + /* (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(¶ms, 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(¶ms, 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-existent (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: Creates an etag for the file path. + */ +static const char *dav_fs_getetag(const dav_resource *resource) +{ + etag_rec er; + + dav_resource_private *ctx = resource->info; + + if (!resource->exists || !ctx->r) { + return ""; + } + + er.vlist_validator = NULL; + er.request_time = ctx->r->request_time; + er.finfo = &ctx->finfo; + er.pathname = ctx->pathname; + er.fd = NULL; + er.force_weak = 0; + + return ap_make_etag_ex(ctx->r, &er); +} + +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; + long 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%ld:%s>%s</lp%ld:%s>" DEBUG_CR, + global_ns, info->name, value, global_ns, info->name); + } + else if (what == DAV_PROP_INSERT_NAME) { + s = apr_psprintf(p, "<lp%ld:%s/>" DEBUG_CR, global_ns, info->name); + } + else { + /* assert: what == DAV_PROP_INSERT_SUPPORTED */ + s = apr_pstrcat(p, + "<D:supported-live-property D:name=\"", + info->name, + "\" D:namespace=\"", + dav_fs_namespace_uris[info->ns], + "\"/>" DEBUG_CR, NULL); + } + 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_ */ +/** @} */ + |