1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 ** DAV extension module for Apache 2.0.*
19 ** - Database support using DBM-style databases,
20 ** part of the filesystem repository implementation
24 ** This implementation uses a SDBM database per file and directory to
25 ** record the properties. These databases are kept in a subdirectory (of
26 ** the directory in question or the directory that holds the file in
27 ** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
28 ** database is equivalent to the target filename, and is
29 ** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
32 #include "apr_strings.h"
33 #include "apr_file_io.h"
37 #define APR_WANT_BYTEFUNC
38 #include "apr_want.h" /* for ntohs and htons */
48 /* when used as a property database: */
50 int version; /* *minor* version of this db */
52 dav_buffer ns_table; /* table of namespace URIs */
53 short ns_count; /* number of entries in table */
54 int ns_table_dirty; /* ns_table was modified */
55 apr_hash_t *uri_index; /* map URIs to (1-based) table indices */
57 dav_buffer wb_key; /* work buffer for dav_gdbm_key */
59 apr_datum_t iter; /* iteration key */
62 /* -------------------------------------------------------------------------
66 * For the most part, this just uses the APR DBM functions. They are wrapped
67 * a bit with some error handling (using the mod_dav error functions).
70 void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
71 const char **state1, const char **state2)
74 fname = DAV_FS_STATE_FILE_FOR_DIR;
76 apr_dbm_get_usednames(p, fname, state1, state2);
79 static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
82 int save_errno = errno;
88 if (status == APR_SUCCESS)
91 p = db ? db->pool : p;
93 /* There might not be a <db> if we had problems creating it. */
96 errstr = "Could not open property database.";
99 (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
100 errstr = apr_pstrdup(p, errbuf);
103 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr);
104 err->save_errno = save_errno;
108 /* ensure that our state subdirectory is present */
109 /* ### does this belong here or in dav_fs_repos.c ?? */
110 void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
112 const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
114 /* ### do we need to deal with the umask? */
116 /* just try to make it, ignoring any resulting errors */
117 (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
120 /* dav_dbm_open_direct: Opens a *dbm database specified by path.
121 * ro = boolean read-only flag.
123 dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
131 if ((status = apr_dbm_open(&file, pathname,
132 ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
136 /* ### do something with 'status' */
138 /* we can't continue if we couldn't open the file
139 and we need to write */
140 return dav_fs_dbm_error(NULL, p, status);
143 /* may be NULL if we tried to open a non-existent db as read-only */
145 /* we have an open database... return it */
146 *pdb = apr_pcalloc(p, sizeof(**pdb));
154 static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
155 int ro, dav_db **pdb)
159 const char *pathname;
161 /* Get directory and filename for resource */
162 /* ### should test this result value... */
163 (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
165 /* If not opening read-only, ensure the state dir exists */
167 /* ### what are the perf implications of always checking this? */
168 dav_fs_ensure_state_dir(p, dirpath);
171 pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
172 fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
175 /* ### readers cannot open while a writer has this open; we should
176 ### perform a few retries with random pauses. */
178 /* ### do we need to deal with the umask? */
180 return dav_dbm_open_direct(p, pathname, ro, pdb);
183 void dav_dbm_close(dav_db *db)
185 apr_dbm_close(db->file);
188 dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
190 apr_status_t status = apr_dbm_fetch(db->file, key, pvalue);
192 return dav_fs_dbm_error(db, NULL, status);
195 dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
197 apr_status_t status = apr_dbm_store(db->file, key, value);
199 return dav_fs_dbm_error(db, NULL, status);
202 dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
204 apr_status_t status = apr_dbm_delete(db->file, key);
206 return dav_fs_dbm_error(db, NULL, status);
209 int dav_dbm_exists(dav_db *db, apr_datum_t key)
211 return apr_dbm_exists(db->file, key);
214 static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
216 apr_status_t status = apr_dbm_firstkey(db->file, pkey);
218 return dav_fs_dbm_error(db, NULL, status);
221 static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
223 apr_status_t status = apr_dbm_nextkey(db->file, pkey);
225 return dav_fs_dbm_error(db, NULL, status);
228 void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
230 apr_dbm_freedatum(db->file, data);
233 /* -------------------------------------------------------------------------
235 * PROPERTY DATABASE FUNCTIONS
239 #define DAV_GDBM_NS_KEY "METADATA"
240 #define DAV_GDBM_NS_KEY_LEN 8
244 #define DAV_DBVSN_MAJOR 4
247 ** Prior versions could have keys or values with invalid
248 ** namespace prefixes as a result of the xmlns="" form not
249 ** resetting the default namespace to be "no namespace". The
250 ** namespace would be set to "" which is invalid; it should
251 ** be set to "no namespace".
254 ** Prior versions could have values with invalid namespace
255 ** prefixes due to an incorrect mapping of input to propdb
256 ** namespace indices. Version bumped to obsolete the old
260 ** This introduced the xml:lang value into the property value's
261 ** record in the propdb.
269 #define DAV_DBVSN_MINOR 0
273 } dav_propdb_metadata;
275 struct dav_deadprop_rollback {
280 struct dav_namespace_map {
285 ** Internal function to build a key
287 ** WARNING: returns a pointer to a "static" buffer holding the key. The
288 ** value must be copied or no longer used if this function is
291 static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
294 apr_size_t l_ns, l_name = strlen(name->name);
295 apr_datum_t key = { 0 };
298 * Convert namespace ID to a string. "no namespace" is an empty string,
299 * so the keys will have the form ":name". Otherwise, the keys will
300 * have the form "#:name".
302 if (*name->ns == '\0') {
307 int ns_id = (int)apr_hash_get(db->uri_index, name->ns,
308 APR_HASH_KEY_STRING);
312 /* the namespace was not found(!) */
313 return key; /* zeroed */
316 l_ns = sprintf(nsbuf, "%d", ns_id - 1);
319 /* assemble: #:name */
320 dav_set_bufsize(db->pool, &db->wb_key, l_ns + 1 + l_name + 1);
321 memcpy(db->wb_key.buf, nsbuf, l_ns);
322 db->wb_key.buf[l_ns] = ':';
323 memcpy(&db->wb_key.buf[l_ns + 1], name->name, l_name + 1);
325 /* build the database key */
326 key.dsize = l_ns + 1 + l_name + 1;
327 key.dptr = db->wb_key.buf;
332 static void dav_append_prop(apr_pool_t *pool,
333 const char *name, const char *value,
334 apr_text_header *phdr)
337 const char *lang = value;
339 /* skip past the xml:lang value */
340 value += strlen(lang) + 1;
342 if (*value == '\0') {
343 /* the property is an empty value */
345 /* "no namespace" case */
346 s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
349 s = apr_psprintf(pool, "<ns%s/>" DEBUG_CR, name);
352 else if (*lang != '\0') {
354 /* "no namespace" case */
355 s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
356 name+1, lang, value, name+1);
359 s = apr_psprintf(pool, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
360 name, lang, value, name);
363 else if (*name == ':') {
364 /* "no namespace" case */
365 s = apr_psprintf(pool, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
368 s = apr_psprintf(pool, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
371 apr_text_append(pool, phdr, s);
374 static dav_error * dav_propdb_open(apr_pool_t *pool,
375 const dav_resource *resource, int ro,
381 apr_datum_t value = { 0 };
386 ** Return if an error occurred, or there is no database.
388 ** NOTE: db could be NULL if we attempted to open a readonly
389 ** database that doesn't exist. If we require read/write
390 ** access, then a database was created and opened.
392 if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
396 db->uri_index = apr_hash_make(pool);
398 key.dptr = DAV_GDBM_NS_KEY;
399 key.dsize = DAV_GDBM_NS_KEY_LEN;
400 if ((err = dav_dbm_fetch(db, key, &value)) != NULL) {
401 /* ### push a higher-level description? */
405 if (value.dptr == NULL) {
406 dav_propdb_metadata m = {
407 DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
411 ** If there is no METADATA key, then the database may be
412 ** from versions 0.9.0 .. 0.9.4 (which would be incompatible).
413 ** These can be identified by the presence of an NS_TABLE entry.
415 key.dptr = "NS_TABLE";
417 if (dav_dbm_exists(db, key)) {
420 /* call it a major version error */
421 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
422 DAV_ERR_PROP_BAD_MAJOR,
423 "Prop database has the wrong major "
424 "version number and cannot be used.");
427 /* initialize a new metadata structure */
428 dav_set_bufsize(pool, &db->ns_table, sizeof(m));
429 memcpy(db->ns_table.buf, &m, sizeof(m));
432 dav_propdb_metadata m;
436 dav_set_bufsize(pool, &db->ns_table, value.dsize);
437 memcpy(db->ns_table.buf, value.dptr, value.dsize);
439 memcpy(&m, value.dptr, sizeof(m));
440 if (m.major != DAV_DBVSN_MAJOR) {
443 return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
444 DAV_ERR_PROP_BAD_MAJOR,
445 "Prop database has the wrong major "
446 "version number and cannot be used.");
448 db->version = m.minor;
449 db->ns_count = ntohs(m.ns_count);
451 dav_dbm_freedatum(db, value);
453 /* create db->uri_index */
454 for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
456 uri += strlen(uri) + 1) {
458 /* we must copy the key, in case ns_table.buf moves */
459 apr_hash_set(db->uri_index,
460 apr_pstrdup(pool, uri), APR_HASH_KEY_STRING,
469 static void dav_propdb_close(dav_db *db)
472 if (db->ns_table_dirty) {
473 dav_propdb_metadata m;
478 key.dptr = DAV_GDBM_NS_KEY;
479 key.dsize = DAV_GDBM_NS_KEY_LEN;
481 value.dptr = db->ns_table.buf;
482 value.dsize = db->ns_table.cur_len;
484 /* fill in the metadata that we store into the prop db. */
485 m.major = DAV_DBVSN_MAJOR;
486 m.minor = db->version; /* ### keep current minor version? */
487 m.ns_count = htons(db->ns_count);
489 memcpy(db->ns_table.buf, &m, sizeof(m));
491 err = dav_dbm_store(db, key, value);
492 /* ### what to do with the error? */
498 static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
501 const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
503 /* within the prop values, we use "ns%d" for prefixes... register them */
504 for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) {
506 /* Empty URIs signify the empty namespace. These do not get a
507 namespace prefix. when we generate the value, we will simply
508 leave off the prefix, which is defined by mod_dav to be the
513 /* ns_table.buf can move, so copy its value (we want the values to
514 last as long as the provided dav_xmlns_info). */
516 apr_psprintf(xi->pool, "ns%d", ns),
517 apr_pstrdup(xi->pool, uri));
523 static dav_error * dav_propdb_output_value(dav_db *db,
524 const dav_prop_name *name,
526 apr_text_header *phdr,
529 apr_datum_t key = dav_build_key(db, name);
533 if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
535 if (value.dptr == NULL) {
541 dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
543 dav_dbm_freedatum(db, value);
548 static dav_error * dav_propdb_map_namespaces(
550 const apr_array_header_t *namespaces,
551 dav_namespace_map **mapping)
553 dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
559 ** Iterate over the provided namespaces. If a namespace already appears
560 ** in our internal map of URI -> ns_id, then store that in the map. If
561 ** we don't know the namespace yet, then add it to the map and to our
562 ** table of known namespaces.
564 m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
565 for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
569 const char *uri = *puri;
570 apr_size_t uri_len = strlen(uri);
571 int ns_id = (int)apr_hash_get(db->uri_index, uri, uri_len);
574 dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
575 memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
576 db->ns_table.cur_len += uri_len + 1;
578 /* copy the uri in case the passed-in namespaces changes in
580 apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
581 (void *)(db->ns_count + 1));
583 db->ns_table_dirty = 1;
585 *pmap = db->ns_count++;
596 static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name,
597 const apr_xml_elem *elem,
598 dav_namespace_map *mapping)
600 apr_datum_t key = dav_build_key(db, name);
603 /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
605 /* ### use a db- subpool for these values? clear on exit? */
607 /* quote all the values in the element */
608 /* ### be nice to do this without affecting the element itself */
609 /* ### of course, the cast indicates Badness is occurring here */
610 apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem);
612 /* generate a text blob for the xml:lang plus the contents */
613 apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
615 (const char **)&value.dptr, &value.dsize);
617 return dav_dbm_store(db, key, value);
620 static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
622 apr_datum_t key = dav_build_key(db, name);
623 return dav_dbm_delete(db, key);
626 static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
628 apr_datum_t key = dav_build_key(db, name);
629 return dav_dbm_exists(db, key);
632 static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
634 const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
642 static void dav_set_name(dav_db *db, dav_prop_name *pname)
644 const char *s = db->iter.dptr;
647 pname->ns = pname->name = NULL;
649 else if (*s == ':') {
656 pname->ns = dav_get_ns_table_uri(db, id);
661 pname->name = ap_strchr_c(s + 2, ':') + 1;
666 static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
670 /* free the previous key. note: if the loop is aborted, then the DBM
671 will toss the key (via pool cleanup) */
672 if (db->iter.dptr != NULL)
673 dav_dbm_freedatum(db, db->iter);
675 if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
678 /* skip past the METADATA key */
679 if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
680 return dav_propdb_next_name(db, pname);
682 dav_set_name(db, pname);
686 static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
690 if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
693 /* skip past the METADATA key */
694 if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
695 return dav_propdb_next_name(db, pname);
697 dav_set_name(db, pname);
701 static dav_error * dav_propdb_get_rollback(dav_db *db,
702 const dav_prop_name *name,
703 dav_deadprop_rollback **prollback)
705 dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
710 key = dav_build_key(db, name);
711 rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
712 rb->key.dsize = key.dsize;
714 if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
716 if (value.dptr != NULL) {
717 rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
718 rb->value.dsize = value.dsize;
725 static dav_error * dav_propdb_apply_rollback(dav_db *db,
726 dav_deadprop_rollback *rollback)
728 if (rollback->value.dptr == NULL) {
729 /* don't fail if the thing isn't really there. */
730 (void) dav_dbm_delete(db, rollback->key);
734 return dav_dbm_store(db, rollback->key, rollback->value);
737 const dav_hooks_db dav_hooks_db_dbm =
741 dav_propdb_define_namespaces,
742 dav_propdb_output_value,
743 dav_propdb_map_namespaces,
747 dav_propdb_first_name,
748 dav_propdb_next_name,
749 dav_propdb_get_rollback,
750 dav_propdb_apply_rollback,