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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17/*
18** DAV extension module for Apache 2.0.*
19**  - Database support using DBM-style databases,
20**    part of the filesystem repository implementation
21*/
22
23/*
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.
30*/
31
32#include "apr_strings.h"
33#include "apr_file_io.h"
34
35#include "apr_dbm.h"
36
37#define APR_WANT_BYTEFUNC
38#include "apr_want.h"       /* for ntohs and htons */
39
40#include "mod_dav.h"
41#include "repos.h"
42#include "http_log.h"
43#include "http_main.h"      /* for ap_server_conf */
44
45APLOG_USE_MODULE(dav_fs);
46
47struct dav_db {
48    apr_pool_t *pool;
49    apr_dbm_t *file;
50
51    /* when used as a property database: */
52
53    int version;                /* *minor* version of this db */
54
55    dav_buffer ns_table;        /* table of namespace URIs */
56    short ns_count;             /* number of entries in table */
57    int ns_table_dirty;         /* ns_table was modified */
58    apr_hash_t *uri_index;      /* map URIs to (1-based) table indices */
59
60    dav_buffer wb_key;          /* work buffer for dav_gdbm_key */
61
62    apr_datum_t iter;           /* iteration key */
63};
64
65/* -------------------------------------------------------------------------
66 *
67 * GENERIC DBM ACCESS
68 *
69 * For the most part, this just uses the APR DBM functions. They are wrapped
70 * a bit with some error handling (using the mod_dav error functions).
71 */
72
73void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
74                            const char **state1, const char **state2)
75{
76    if (fname == NULL)
77        fname = DAV_FS_STATE_FILE_FOR_DIR;
78
79    apr_dbm_get_usednames(p, fname, state1, state2);
80}
81
82static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
83                                    apr_status_t status)
84{
85    int errcode;
86    const char *errstr;
87    dav_error *err;
88    char errbuf[200];
89
90    if (status == APR_SUCCESS)
91        return NULL;
92
93    p = db ? db->pool : p;
94
95    /* There might not be a <db> if we had problems creating it. */
96    if (db == NULL) {
97        errcode = 1;
98        errstr = "Could not open property database.";
99        if (APR_STATUS_IS_EDSOOPEN(status))
100            ap_log_error(APLOG_MARK, APLOG_CRIT, status, ap_server_conf, APLOGNO(00576)
101            "The DBM driver could not be loaded");
102    }
103    else {
104        (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
105        errstr = apr_pstrdup(p, errbuf);
106    }
107
108    err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
109    return err;
110}
111
112/* ensure that our state subdirectory is present */
113/* ### does this belong here or in dav_fs_repos.c ?? */
114void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
115{
116    const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
117
118    /* ### do we need to deal with the umask? */
119
120    /* just try to make it, ignoring any resulting errors */
121    (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
122}
123
124/* dav_dbm_open_direct:  Opens a *dbm database specified by path.
125 *    ro = boolean read-only flag.
126 */
127dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
128                                dav_db **pdb)
129{
130    apr_status_t status;
131    apr_dbm_t *file = NULL;
132
133    *pdb = NULL;
134
135    if ((status = apr_dbm_open(&file, pathname,
136                               ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
137                               APR_OS_DEFAULT, p))
138                != APR_SUCCESS
139        && !ro) {
140        /* ### do something with 'status' */
141
142        /* we can't continue if we couldn't open the file
143           and we need to write */
144        return dav_fs_dbm_error(NULL, p, status);
145    }
146
147    /* may be NULL if we tried to open a non-existent db as read-only */
148    if (file != NULL) {
149        /* we have an open database... return it */
150        *pdb = apr_pcalloc(p, sizeof(**pdb));
151        (*pdb)->pool = p;
152        (*pdb)->file = file;
153    }
154
155    return NULL;
156}
157
158static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
159                                int ro, dav_db **pdb)
160{
161    const char *dirpath;
162    const char *fname;
163    const char *pathname;
164
165    /* Get directory and filename for resource */
166    /* ### should test this result value... */
167    (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
168
169    /* If not opening read-only, ensure the state dir exists */
170    if (!ro) {
171        /* ### what are the perf implications of always checking this? */
172        dav_fs_ensure_state_dir(p, dirpath);
173    }
174
175    pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
176                              fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
177                              NULL);
178
179    /* ### readers cannot open while a writer has this open; we should
180       ### perform a few retries with random pauses. */
181
182    /* ### do we need to deal with the umask? */
183
184    return dav_dbm_open_direct(p, pathname, ro, pdb);
185}
186
187void dav_dbm_close(dav_db *db)
188{
189    apr_dbm_close(db->file);
190}
191
192dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
193{
194    apr_status_t status;
195
196    if (!key.dptr) {
197        /* no key could be created (namespace not known) => no value */
198        memset(pvalue, 0, sizeof(*pvalue));
199        status = APR_SUCCESS;
200    } else {
201        status = apr_dbm_fetch(db->file, key, pvalue);
202    }
203
204    return dav_fs_dbm_error(db, NULL, status);
205}
206
207dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
208{
209    apr_status_t status = apr_dbm_store(db->file, key, value);
210
211    return dav_fs_dbm_error(db, NULL, status);
212}
213
214dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
215{
216    apr_status_t status = apr_dbm_delete(db->file, key);
217
218    return dav_fs_dbm_error(db, NULL, status);
219}
220
221int dav_dbm_exists(dav_db *db, apr_datum_t key)
222{
223    return apr_dbm_exists(db->file, key);
224}
225
226static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
227{
228    apr_status_t status = apr_dbm_firstkey(db->file, pkey);
229
230    return dav_fs_dbm_error(db, NULL, status);
231}
232
233static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
234{
235    apr_status_t status = apr_dbm_nextkey(db->file, pkey);
236
237    return dav_fs_dbm_error(db, NULL, status);
238}
239
240void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
241{
242    apr_dbm_freedatum(db->file, data);
243}
244
245/* -------------------------------------------------------------------------
246 *
247 * PROPERTY DATABASE FUNCTIONS
248 */
249
250
251#define DAV_GDBM_NS_KEY         "METADATA"
252#define DAV_GDBM_NS_KEY_LEN     8
253
254typedef struct {
255    unsigned char major;
256#define DAV_DBVSN_MAJOR         4
257    /*
258    ** V4 -- 0.9.9 ..
259    **       Prior versions could have keys or values with invalid
260    **       namespace prefixes as a result of the xmlns="" form not
261    **       resetting the default namespace to be "no namespace". The
262    **       namespace would be set to "" which is invalid; it should
263    **       be set to "no namespace".
264    **
265    ** V3 -- 0.9.8
266    **       Prior versions could have values with invalid namespace
267    **       prefixes due to an incorrect mapping of input to propdb
268    **       namespace indices. Version bumped to obsolete the old
269    **       values.
270    **
271    ** V2 -- 0.9.7
272    **       This introduced the xml:lang value into the property value's
273    **       record in the propdb.
274    **
275    ** V1 -- .. 0.9.6
276    **       Initial version.
277    */
278
279
280    unsigned char minor;
281#define DAV_DBVSN_MINOR         0
282
283    short ns_count;
284
285} dav_propdb_metadata;
286
287struct dav_deadprop_rollback {
288    apr_datum_t key;
289    apr_datum_t value;
290};
291
292struct dav_namespace_map {
293    int *ns_map;
294};
295
296/*
297** Internal function to build a key
298**
299** WARNING: returns a pointer to a "static" buffer holding the key. The
300**          value must be copied or no longer used if this function is
301**          called again.
302*/
303static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
304{
305    char nsbuf[20];
306    apr_size_t l_ns, l_name = strlen(name->name);
307    apr_datum_t key = { 0 };
308
309    /*
310     * Convert namespace ID to a string. "no namespace" is an empty string,
311     * so the keys will have the form ":name". Otherwise, the keys will
312     * have the form "#:name".
313     */
314    if (*name->ns == '\0') {
315        nsbuf[0] = '\0';
316        l_ns = 0;
317    }
318    else {
319        long ns_id = (long)apr_hash_get(db->uri_index, name->ns,
320                                      APR_HASH_KEY_STRING);
321
322
323        if (ns_id == 0) {
324            /* the namespace was not found(!) */
325            return key;         /* zeroed */
326        }
327
328        l_ns = apr_snprintf(nsbuf, sizeof(nsbuf), "%ld", ns_id - 1);
329    }
330
331    /* assemble: #:name */
332    dav_set_bufsize(db->pool, &db->wb_key, l_ns + 1 + l_name + 1);
333    memcpy(db->wb_key.buf, nsbuf, l_ns);
334    db->wb_key.buf[l_ns] = ':';
335    memcpy(&db->wb_key.buf[l_ns + 1], name->name, l_name + 1);
336
337    /* build the database key */
338    key.dsize = l_ns + 1 + l_name + 1;
339    key.dptr = db->wb_key.buf;
340
341    return key;
342}
343
344static void dav_append_prop(apr_pool_t *pool,
345                            const char *name, const char *value,
346                            apr_text_header *phdr)
347{
348    const char *s;
349    const char *lang = value;
350
351    /* skip past the xml:lang value */
352    value += strlen(lang) + 1;
353
354    if (*value == '\0') {
355        /* the property is an empty value */
356        if (*name == ':') {
357            /* "no namespace" case */
358            s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
359        }
360        else {
361            s = apr_psprintf(pool, "<ns%s/>" DEBUG_CR, name);
362        }
363    }
364    else if (*lang != '\0') {
365        if (*name == ':') {
366            /* "no namespace" case */
367            s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
368                             name+1, lang, value, name+1);
369        }
370        else {
371            s = apr_psprintf(pool, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
372                             name, lang, value, name);
373        }
374    }
375    else if (*name == ':') {
376        /* "no namespace" case */
377        s = apr_psprintf(pool, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
378    }
379    else {
380        s = apr_psprintf(pool, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
381    }
382
383    apr_text_append(pool, phdr, s);
384}
385
386static dav_error * dav_propdb_open(apr_pool_t *pool,
387                                   const dav_resource *resource, int ro,
388                                   dav_db **pdb)
389{
390    dav_db *db;
391    dav_error *err;
392    apr_datum_t key;
393    apr_datum_t value = { 0 };
394
395    *pdb = NULL;
396
397    /*
398    ** Return if an error occurred, or there is no database.
399    **
400    ** NOTE: db could be NULL if we attempted to open a readonly
401    **       database that doesn't exist. If we require read/write
402    **       access, then a database was created and opened.
403    */
404    if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
405        || db == NULL)
406        return err;
407
408    db->uri_index = apr_hash_make(pool);
409
410    key.dptr = DAV_GDBM_NS_KEY;
411    key.dsize = DAV_GDBM_NS_KEY_LEN;
412    if ((err = dav_dbm_fetch(db, key, &value)) != NULL) {
413        /* ### push a higher-level description? */
414        return err;
415    }
416
417    if (value.dptr == NULL) {
418        dav_propdb_metadata m = {
419            DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
420        };
421
422        /*
423        ** If there is no METADATA key, then the database may be
424        ** from versions 0.9.0 .. 0.9.4 (which would be incompatible).
425        ** These can be identified by the presence of an NS_TABLE entry.
426        */
427        key.dptr = "NS_TABLE";
428        key.dsize = 8;
429        if (dav_dbm_exists(db, key)) {
430            dav_dbm_close(db);
431
432            /* call it a major version error */
433            return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
434                                 DAV_ERR_PROP_BAD_MAJOR, 0,
435                                 "Prop database has the wrong major "
436                                 "version number and cannot be used.");
437        }
438
439        /* initialize a new metadata structure */
440        dav_set_bufsize(pool, &db->ns_table, sizeof(m));
441        memcpy(db->ns_table.buf, &m, sizeof(m));
442    }
443    else {
444        dav_propdb_metadata m;
445        long ns;
446        const char *uri;
447
448        dav_set_bufsize(pool, &db->ns_table, value.dsize);
449        memcpy(db->ns_table.buf, value.dptr, value.dsize);
450
451        memcpy(&m, value.dptr, sizeof(m));
452        if (m.major != DAV_DBVSN_MAJOR) {
453            dav_dbm_close(db);
454
455            return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
456                                 DAV_ERR_PROP_BAD_MAJOR, 0,
457                                 "Prop database has the wrong major "
458                                 "version number and cannot be used.");
459        }
460        db->version = m.minor;
461        db->ns_count = ntohs(m.ns_count);
462
463        dav_dbm_freedatum(db, value);
464
465        /* create db->uri_index */
466        for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
467             ns++ < db->ns_count;
468             uri += strlen(uri) + 1) {
469
470            /* we must copy the key, in case ns_table.buf moves */
471            apr_hash_set(db->uri_index,
472                         apr_pstrdup(pool, uri), APR_HASH_KEY_STRING,
473                         (void *)ns);
474        }
475    }
476
477    *pdb = db;
478    return NULL;
479}
480
481static void dav_propdb_close(dav_db *db)
482{
483
484    if (db->ns_table_dirty) {
485        dav_propdb_metadata m;
486        apr_datum_t key;
487        apr_datum_t value;
488        dav_error *err;
489
490        key.dptr = DAV_GDBM_NS_KEY;
491        key.dsize = DAV_GDBM_NS_KEY_LEN;
492
493        value.dptr = db->ns_table.buf;
494        value.dsize = db->ns_table.cur_len;
495
496        /* fill in the metadata that we store into the prop db. */
497        m.major = DAV_DBVSN_MAJOR;
498        m.minor = db->version;          /* ### keep current minor version? */
499        m.ns_count = htons(db->ns_count);
500
501        memcpy(db->ns_table.buf, &m, sizeof(m));
502
503        err = dav_dbm_store(db, key, value);
504        if (err != NULL)
505            ap_log_error(APLOG_MARK, APLOG_WARNING, err->aprerr, ap_server_conf,
506                         APLOGNO(00577) "Error writing propdb: %s", err->desc);
507    }
508
509    dav_dbm_close(db);
510}
511
512static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
513{
514    int ns;
515    const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
516
517    /* within the prop values, we use "ns%d" for prefixes... register them */
518    for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) {
519
520        /* Empty URIs signify the empty namespace. These do not get a
521           namespace prefix. when we generate the value, we will simply
522           leave off the prefix, which is defined by mod_dav to be the
523           empty namespace. */
524        if (*uri == '\0')
525            continue;
526
527        /* ns_table.buf can move, so copy its value (we want the values to
528           last as long as the provided dav_xmlns_info). */
529        dav_xmlns_add(xi,
530                      apr_psprintf(xi->pool, "ns%d", ns),
531                      apr_pstrdup(xi->pool, uri));
532    }
533
534    return NULL;
535}
536
537static dav_error * dav_propdb_output_value(dav_db *db,
538                                           const dav_prop_name *name,
539                                           dav_xmlns_info *xi,
540                                           apr_text_header *phdr,
541                                           int *found)
542{
543    apr_datum_t key = dav_build_key(db, name);
544    apr_datum_t value;
545    dav_error *err;
546
547    if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
548        return err;
549    if (value.dptr == NULL) {
550        *found = 0;
551        return NULL;
552    }
553    *found = 1;
554
555    dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
556
557    dav_dbm_freedatum(db, value);
558
559    return NULL;
560}
561
562static dav_error * dav_propdb_map_namespaces(
563    dav_db *db,
564    const apr_array_header_t *namespaces,
565    dav_namespace_map **mapping)
566{
567    dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
568    int i;
569    int *pmap;
570    const char **puri;
571
572    /*
573    ** Iterate over the provided namespaces. If a namespace already appears
574    ** in our internal map of URI -> ns_id, then store that in the map. If
575    ** we don't know the namespace yet, then add it to the map and to our
576    ** table of known namespaces.
577    */
578    m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
579    for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
580         i-- > 0;
581         ++puri, ++pmap) {
582
583        const char *uri = *puri;
584        apr_size_t uri_len = strlen(uri);
585        long ns_id = (long)apr_hash_get(db->uri_index, uri, uri_len);
586
587        if (ns_id == 0) {
588            dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
589            memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
590            db->ns_table.cur_len += uri_len + 1;
591
592            /* copy the uri in case the passed-in namespaces changes in
593               some way. */
594            apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
595                         (void *)((long)(db->ns_count + 1)));
596
597            db->ns_table_dirty = 1;
598
599            *pmap = db->ns_count++;
600        }
601        else {
602            *pmap = ns_id - 1;
603        }
604    }
605
606    *mapping = m;
607    return NULL;
608}
609
610static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name,
611                                    const apr_xml_elem *elem,
612                                    dav_namespace_map *mapping)
613{
614    apr_datum_t key = dav_build_key(db, name);
615    apr_datum_t value;
616
617    /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
618
619    /* ### use a db- subpool for these values? clear on exit? */
620
621    /* quote all the values in the element */
622    /* ### be nice to do this without affecting the element itself */
623    /* ### of course, the cast indicates Badness is occurring here */
624    apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem);
625
626    /* generate a text blob for the xml:lang plus the contents */
627    apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
628                    mapping->ns_map,
629                    (const char **)&value.dptr, &value.dsize);
630
631    return dav_dbm_store(db, key, value);
632}
633
634static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
635{
636    apr_datum_t key = dav_build_key(db, name);
637    return dav_dbm_delete(db, key);
638}
639
640static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
641{
642    apr_datum_t key = dav_build_key(db, name);
643    return dav_dbm_exists(db, key);
644}
645
646static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
647{
648    const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
649
650    while (ns_id--)
651        p += strlen(p) + 1;
652
653    return p;
654}
655
656static void dav_set_name(dav_db *db, dav_prop_name *pname)
657{
658    const char *s = db->iter.dptr;
659
660    if (s == NULL) {
661        pname->ns = pname->name = NULL;
662    }
663    else if (*s == ':') {
664        pname->ns = "";
665        pname->name = s + 1;
666    }
667    else {
668        int id = atoi(s);
669
670        pname->ns = dav_get_ns_table_uri(db, id);
671        if (s[1] == ':') {
672            pname->name = s + 2;
673        }
674        else {
675            pname->name = ap_strchr_c(s + 2, ':') + 1;
676        }
677    }
678}
679
680static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
681{
682    dav_error *err;
683
684    /* free the previous key. note: if the loop is aborted, then the DBM
685       will toss the key (via pool cleanup) */
686    if (db->iter.dptr != NULL)
687        dav_dbm_freedatum(db, db->iter);
688
689    if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
690        return err;
691
692    /* skip past the METADATA key */
693    if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
694        return dav_propdb_next_name(db, pname);
695
696    dav_set_name(db, pname);
697    return NULL;
698}
699
700static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
701{
702    dav_error *err;
703
704    if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
705        return err;
706
707    /* skip past the METADATA key */
708    if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
709        return dav_propdb_next_name(db, pname);
710
711    dav_set_name(db, pname);
712    return NULL;
713}
714
715static dav_error * dav_propdb_get_rollback(dav_db *db,
716                                           const dav_prop_name *name,
717                                           dav_deadprop_rollback **prollback)
718{
719    dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
720    apr_datum_t key;
721    apr_datum_t value;
722    dav_error *err;
723
724    key = dav_build_key(db, name);
725    rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
726    rb->key.dsize = key.dsize;
727
728    if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
729        return err;
730    if (value.dptr != NULL) {
731        rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
732        rb->value.dsize = value.dsize;
733    }
734
735    *prollback = rb;
736    return NULL;
737}
738
739static dav_error * dav_propdb_apply_rollback(dav_db *db,
740                                             dav_deadprop_rollback *rollback)
741{
742    if (!rollback) {
743        return NULL; /* no rollback, nothing to do */
744    }
745
746    if (rollback->value.dptr == NULL) {
747        /* don't fail if the thing isn't really there. */
748        (void) dav_dbm_delete(db, rollback->key);
749        return NULL;
750    }
751
752    return dav_dbm_store(db, rollback->key, rollback->value);
753}
754
755const dav_hooks_db dav_hooks_db_dbm =
756{
757    dav_propdb_open,
758    dav_propdb_close,
759    dav_propdb_define_namespaces,
760    dav_propdb_output_value,
761    dav_propdb_map_namespaces,
762    dav_propdb_store,
763    dav_propdb_remove,
764    dav_propdb_exists,
765    dav_propdb_first_name,
766    dav_propdb_next_name,
767    dav_propdb_get_rollback,
768    dav_propdb_apply_rollback,
769
770    NULL /* ctx */
771};
772