bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / dav / fs / dbm.c
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
43
44 struct dav_db {
45     apr_pool_t *pool;
46     apr_dbm_t *file;
47
48     /* when used as a property database: */
49
50     int version;                /* *minor* version of this db */
51
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 */
56
57     dav_buffer wb_key;          /* work buffer for dav_gdbm_key */
58
59     apr_datum_t iter;           /* iteration key */
60 };
61
62 /* -------------------------------------------------------------------------
63  *
64  * GENERIC DBM ACCESS
65  *
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).
68  */
69
70 void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
71                             const char **state1, const char **state2)
72 {
73     if (fname == NULL)
74         fname = DAV_FS_STATE_FILE_FOR_DIR;
75
76     apr_dbm_get_usednames(p, fname, state1, state2);
77 }
78
79 static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
80                                     apr_status_t status)
81 {
82     int save_errno = errno;
83     int errcode;
84     const char *errstr;
85     dav_error *err;
86     char errbuf[200];
87
88     if (status == APR_SUCCESS)
89         return NULL;
90
91     p = db ? db->pool : p;
92
93     /* There might not be a <db> if we had problems creating it. */
94     if (db == NULL) {
95         errcode = 1;
96         errstr = "Could not open property database.";
97     }
98     else {
99         (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
100         errstr = apr_pstrdup(p, errbuf);
101     }
102
103     err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr);
104     err->save_errno = save_errno;
105     return err;
106 }
107
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)
111 {
112     const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
113
114     /* ### do we need to deal with the umask? */
115
116     /* just try to make it, ignoring any resulting errors */
117     (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
118 }
119
120 /* dav_dbm_open_direct:  Opens a *dbm database specified by path.
121  *    ro = boolean read-only flag.
122  */
123 dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
124                                 dav_db **pdb)
125 {
126     apr_status_t status;
127     apr_dbm_t *file;
128
129     *pdb = NULL;
130
131     if ((status = apr_dbm_open(&file, pathname,
132                                ro ? APR_DBM_READONLY : APR_DBM_RWCREATE, 
133                                APR_OS_DEFAULT, p))
134                 != APR_SUCCESS
135         && !ro) {
136         /* ### do something with 'status' */
137
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);
141     }
142
143     /* may be NULL if we tried to open a non-existent db as read-only */
144     if (file != NULL) {
145         /* we have an open database... return it */
146         *pdb = apr_pcalloc(p, sizeof(**pdb));
147         (*pdb)->pool = p;
148         (*pdb)->file = file;
149     }
150
151     return NULL;
152 }
153
154 static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
155                                 int ro, dav_db **pdb)
156 {
157     const char *dirpath;
158     const char *fname;
159     const char *pathname;
160
161     /* Get directory and filename for resource */
162     /* ### should test this result value... */
163     (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
164
165     /* If not opening read-only, ensure the state dir exists */
166     if (!ro) {
167         /* ### what are the perf implications of always checking this? */
168         dav_fs_ensure_state_dir(p, dirpath);
169     }
170
171     pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
172                               fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
173                               NULL);
174
175     /* ### readers cannot open while a writer has this open; we should
176        ### perform a few retries with random pauses. */
177
178     /* ### do we need to deal with the umask? */
179
180     return dav_dbm_open_direct(p, pathname, ro, pdb);
181 }
182
183 void dav_dbm_close(dav_db *db)
184 {
185     apr_dbm_close(db->file);
186 }
187
188 dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
189 {
190     apr_status_t status = apr_dbm_fetch(db->file, key, pvalue);
191
192     return dav_fs_dbm_error(db, NULL, status);
193 }
194
195 dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
196 {
197     apr_status_t status = apr_dbm_store(db->file, key, value);
198
199     return dav_fs_dbm_error(db, NULL, status);
200 }
201
202 dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
203 {
204     apr_status_t status = apr_dbm_delete(db->file, key);
205
206     return dav_fs_dbm_error(db, NULL, status);
207 }
208
209 int dav_dbm_exists(dav_db *db, apr_datum_t key)
210 {
211     return apr_dbm_exists(db->file, key);
212 }
213
214 static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
215 {
216     apr_status_t status = apr_dbm_firstkey(db->file, pkey);
217
218     return dav_fs_dbm_error(db, NULL, status);
219 }
220
221 static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
222 {
223     apr_status_t status = apr_dbm_nextkey(db->file, pkey);
224
225     return dav_fs_dbm_error(db, NULL, status);
226 }
227
228 void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
229 {
230     apr_dbm_freedatum(db->file, data);
231 }
232
233 /* -------------------------------------------------------------------------
234  *
235  * PROPERTY DATABASE FUNCTIONS
236  */
237
238
239 #define DAV_GDBM_NS_KEY         "METADATA"
240 #define DAV_GDBM_NS_KEY_LEN     8
241
242 typedef struct {
243     unsigned char major;
244 #define DAV_DBVSN_MAJOR         4
245     /*
246     ** V4 -- 0.9.9 ..
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".
252     **
253     ** V3 -- 0.9.8
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
257     **       values.
258     **
259     ** V2 -- 0.9.7
260     **       This introduced the xml:lang value into the property value's
261     **       record in the propdb.
262     **
263     ** V1 -- .. 0.9.6
264     **       Initial version.
265     */
266
267
268     unsigned char minor;
269 #define DAV_DBVSN_MINOR         0
270
271     short ns_count;
272
273 } dav_propdb_metadata;
274
275 struct dav_deadprop_rollback {
276     apr_datum_t key;
277     apr_datum_t value;
278 };
279
280 struct dav_namespace_map {
281     int *ns_map;
282 };
283
284 /*
285 ** Internal function to build a key
286 **
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
289 **          called again.
290 */
291 static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
292 {
293     char nsbuf[20];
294     apr_size_t l_ns, l_name = strlen(name->name);
295     apr_datum_t key = { 0 };
296
297     /*
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".
301      */
302     if (*name->ns == '\0') {
303         nsbuf[0] = '\0';
304         l_ns = 0;
305     }
306     else {
307         int ns_id = (int)apr_hash_get(db->uri_index, name->ns,
308                                       APR_HASH_KEY_STRING);
309
310
311         if (ns_id == 0) {
312             /* the namespace was not found(!) */
313             return key;         /* zeroed */
314         }
315
316         l_ns = sprintf(nsbuf, "%d", ns_id - 1);
317     }
318
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);
324
325     /* build the database key */
326     key.dsize = l_ns + 1 + l_name + 1;
327     key.dptr = db->wb_key.buf;
328
329     return key;
330 }
331
332 static void dav_append_prop(apr_pool_t *pool,
333                             const char *name, const char *value,
334                             apr_text_header *phdr)
335 {
336     const char *s;
337     const char *lang = value;
338
339     /* skip past the xml:lang value */
340     value += strlen(lang) + 1;
341
342     if (*value == '\0') {
343         /* the property is an empty value */
344         if (*name == ':') {
345             /* "no namespace" case */
346             s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
347         }
348         else {
349             s = apr_psprintf(pool, "<ns%s/>" DEBUG_CR, name);
350         }
351     }
352     else if (*lang != '\0') {
353         if (*name == ':') {
354             /* "no namespace" case */
355             s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
356                              name+1, lang, value, name+1);
357         }
358         else {
359             s = apr_psprintf(pool, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
360                              name, lang, value, name);
361         }
362     }
363     else if (*name == ':') {
364         /* "no namespace" case */
365         s = apr_psprintf(pool, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
366     }
367     else {
368         s = apr_psprintf(pool, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
369     }
370
371     apr_text_append(pool, phdr, s);
372 }
373
374 static dav_error * dav_propdb_open(apr_pool_t *pool,
375                                    const dav_resource *resource, int ro,
376                                    dav_db **pdb)
377 {
378     dav_db *db;
379     dav_error *err;
380     apr_datum_t key;
381     apr_datum_t value = { 0 };
382
383     *pdb = NULL;
384
385     /*
386     ** Return if an error occurred, or there is no database.
387     **
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.
391     */
392     if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
393         || db == NULL)
394         return err;
395
396     db->uri_index = apr_hash_make(pool);
397
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? */
402         return err;
403     }
404
405     if (value.dptr == NULL) {
406         dav_propdb_metadata m = {
407             DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
408         };
409
410         /*
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.
414         */
415         key.dptr = "NS_TABLE";
416         key.dsize = 8;
417         if (dav_dbm_exists(db, key)) {
418             dav_dbm_close(db);
419
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.");
425         }
426
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));
430     }
431     else {
432         dav_propdb_metadata m;
433         int ns;
434         const char *uri;
435
436         dav_set_bufsize(pool, &db->ns_table, value.dsize);
437         memcpy(db->ns_table.buf, value.dptr, value.dsize);
438
439         memcpy(&m, value.dptr, sizeof(m));
440         if (m.major != DAV_DBVSN_MAJOR) {
441             dav_dbm_close(db);
442
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.");
447         }
448         db->version = m.minor;
449         db->ns_count = ntohs(m.ns_count);
450
451         dav_dbm_freedatum(db, value);
452
453         /* create db->uri_index */
454         for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
455              ns++ < db->ns_count;
456              uri += strlen(uri) + 1) {
457
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,
461                          (void *)ns);
462         }
463     }
464
465     *pdb = db;
466     return NULL;
467 }
468
469 static void dav_propdb_close(dav_db *db)
470 {
471
472     if (db->ns_table_dirty) {
473         dav_propdb_metadata m;
474         apr_datum_t key;
475         apr_datum_t value;
476         dav_error *err;
477
478         key.dptr = DAV_GDBM_NS_KEY;
479         key.dsize = DAV_GDBM_NS_KEY_LEN;
480
481         value.dptr = db->ns_table.buf;
482         value.dsize = db->ns_table.cur_len;
483
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);
488
489         memcpy(db->ns_table.buf, &m, sizeof(m));
490
491         err = dav_dbm_store(db, key, value);
492         /* ### what to do with the error? */
493     }
494
495     dav_dbm_close(db);
496 }
497
498 static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
499 {
500     int ns;
501     const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
502
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) {
505
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
509            empty namespace. */
510         if (*uri == '\0')
511             continue;
512
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). */
515         dav_xmlns_add(xi,
516                       apr_psprintf(xi->pool, "ns%d", ns),
517                       apr_pstrdup(xi->pool, uri));
518     }
519
520     return NULL;
521 }
522
523 static dav_error * dav_propdb_output_value(dav_db *db,
524                                            const dav_prop_name *name,
525                                            dav_xmlns_info *xi,
526                                            apr_text_header *phdr,
527                                            int *found)
528 {
529     apr_datum_t key = dav_build_key(db, name);
530     apr_datum_t value;
531     dav_error *err;
532
533     if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
534         return err;
535     if (value.dptr == NULL) {
536         *found = 0;
537         return NULL;
538     }
539     *found = 1;
540
541     dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
542
543     dav_dbm_freedatum(db, value);
544
545     return NULL;
546 }
547
548 static dav_error * dav_propdb_map_namespaces(
549     dav_db *db,
550     const apr_array_header_t *namespaces,
551     dav_namespace_map **mapping)
552 {
553     dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
554     int i;
555     int *pmap;
556     const char **puri;
557
558     /*
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.
563     */
564     m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
565     for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
566          i-- > 0;
567          ++puri, ++pmap) {
568
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);
572
573         if (ns_id == 0) {
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;
577
578             /* copy the uri in case the passed-in namespaces changes in
579                some way. */
580             apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
581                          (void *)(db->ns_count + 1));
582
583             db->ns_table_dirty = 1;
584
585             *pmap = db->ns_count++;
586         }
587         else {
588             *pmap = ns_id - 1;
589         }
590     }
591
592     *mapping = m;
593     return NULL;
594 }
595
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)
599 {
600     apr_datum_t key = dav_build_key(db, name);
601     apr_datum_t value;
602
603     /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
604
605     /* ### use a db- subpool for these values? clear on exit? */
606
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);
611
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,
614                     mapping->ns_map,
615                     (const char **)&value.dptr, &value.dsize);
616
617     return dav_dbm_store(db, key, value);
618 }
619
620 static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
621 {
622     apr_datum_t key = dav_build_key(db, name);
623     return dav_dbm_delete(db, key);
624 }
625
626 static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
627 {
628     apr_datum_t key = dav_build_key(db, name);
629     return dav_dbm_exists(db, key);
630 }
631
632 static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
633 {
634     const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
635
636     while (ns_id--)
637         p += strlen(p) + 1;
638
639     return p;
640 }
641
642 static void dav_set_name(dav_db *db, dav_prop_name *pname)
643 {
644     const char *s = db->iter.dptr;
645
646     if (s == NULL) {
647         pname->ns = pname->name = NULL;
648     }
649     else if (*s == ':') {
650         pname->ns = "";
651         pname->name = s + 1;
652     }
653     else {
654         int id = atoi(s);
655
656         pname->ns = dav_get_ns_table_uri(db, id);
657         if (s[1] == ':') {
658             pname->name = s + 2;
659         }
660         else {
661             pname->name = ap_strchr_c(s + 2, ':') + 1;
662         }
663     }
664 }
665
666 static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
667 {
668     dav_error *err;
669
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);
674
675     if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
676         return err;
677
678     /* skip past the METADATA key */
679     if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
680         return dav_propdb_next_name(db, pname);
681
682     dav_set_name(db, pname);
683     return NULL;
684 }
685
686 static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
687 {
688     dav_error *err;
689
690     if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
691         return err;
692
693     /* skip past the METADATA key */
694     if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
695         return dav_propdb_next_name(db, pname);
696
697     dav_set_name(db, pname);
698     return NULL;
699 }
700
701 static dav_error * dav_propdb_get_rollback(dav_db *db,
702                                            const dav_prop_name *name,
703                                            dav_deadprop_rollback **prollback)
704 {
705     dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
706     apr_datum_t key;
707     apr_datum_t value;
708     dav_error *err;
709
710     key = dav_build_key(db, name);
711     rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
712     rb->key.dsize = key.dsize;
713
714     if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
715         return err;
716     if (value.dptr != NULL) {
717         rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
718         rb->value.dsize = value.dsize;
719     }
720
721     *prollback = rb;
722     return NULL;
723 }
724
725 static dav_error * dav_propdb_apply_rollback(dav_db *db,
726                                              dav_deadprop_rollback *rollback)
727 {
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);
731         return NULL;
732     }
733
734     return dav_dbm_store(db, rollback->key, rollback->value);
735 }
736
737 const dav_hooks_db dav_hooks_db_dbm =
738 {
739     dav_propdb_open,
740     dav_propdb_close,
741     dav_propdb_define_namespaces,
742     dav_propdb_output_value,
743     dav_propdb_map_namespaces,
744     dav_propdb_store,
745     dav_propdb_remove,
746     dav_propdb_exists,
747     dav_propdb_first_name,
748     dav_propdb_next_name,
749     dav_propdb_get_rollback,
750     dav_propdb_apply_rollback,
751
752     NULL /* ctx */
753 };