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 44 45struct dav_db { 46 apr_pool_t *pool; 47 apr_dbm_t *file; 48 49 /* when used as a property database: */ 50 51 int version; /* *minor* version of this db */ 52 53 dav_buffer ns_table; /* table of namespace URIs */ 54 short ns_count; /* number of entries in table */ 55 int ns_table_dirty; /* ns_table was modified */ 56 apr_hash_t *uri_index; /* map URIs to (1-based) table indices */ 57 58 dav_buffer wb_key; /* work buffer for dav_gdbm_key */ 59 60 apr_datum_t iter; /* iteration key */ 61}; 62 63/* ------------------------------------------------------------------------- 64 * 65 * GENERIC DBM ACCESS 66 * 67 * For the most part, this just uses the APR DBM functions. They are wrapped 68 * a bit with some error handling (using the mod_dav error functions). 69 */ 70 71void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname, 72 const char **state1, const char **state2) 73{ 74 if (fname == NULL) 75 fname = DAV_FS_STATE_FILE_FOR_DIR; 76 77 apr_dbm_get_usednames(p, fname, state1, state2); 78} 79 80static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p, 81 apr_status_t status) 82{ 83 int save_errno = errno; 84 int errcode; 85 const char *errstr; 86 dav_error *err; 87 char errbuf[200]; 88 89 if (status == APR_SUCCESS) 90 return NULL; 91 92 p = db ? db->pool : p; 93 94 /* There might not be a <db> if we had problems creating it. */ 95 if (db == NULL) { 96 errcode = 1; 97 errstr = "Could not open property database."; 98 if (APR_STATUS_IS_EDSOOPEN(status)) 99 ap_log_error(APLOG_MARK, APLOG_CRIT, status, NULL, 100 "The DBM driver could not be loaded"); 101 } 102 else { 103 (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf)); 104 errstr = apr_pstrdup(p, errbuf); 105 } 106 107 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr); 108 err->save_errno = save_errno; 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 = sprintf(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, 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, 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 /* ### what to do with the error? */ 505 } 506 507 dav_dbm_close(db); 508} 509 510static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi) 511{ 512 int ns; 513 const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata); 514 515 /* within the prop values, we use "ns%d" for prefixes... register them */ 516 for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) { 517 518 /* Empty URIs signify the empty namespace. These do not get a 519 namespace prefix. when we generate the value, we will simply 520 leave off the prefix, which is defined by mod_dav to be the 521 empty namespace. */ 522 if (*uri == '\0') 523 continue; 524 525 /* ns_table.buf can move, so copy its value (we want the values to 526 last as long as the provided dav_xmlns_info). */ 527 dav_xmlns_add(xi, 528 apr_psprintf(xi->pool, "ns%d", ns), 529 apr_pstrdup(xi->pool, uri)); 530 } 531 532 return NULL; 533} 534 535static dav_error * dav_propdb_output_value(dav_db *db, 536 const dav_prop_name *name, 537 dav_xmlns_info *xi, 538 apr_text_header *phdr, 539 int *found) 540{ 541 apr_datum_t key = dav_build_key(db, name); 542 apr_datum_t value; 543 dav_error *err; 544 545 if ((err = dav_dbm_fetch(db, key, &value)) != NULL) 546 return err; 547 if (value.dptr == NULL) { 548 *found = 0; 549 return NULL; 550 } 551 *found = 1; 552 553 dav_append_prop(db->pool, key.dptr, value.dptr, phdr); 554 555 dav_dbm_freedatum(db, value); 556 557 return NULL; 558} 559 560static dav_error * dav_propdb_map_namespaces( 561 dav_db *db, 562 const apr_array_header_t *namespaces, 563 dav_namespace_map **mapping) 564{ 565 dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m)); 566 int i; 567 int *pmap; 568 const char **puri; 569 570 /* 571 ** Iterate over the provided namespaces. If a namespace already appears 572 ** in our internal map of URI -> ns_id, then store that in the map. If 573 ** we don't know the namespace yet, then add it to the map and to our 574 ** table of known namespaces. 575 */ 576 m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap)); 577 for (i = namespaces->nelts, puri = (const char **)namespaces->elts; 578 i-- > 0; 579 ++puri, ++pmap) { 580 581 const char *uri = *puri; 582 apr_size_t uri_len = strlen(uri); 583 long ns_id = (long)apr_hash_get(db->uri_index, uri, uri_len); 584 585 if (ns_id == 0) { 586 dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1); 587 memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1); 588 db->ns_table.cur_len += uri_len + 1; 589 590 /* copy the uri in case the passed-in namespaces changes in 591 some way. */ 592 apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len, 593 (void *)((long)(db->ns_count + 1))); 594 595 db->ns_table_dirty = 1; 596 597 *pmap = db->ns_count++; 598 } 599 else { 600 *pmap = ns_id - 1; 601 } 602 } 603 604 *mapping = m; 605 return NULL; 606} 607 608static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name, 609 const apr_xml_elem *elem, 610 dav_namespace_map *mapping) 611{ 612 apr_datum_t key = dav_build_key(db, name); 613 apr_datum_t value; 614 615 /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */ 616 617 /* ### use a db- subpool for these values? clear on exit? */ 618 619 /* quote all the values in the element */ 620 /* ### be nice to do this without affecting the element itself */ 621 /* ### of course, the cast indicates Badness is occurring here */ 622 apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem); 623 624 /* generate a text blob for the xml:lang plus the contents */ 625 apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL, 626 mapping->ns_map, 627 (const char **)&value.dptr, &value.dsize); 628 629 return dav_dbm_store(db, key, value); 630} 631 632static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name) 633{ 634 apr_datum_t key = dav_build_key(db, name); 635 return dav_dbm_delete(db, key); 636} 637 638static int dav_propdb_exists(dav_db *db, const dav_prop_name *name) 639{ 640 apr_datum_t key = dav_build_key(db, name); 641 return dav_dbm_exists(db, key); 642} 643 644static const char *dav_get_ns_table_uri(dav_db *db, int ns_id) 645{ 646 const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata); 647 648 while (ns_id--) 649 p += strlen(p) + 1; 650 651 return p; 652} 653 654static void dav_set_name(dav_db *db, dav_prop_name *pname) 655{ 656 const char *s = db->iter.dptr; 657 658 if (s == NULL) { 659 pname->ns = pname->name = NULL; 660 } 661 else if (*s == ':') { 662 pname->ns = ""; 663 pname->name = s + 1; 664 } 665 else { 666 int id = atoi(s); 667 668 pname->ns = dav_get_ns_table_uri(db, id); 669 if (s[1] == ':') { 670 pname->name = s + 2; 671 } 672 else { 673 pname->name = ap_strchr_c(s + 2, ':') + 1; 674 } 675 } 676} 677 678static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname) 679{ 680 dav_error *err; 681 682 /* free the previous key. note: if the loop is aborted, then the DBM 683 will toss the key (via pool cleanup) */ 684 if (db->iter.dptr != NULL) 685 dav_dbm_freedatum(db, db->iter); 686 687 if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL) 688 return err; 689 690 /* skip past the METADATA key */ 691 if (db->iter.dptr != NULL && *db->iter.dptr == 'M') 692 return dav_propdb_next_name(db, pname); 693 694 dav_set_name(db, pname); 695 return NULL; 696} 697 698static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname) 699{ 700 dav_error *err; 701 702 if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL) 703 return err; 704 705 /* skip past the METADATA key */ 706 if (db->iter.dptr != NULL && *db->iter.dptr == 'M') 707 return dav_propdb_next_name(db, pname); 708 709 dav_set_name(db, pname); 710 return NULL; 711} 712 713static dav_error * dav_propdb_get_rollback(dav_db *db, 714 const dav_prop_name *name, 715 dav_deadprop_rollback **prollback) 716{ 717 dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb)); 718 apr_datum_t key; 719 apr_datum_t value; 720 dav_error *err; 721 722 key = dav_build_key(db, name); 723 rb->key.dptr = apr_pstrdup(db->pool, key.dptr); 724 rb->key.dsize = key.dsize; 725 726 if ((err = dav_dbm_fetch(db, key, &value)) != NULL) 727 return err; 728 if (value.dptr != NULL) { 729 rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize); 730 rb->value.dsize = value.dsize; 731 } 732 733 *prollback = rb; 734 return NULL; 735} 736 737static dav_error * dav_propdb_apply_rollback(dav_db *db, 738 dav_deadprop_rollback *rollback) 739{ 740 if (!rollback) { 741 return NULL; /* no rollback, nothing to do */ 742 } 743 744 if (rollback->value.dptr == NULL) { 745 /* don't fail if the thing isn't really there. */ 746 (void) dav_dbm_delete(db, rollback->key); 747 return NULL; 748 } 749 750 return dav_dbm_store(db, rollback->key, rollback->value); 751} 752 753const dav_hooks_db dav_hooks_db_dbm = 754{ 755 dav_propdb_open, 756 dav_propdb_close, 757 dav_propdb_define_namespaces, 758 dav_propdb_output_value, 759 dav_propdb_map_namespaces, 760 dav_propdb_store, 761 dav_propdb_remove, 762 dav_propdb_exists, 763 dav_propdb_first_name, 764 dav_propdb_next_name, 765 dav_propdb_get_rollback, 766 dav_propdb_apply_rollback, 767 768 NULL /* ctx */ 769}; 770