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 filesystem lock implementation 19*/ 20 21#include "apr.h" 22#include "apr_strings.h" 23#include "apr_file_io.h" 24#include "apr_uuid.h" 25 26#define APR_WANT_MEMFUNC 27#include "apr_want.h" 28 29#include "httpd.h" 30#include "http_log.h" 31 32#include "mod_dav.h" 33#include "repos.h" 34 35 36/* --------------------------------------------------------------- 37** 38** Lock database primitives 39** 40*/ 41 42/* 43** LOCK DATABASES 44** 45** Lockdiscovery information is stored in the single lock database specified 46** by the DAVLockDB directive. Information about this db is stored in the 47** global server configuration. 48** 49** KEY 50** 51** The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME) 52** followed by the full path. The key_type DAV_TYPE_INODE is not used anymore. 53** 54** VALUE 55** 56** The value consists of a list of elements. 57** DIRECT LOCK: [char (DAV_LOCK_DIRECT), 58** char (dav_lock_scope), 59** char (dav_lock_type), 60** int depth, 61** time_t expires, 62** apr_uuid_t locktoken, 63** char[] owner, 64** char[] auth_user] 65** 66** INDIRECT LOCK: [char (DAV_LOCK_INDIRECT), 67** apr_uuid_t locktoken, 68** time_t expires, 69** apr_size_t key_size, 70** char[] key] 71** The key is to the collection lock that resulted in this indirect lock 72*/ 73 74#define DAV_TRUE 1 75#define DAV_FALSE 0 76 77#define DAV_CREATE_LIST 23 78#define DAV_APPEND_LIST 24 79 80/* Stored lock_discovery prefix */ 81#define DAV_LOCK_DIRECT 1 82#define DAV_LOCK_INDIRECT 2 83 84/* 85 * not used anymore 86 * #define DAV_TYPE_INODE 10 87 */ 88#define DAV_TYPE_FNAME 11 89 90 91/* ack. forward declare. */ 92static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p, 93 const char *filename, 94 dav_buffer *pbuf); 95 96/* 97** Use the opaquelock scheme for locktokens 98*/ 99struct dav_locktoken { 100 apr_uuid_t uuid; 101}; 102#define dav_compare_locktoken(plt1, plt2) \ 103 memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid)) 104 105 106/* ################################################################# 107** ### keep these structures (internal) or move fully to dav_lock? 108*/ 109 110/* 111** We need to reliably size the fixed-length portion of 112** dav_lock_discovery; best to separate it into another 113** struct for a convenient sizeof, unless we pack lock_discovery. 114*/ 115typedef struct dav_lock_discovery_fixed 116{ 117 char scope; 118 char type; 119 int depth; 120 time_t timeout; 121} dav_lock_discovery_fixed; 122 123typedef struct dav_lock_discovery 124{ 125 struct dav_lock_discovery_fixed f; 126 127 dav_locktoken *locktoken; 128 const char *owner; /* owner field from activelock */ 129 const char *auth_user; /* authenticated user who created the lock */ 130 struct dav_lock_discovery *next; 131} dav_lock_discovery; 132 133/* Indirect locks represent locks inherited from containing collections. 134 * They reference the lock token for the collection the lock is 135 * inherited from. A lock provider may also define a key to the 136 * inherited lock, for fast datbase lookup. The key is opaque outside 137 * the lock provider. 138 */ 139typedef struct dav_lock_indirect 140{ 141 dav_locktoken *locktoken; 142 apr_datum_t key; 143 struct dav_lock_indirect *next; 144 time_t timeout; 145} dav_lock_indirect; 146 147/* ################################################################# */ 148 149 150/* 151** Stored direct lock info - full lock_discovery length: 152** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string) 153*/ 154#define dav_size_direct(a) ( 1 + sizeof(dav_lock_discovery_fixed) \ 155 + sizeof(apr_uuid_t) \ 156 + ((a)->owner ? strlen((a)->owner) : 0) \ 157 + ((a)->auth_user ? strlen((a)->auth_user) : 0) \ 158 + 2) 159 160/* Stored indirect lock info - lock token and apr_datum_t */ 161#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \ 162 + sizeof(time_t) \ 163 + sizeof((a)->key.dsize) + (a)->key.dsize) 164 165/* 166** The lockdb structure. 167** 168** The <db> field may be NULL, meaning one of two things: 169** 1) That we have not actually opened the underlying database (yet). The 170** <opened> field should be false. 171** 2) We opened it readonly and it wasn't present. 172** 173** The delayed opening (determined by <opened>) makes creating a lockdb 174** quick, while deferring the underlying I/O until it is actually required. 175** 176** We export the notion of a lockdb, but hide the details of it. Most 177** implementations will use a database of some kind, but it is certainly 178** possible that alternatives could be used. 179*/ 180struct dav_lockdb_private 181{ 182 request_rec *r; /* for accessing the uuid state */ 183 apr_pool_t *pool; /* a pool to use */ 184 const char *lockdb_path; /* where is the lock database? */ 185 186 int opened; /* we opened the database */ 187 dav_db *db; /* if non-NULL, the lock database */ 188}; 189typedef struct 190{ 191 dav_lockdb pub; 192 dav_lockdb_private priv; 193} dav_lockdb_combined; 194 195/* 196** The private part of the lock structure. 197*/ 198struct dav_lock_private 199{ 200 apr_datum_t key; /* key into the lock database */ 201}; 202typedef struct 203{ 204 dav_lock pub; 205 dav_lock_private priv; 206 dav_locktoken token; 207} dav_lock_combined; 208 209/* 210** This must be forward-declared so the open_lockdb function can use it. 211*/ 212extern const dav_hooks_locks dav_hooks_locks_fs; 213 214 215/* internal function for creating locks */ 216static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key, 217 const dav_locktoken *locktoken) 218{ 219 dav_lock_combined *comb; 220 221 comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb)); 222 comb->pub.rectype = DAV_LOCKREC_DIRECT; 223 comb->pub.info = &comb->priv; 224 comb->priv.key = key; 225 226 if (locktoken == NULL) { 227 comb->pub.locktoken = &comb->token; 228 apr_uuid_get(&comb->token.uuid); 229 } 230 else { 231 comb->pub.locktoken = locktoken; 232 } 233 234 return &comb->pub; 235} 236 237/* 238** dav_fs_parse_locktoken 239** 240** Parse an opaquelocktoken URI into a locktoken. 241*/ 242static dav_error * dav_fs_parse_locktoken( 243 apr_pool_t *p, 244 const char *char_token, 245 dav_locktoken **locktoken_p) 246{ 247 dav_locktoken *locktoken; 248 249 if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) { 250 return dav_new_error(p, 251 HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0, 252 "The lock token uses an unknown State-token " 253 "format and could not be parsed."); 254 } 255 char_token += 16; 256 257 locktoken = apr_pcalloc(p, sizeof(*locktoken)); 258 if (apr_uuid_parse(&locktoken->uuid, char_token)) { 259 return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0, 260 "The opaquelocktoken has an incorrect format " 261 "and could not be parsed."); 262 } 263 264 *locktoken_p = locktoken; 265 return NULL; 266} 267 268/* 269** dav_fs_format_locktoken 270** 271** Generate the URI for a locktoken 272*/ 273static const char *dav_fs_format_locktoken( 274 apr_pool_t *p, 275 const dav_locktoken *locktoken) 276{ 277 char buf[APR_UUID_FORMATTED_LENGTH + 1]; 278 279 apr_uuid_format(buf, &locktoken->uuid); 280 return apr_pstrcat(p, "opaquelocktoken:", buf, NULL); 281} 282 283/* 284** dav_fs_compare_locktoken 285** 286** Determine whether two locktokens are the same 287*/ 288static int dav_fs_compare_locktoken( 289 const dav_locktoken *lt1, 290 const dav_locktoken *lt2) 291{ 292 return dav_compare_locktoken(lt1, lt2); 293} 294 295/* 296** dav_fs_really_open_lockdb: 297** 298** If the database hasn't been opened yet, then open the thing. 299*/ 300static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb) 301{ 302 dav_error *err; 303 304 if (lockdb->info->opened) 305 return NULL; 306 307 err = dav_dbm_open_direct(lockdb->info->pool, 308 lockdb->info->lockdb_path, 309 lockdb->ro, 310 &lockdb->info->db); 311 if (err != NULL) { 312 return dav_push_error(lockdb->info->pool, 313 HTTP_INTERNAL_SERVER_ERROR, 314 DAV_ERR_LOCK_OPENDB, 315 "Could not open the lock database.", 316 err); 317 } 318 319 /* all right. it is opened now. */ 320 lockdb->info->opened = 1; 321 322 return NULL; 323} 324 325/* 326** dav_fs_open_lockdb: 327** 328** "open" the lock database, as specified in the global server configuration. 329** If force is TRUE, then the database is opened now, rather than lazily. 330** 331** Note that only one can be open read/write. 332*/ 333static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force, 334 dav_lockdb **lockdb) 335{ 336 dav_lockdb_combined *comb; 337 338 comb = apr_pcalloc(r->pool, sizeof(*comb)); 339 comb->pub.hooks = &dav_hooks_locks_fs; 340 comb->pub.ro = ro; 341 comb->pub.info = &comb->priv; 342 comb->priv.r = r; 343 comb->priv.pool = r->pool; 344 345 comb->priv.lockdb_path = dav_get_lockdb_path(r); 346 if (comb->priv.lockdb_path == NULL) { 347 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 348 DAV_ERR_LOCK_NO_DB, 0, 349 "A lock database was not specified with the " 350 "DAVLockDB directive. One must be specified " 351 "to use the locking functionality."); 352 } 353 354 /* done initializing. return it. */ 355 *lockdb = &comb->pub; 356 357 if (force) { 358 /* ### add a higher-level comment? */ 359 return dav_fs_really_open_lockdb(*lockdb); 360 } 361 362 return NULL; 363} 364 365/* 366** dav_fs_close_lockdb: 367** 368** Close it. Duh. 369*/ 370static void dav_fs_close_lockdb(dav_lockdb *lockdb) 371{ 372 if (lockdb->info->db != NULL) 373 dav_dbm_close(lockdb->info->db); 374} 375 376/* 377** dav_fs_build_key: Given a resource, return a apr_datum_t key 378** to look up lock information for this file. 379*/ 380static apr_datum_t dav_fs_build_key(apr_pool_t *p, 381 const dav_resource *resource) 382{ 383 const char *pathname = dav_fs_pathname(resource); 384 apr_datum_t key; 385 386 /* ### does this allocation have a proper lifetime? need to check */ 387 /* ### can we use a buffer for this? */ 388 389 /* size is TYPE + pathname + null */ 390 key.dsize = strlen(pathname) + 2; 391 key.dptr = apr_palloc(p, key.dsize); 392 *key.dptr = DAV_TYPE_FNAME; 393 memcpy(key.dptr + 1, pathname, key.dsize - 1); 394 if (key.dptr[key.dsize - 2] == '/') 395 key.dptr[--key.dsize - 1] = '\0'; 396 return key; 397} 398 399/* 400** dav_fs_lock_expired: return 1 (true) if the given timeout is in the past 401** or present (the lock has expired), or 0 (false) if in the future 402** (the lock has not yet expired). 403*/ 404static int dav_fs_lock_expired(time_t expires) 405{ 406 return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires; 407} 408 409/* 410** dav_fs_save_lock_record: Saves the lock information specified in the 411** direct and indirect lock lists about path into the lock database. 412** If direct and indirect == NULL, the key is removed. 413*/ 414static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key, 415 dav_lock_discovery *direct, 416 dav_lock_indirect *indirect) 417{ 418 dav_error *err; 419 apr_datum_t val = { 0 }; 420 char *ptr; 421 dav_lock_discovery *dp = direct; 422 dav_lock_indirect *ip = indirect; 423 424#if DAV_DEBUG 425 if (lockdb->ro) { 426 return dav_new_error(lockdb->info->pool, 427 HTTP_INTERNAL_SERVER_ERROR, 0, 0, 428 "INTERNAL DESIGN ERROR: the lockdb was opened " 429 "readonly, but an attempt to save locks was " 430 "performed."); 431 } 432#endif 433 434 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) { 435 /* ### add a higher-level error? */ 436 return err; 437 } 438 439 /* If nothing to save, delete key */ 440 if (dp == NULL && ip == NULL) { 441 /* don't fail if the key is not present */ 442 /* ### but what about other errors? */ 443 (void) dav_dbm_delete(lockdb->info->db, key); 444 return NULL; 445 } 446 447 while(dp) { 448 val.dsize += dav_size_direct(dp); 449 dp = dp->next; 450 } 451 while(ip) { 452 val.dsize += dav_size_indirect(ip); 453 ip = ip->next; 454 } 455 456 /* ### can this be apr_palloc() ? */ 457 /* ### hmmm.... investigate the use of a buffer here */ 458 ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize); 459 dp = direct; 460 ip = indirect; 461 462 while(dp) { 463 *ptr++ = DAV_LOCK_DIRECT; /* Direct lock - lock_discovery struct follows */ 464 memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */ 465 ptr += sizeof(dp->f); 466 memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken)); 467 ptr += sizeof(*dp->locktoken); 468 if (dp->owner == NULL) { 469 *ptr++ = '\0'; 470 } 471 else { 472 memcpy(ptr, dp->owner, strlen(dp->owner) + 1); 473 ptr += strlen(dp->owner) + 1; 474 } 475 if (dp->auth_user == NULL) { 476 *ptr++ = '\0'; 477 } 478 else { 479 memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1); 480 ptr += strlen(dp->auth_user) + 1; 481 } 482 483 dp = dp->next; 484 } 485 486 while(ip) { 487 *ptr++ = DAV_LOCK_INDIRECT; /* Indirect lock prefix */ 488 memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken)); /* Locktoken */ 489 ptr += sizeof(*ip->locktoken); 490 memcpy(ptr, &ip->timeout, sizeof(ip->timeout)); /* Expire time */ 491 ptr += sizeof(ip->timeout); 492 memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize)); /* Size of key */ 493 ptr += sizeof(ip->key.dsize); 494 memcpy(ptr, ip->key.dptr, ip->key.dsize); /* Key data */ 495 ptr += ip->key.dsize; 496 ip = ip->next; 497 } 498 499 if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) { 500 /* ### more details? add an error_id? */ 501 return dav_push_error(lockdb->info->pool, 502 HTTP_INTERNAL_SERVER_ERROR, 503 DAV_ERR_LOCK_SAVE_LOCK, 504 "Could not save lock information.", 505 err); 506 } 507 508 return NULL; 509} 510 511/* 512** dav_load_lock_record: Reads lock information about key from lock db; 513** creates linked lists of the direct and indirect locks. 514** 515** If add_method = DAV_APPEND_LIST, the result will be appended to the 516** head of the direct and indirect lists supplied. 517** 518** Passive lock removal: If lock has timed out, it will not be returned. 519** ### How much "logging" does RFC 2518 require? 520*/ 521static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key, 522 int add_method, 523 dav_lock_discovery **direct, 524 dav_lock_indirect **indirect) 525{ 526 apr_pool_t *p = lockdb->info->pool; 527 dav_error *err; 528 apr_size_t offset = 0; 529 int need_save = DAV_FALSE; 530 apr_datum_t val = { 0 }; 531 dav_lock_discovery *dp; 532 dav_lock_indirect *ip; 533 dav_buffer buf = { 0 }; 534 535 if (add_method != DAV_APPEND_LIST) { 536 *direct = NULL; 537 *indirect = NULL; 538 } 539 540 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) { 541 /* ### add a higher-level error? */ 542 return err; 543 } 544 545 /* 546 ** If we opened readonly and the db wasn't there, then there are no 547 ** locks for this resource. Just exit. 548 */ 549 if (lockdb->info->db == NULL) 550 return NULL; 551 552 if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL) 553 return err; 554 555 if (!val.dsize) 556 return NULL; 557 558 while (offset < val.dsize) { 559 switch (*(val.dptr + offset++)) { 560 case DAV_LOCK_DIRECT: 561 /* Create and fill a dav_lock_discovery structure */ 562 563 dp = apr_pcalloc(p, sizeof(*dp)); 564 memcpy(dp, val.dptr + offset, sizeof(dp->f)); 565 offset += sizeof(dp->f); 566 dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken)); 567 offset += sizeof(*dp->locktoken); 568 if (*(val.dptr + offset) == '\0') { 569 ++offset; 570 } 571 else { 572 dp->owner = apr_pstrdup(p, val.dptr + offset); 573 offset += strlen(dp->owner) + 1; 574 } 575 576 if (*(val.dptr + offset) == '\0') { 577 ++offset; 578 } 579 else { 580 dp->auth_user = apr_pstrdup(p, val.dptr + offset); 581 offset += strlen(dp->auth_user) + 1; 582 } 583 584 if (!dav_fs_lock_expired(dp->f.timeout)) { 585 dp->next = *direct; 586 *direct = dp; 587 } 588 else { 589 need_save = DAV_TRUE; 590 591 /* Remove timed-out locknull fm .locknull list */ 592 if (*key.dptr == DAV_TYPE_FNAME) { 593 const char *fname = key.dptr + 1; 594 apr_finfo_t finfo; 595 apr_status_t rv; 596 597 /* if we don't see the file, then it's a locknull */ 598 rv = apr_stat(&finfo, fname, APR_FINFO_MIN | APR_FINFO_LINK, p); 599 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { 600 if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) { 601 /* ### push a higher-level description? */ 602 return err; 603 } 604 } 605 } 606 } 607 break; 608 609 case DAV_LOCK_INDIRECT: 610 /* Create and fill a dav_lock_indirect structure */ 611 612 ip = apr_pcalloc(p, sizeof(*ip)); 613 ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken)); 614 offset += sizeof(*ip->locktoken); 615 memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout)); 616 offset += sizeof(ip->timeout); 617 memcpy(&ip->key.dsize, val.dptr + offset, sizeof(ip->key.dsize)); /* length of datum */ 618 offset += sizeof(ip->key.dsize); 619 ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize); 620 offset += ip->key.dsize; 621 622 if (!dav_fs_lock_expired(ip->timeout)) { 623 ip->next = *indirect; 624 *indirect = ip; 625 } 626 else { 627 need_save = DAV_TRUE; 628 /* A locknull resource will never be locked indirectly */ 629 } 630 631 break; 632 633 default: 634 dav_dbm_freedatum(lockdb->info->db, val); 635 636 /* ### should use a computed_desc and insert corrupt token data */ 637 --offset; 638 return dav_new_error(p, 639 HTTP_INTERNAL_SERVER_ERROR, 640 DAV_ERR_LOCK_CORRUPT_DB, 0, 641 apr_psprintf(p, 642 "The lock database was found to " 643 "be corrupt. offset %" 644 APR_SIZE_T_FMT ", c=%02x", 645 offset, val.dptr[offset])); 646 } 647 } 648 649 dav_dbm_freedatum(lockdb->info->db, val); 650 651 /* Clean up this record if we found expired locks */ 652 /* 653 ** ### shouldn't do this if we've been opened READONLY. elide the 654 ** ### timed-out locks from the response, but don't save that info back 655 */ 656 if (need_save == DAV_TRUE) { 657 return dav_fs_save_lock_record(lockdb, key, *direct, *indirect); 658 } 659 660 return NULL; 661} 662 663/* resolve <indirect>, returning <*direct> */ 664static dav_error * dav_fs_resolve(dav_lockdb *lockdb, 665 dav_lock_indirect *indirect, 666 dav_lock_discovery **direct, 667 dav_lock_discovery **ref_dp, 668 dav_lock_indirect **ref_ip) 669{ 670 dav_error *err; 671 dav_lock_discovery *dir; 672 dav_lock_indirect *ind; 673 674 if ((err = dav_fs_load_lock_record(lockdb, indirect->key, 675 DAV_CREATE_LIST, 676 &dir, &ind)) != NULL) { 677 /* ### insert a higher-level description? */ 678 return err; 679 } 680 if (ref_dp != NULL) { 681 *ref_dp = dir; 682 *ref_ip = ind; 683 } 684 685 for (; dir != NULL; dir = dir->next) { 686 if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) { 687 *direct = dir; 688 return NULL; 689 } 690 } 691 692 /* No match found (but we should have found one!) */ 693 694 /* ### use a different description and/or error ID? */ 695 return dav_new_error(lockdb->info->pool, 696 HTTP_INTERNAL_SERVER_ERROR, 697 DAV_ERR_LOCK_CORRUPT_DB, 0, 698 "The lock database was found to be corrupt. " 699 "An indirect lock's direct lock could not " 700 "be found."); 701} 702 703/* --------------------------------------------------------------- 704** 705** Property-related lock functions 706** 707*/ 708 709/* 710** dav_fs_get_supportedlock: Returns a static string for all supportedlock 711** properties. I think we save more returning a static string than 712** constructing it every time, though it might look cleaner. 713*/ 714static const char *dav_fs_get_supportedlock(const dav_resource *resource) 715{ 716 static const char supported[] = DEBUG_CR 717 "<D:lockentry>" DEBUG_CR 718 "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR 719 "<D:locktype><D:write/></D:locktype>" DEBUG_CR 720 "</D:lockentry>" DEBUG_CR 721 "<D:lockentry>" DEBUG_CR 722 "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR 723 "<D:locktype><D:write/></D:locktype>" DEBUG_CR 724 "</D:lockentry>" DEBUG_CR; 725 726 return supported; 727} 728 729/* --------------------------------------------------------------- 730** 731** General lock functions 732** 733*/ 734 735/* --------------------------------------------------------------- 736** 737** Functions dealing with lock-null resources 738** 739*/ 740 741/* 742** dav_fs_load_locknull_list: Returns a dav_buffer dump of the locknull file 743** for the given directory. 744*/ 745static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath, 746 dav_buffer *pbuf) 747{ 748 apr_finfo_t finfo; 749 apr_file_t *file = NULL; 750 dav_error *err = NULL; 751 apr_size_t amt; 752 apr_status_t rv; 753 754 dav_buffer_init(p, pbuf, dirpath); 755 756 if (pbuf->buf[pbuf->cur_len - 1] == '/') 757 pbuf->buf[--pbuf->cur_len] = '\0'; 758 759 dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE); 760 761 /* reset this in case we leave w/o reading into the buffer */ 762 pbuf->cur_len = 0; 763 764 if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT, 765 p) != APR_SUCCESS) { 766 return NULL; 767 } 768 769 rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file); 770 if (rv != APR_SUCCESS) { 771 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 772 apr_psprintf(p, 773 "Opened but could not stat file %s", 774 pbuf->buf)); 775 goto loaderror; 776 } 777 778 if (finfo.size != (apr_size_t)finfo.size) { 779 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 780 apr_psprintf(p, 781 "Opened but rejected huge file %s", 782 pbuf->buf)); 783 goto loaderror; 784 } 785 786 amt = (apr_size_t)finfo.size; 787 dav_set_bufsize(p, pbuf, amt); 788 if ((rv = apr_file_read(file, pbuf->buf, &amt)) != APR_SUCCESS 789 || amt != finfo.size) { 790 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 791 apr_psprintf(p, 792 "Failure reading locknull file " 793 "for %s", dirpath)); 794 795 /* just in case the caller disregards the returned error */ 796 pbuf->cur_len = 0; 797 goto loaderror; 798 } 799 800 loaderror: 801 apr_file_close(file); 802 return err; 803} 804 805/* 806** dav_fs_save_locknull_list: Saves contents of pbuf into the 807** locknull file for dirpath. 808*/ 809static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath, 810 dav_buffer *pbuf) 811{ 812 const char *pathname; 813 apr_file_t *file = NULL; 814 dav_error *err = NULL; 815 apr_size_t amt; 816 apr_status_t rv; 817 818 if (pbuf->buf == NULL) 819 return NULL; 820 821 dav_fs_ensure_state_dir(p, dirpath); 822 pathname = apr_pstrcat(p, 823 dirpath, 824 dirpath[strlen(dirpath) - 1] == '/' ? "" : "/", 825 DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE, 826 NULL); 827 828 if (pbuf->cur_len == 0) { 829 /* delete the file if cur_len == 0 */ 830 if ((rv = apr_file_remove(pathname, p)) != APR_SUCCESS) { 831 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 832 apr_psprintf(p, 833 "Error removing %s", pathname)); 834 } 835 return NULL; 836 } 837 838 if ((rv = apr_file_open(&file, pathname, 839 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, 840 APR_OS_DEFAULT, p)) != APR_SUCCESS) { 841 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 842 apr_psprintf(p, 843 "Error opening %s for writing", 844 pathname)); 845 } 846 847 amt = pbuf->cur_len; 848 if ((rv = apr_file_write_full(file, pbuf->buf, amt, &amt)) != APR_SUCCESS 849 || amt != pbuf->cur_len) { 850 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 851 apr_psprintf(p, 852 "Error writing %" APR_SIZE_T_FMT 853 " bytes to %s", 854 pbuf->cur_len, pathname)); 855 } 856 857 apr_file_close(file); 858 return err; 859} 860 861/* 862** dav_fs_remove_locknull_member: Removes filename from the locknull list 863** for directory path. 864*/ 865static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p, 866 const char *filename, 867 dav_buffer *pbuf) 868{ 869 dav_error *err; 870 apr_size_t len; 871 apr_size_t scanlen; 872 char *scan; 873 const char *scanend; 874 char *dirpath = apr_pstrdup(p, filename); 875 char *fname = strrchr(dirpath, '/'); 876 int dirty = 0; 877 878 if (fname != NULL) 879 *fname++ = '\0'; 880 else 881 fname = dirpath; 882 len = strlen(fname) + 1; 883 884 if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) { 885 /* ### add a higher level description? */ 886 return err; 887 } 888 889 for (scan = pbuf->buf, scanend = scan + pbuf->cur_len; 890 scan < scanend; 891 scan += scanlen) { 892 scanlen = strlen(scan) + 1; 893 if (len == scanlen && memcmp(fname, scan, scanlen) == 0) { 894 pbuf->cur_len -= scanlen; 895 memmove(scan, scan + scanlen, scanend - (scan + scanlen)); 896 dirty = 1; 897 break; 898 } 899 } 900 901 if (dirty) { 902 if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) { 903 /* ### add a higher level description? */ 904 return err; 905 } 906 } 907 908 return NULL; 909} 910 911/* Note: used by dav_fs_repos.c */ 912dav_error * dav_fs_get_locknull_members( 913 const dav_resource *resource, 914 dav_buffer *pbuf) 915{ 916 const char *dirpath; 917 918 /* ### should test this result value... */ 919 (void) dav_fs_dir_file_name(resource, &dirpath, NULL); 920 return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf); 921} 922 923/* ### fold into append_lock? */ 924/* ### take an optional buf parameter? */ 925static dav_error * dav_fs_add_locknull_state( 926 dav_lockdb *lockdb, 927 const dav_resource *resource) 928{ 929 dav_buffer buf = { 0 }; 930 apr_pool_t *p = lockdb->info->pool; 931 const char *dirpath; 932 const char *fname; 933 dav_error *err; 934 935 /* ### should test this result value... */ 936 (void) dav_fs_dir_file_name(resource, &dirpath, &fname); 937 938 if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) { 939 return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 940 "Could not load .locknull file.", err); 941 } 942 943 dav_buffer_append(p, &buf, fname); 944 buf.cur_len++; /* we want the null-term here */ 945 946 if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) { 947 return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 948 "Could not save .locknull file.", err); 949 } 950 951 return NULL; 952} 953 954/* 955** dav_fs_remove_locknull_state: Given a request, check to see if r->filename 956** is/was a lock-null resource. If so, return it to an existant state, i.e. 957** remove it from the list in the appropriate .DAV/locknull file. 958*/ 959static dav_error * dav_fs_remove_locknull_state( 960 dav_lockdb *lockdb, 961 const dav_resource *resource) 962{ 963 dav_buffer buf = { 0 }; 964 dav_error *err; 965 apr_pool_t *p = lockdb->info->pool; 966 const char *pathname = dav_fs_pathname(resource); 967 968 if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) { 969 /* ### add a higher-level description? */ 970 return err; 971 } 972 973 return NULL; 974} 975 976static dav_error * dav_fs_create_lock(dav_lockdb *lockdb, 977 const dav_resource *resource, 978 dav_lock **lock) 979{ 980 apr_datum_t key; 981 982 key = dav_fs_build_key(lockdb->info->pool, resource); 983 984 *lock = dav_fs_alloc_lock(lockdb, 985 key, 986 NULL); 987 988 (*lock)->is_locknull = !resource->exists; 989 990 return NULL; 991} 992 993static dav_error * dav_fs_get_locks(dav_lockdb *lockdb, 994 const dav_resource *resource, 995 int calltype, 996 dav_lock **locks) 997{ 998 apr_pool_t *p = lockdb->info->pool; 999 apr_datum_t key; 1000 dav_error *err; 1001 dav_lock *lock = NULL; 1002 dav_lock *newlock; 1003 dav_lock_discovery *dp; 1004 dav_lock_indirect *ip; 1005 1006#if DAV_DEBUG 1007 if (calltype == DAV_GETLOCKS_COMPLETE) { 1008 return dav_new_error(lockdb->info->pool, 1009 HTTP_INTERNAL_SERVER_ERROR, 0, 0, 1010 "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE " 1011 "is not yet supported"); 1012 } 1013#endif 1014 1015 key = dav_fs_build_key(p, resource); 1016 if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST, 1017 &dp, &ip)) != NULL) { 1018 /* ### push a higher-level desc? */ 1019 return err; 1020 } 1021 1022 /* copy all direct locks to the result list */ 1023 for (; dp != NULL; dp = dp->next) { 1024 newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken); 1025 newlock->is_locknull = !resource->exists; 1026 newlock->scope = dp->f.scope; 1027 newlock->type = dp->f.type; 1028 newlock->depth = dp->f.depth; 1029 newlock->timeout = dp->f.timeout; 1030 newlock->owner = dp->owner; 1031 newlock->auth_user = dp->auth_user; 1032 1033 /* hook into the result list */ 1034 newlock->next = lock; 1035 lock = newlock; 1036 } 1037 1038 /* copy all the indirect locks to the result list. resolve as needed. */ 1039 for (; ip != NULL; ip = ip->next) { 1040 newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken); 1041 newlock->is_locknull = !resource->exists; 1042 1043 if (calltype == DAV_GETLOCKS_RESOLVED) { 1044 if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) { 1045 /* ### push a higher-level desc? */ 1046 return err; 1047 } 1048 1049 newlock->scope = dp->f.scope; 1050 newlock->type = dp->f.type; 1051 newlock->depth = dp->f.depth; 1052 newlock->timeout = dp->f.timeout; 1053 newlock->owner = dp->owner; 1054 newlock->auth_user = dp->auth_user; 1055 } 1056 else { 1057 /* DAV_GETLOCKS_PARTIAL */ 1058 newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL; 1059 } 1060 1061 /* hook into the result list */ 1062 newlock->next = lock; 1063 lock = newlock; 1064 } 1065 1066 *locks = lock; 1067 return NULL; 1068} 1069 1070static dav_error * dav_fs_find_lock(dav_lockdb *lockdb, 1071 const dav_resource *resource, 1072 const dav_locktoken *locktoken, 1073 int partial_ok, 1074 dav_lock **lock) 1075{ 1076 dav_error *err; 1077 apr_datum_t key; 1078 dav_lock_discovery *dp; 1079 dav_lock_indirect *ip; 1080 1081 *lock = NULL; 1082 1083 key = dav_fs_build_key(lockdb->info->pool, resource); 1084 if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST, 1085 &dp, &ip)) != NULL) { 1086 /* ### push a higher-level desc? */ 1087 return err; 1088 } 1089 1090 for (; dp != NULL; dp = dp->next) { 1091 if (!dav_compare_locktoken(locktoken, dp->locktoken)) { 1092 *lock = dav_fs_alloc_lock(lockdb, key, locktoken); 1093 (*lock)->is_locknull = !resource->exists; 1094 (*lock)->scope = dp->f.scope; 1095 (*lock)->type = dp->f.type; 1096 (*lock)->depth = dp->f.depth; 1097 (*lock)->timeout = dp->f.timeout; 1098 (*lock)->owner = dp->owner; 1099 (*lock)->auth_user = dp->auth_user; 1100 return NULL; 1101 } 1102 } 1103 1104 for (; ip != NULL; ip = ip->next) { 1105 if (!dav_compare_locktoken(locktoken, ip->locktoken)) { 1106 *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken); 1107 (*lock)->is_locknull = !resource->exists; 1108 1109 /* ### nobody uses the resolving right now! */ 1110 if (partial_ok) { 1111 (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL; 1112 } 1113 else { 1114 (*lock)->rectype = DAV_LOCKREC_INDIRECT; 1115 if ((err = dav_fs_resolve(lockdb, ip, &dp, 1116 NULL, NULL)) != NULL) { 1117 /* ### push a higher-level desc? */ 1118 return err; 1119 } 1120 (*lock)->scope = dp->f.scope; 1121 (*lock)->type = dp->f.type; 1122 (*lock)->depth = dp->f.depth; 1123 (*lock)->timeout = dp->f.timeout; 1124 (*lock)->owner = dp->owner; 1125 (*lock)->auth_user = dp->auth_user; 1126 } 1127 return NULL; 1128 } 1129 } 1130 1131 return NULL; 1132} 1133 1134static dav_error * dav_fs_has_locks(dav_lockdb *lockdb, 1135 const dav_resource *resource, 1136 int *locks_present) 1137{ 1138 dav_error *err; 1139 apr_datum_t key; 1140 1141 *locks_present = 0; 1142 1143 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) { 1144 /* ### insert a higher-level error description */ 1145 return err; 1146 } 1147 1148 /* 1149 ** If we opened readonly and the db wasn't there, then there are no 1150 ** locks for this resource. Just exit. 1151 */ 1152 if (lockdb->info->db == NULL) 1153 return NULL; 1154 1155 key = dav_fs_build_key(lockdb->info->pool, resource); 1156 1157 *locks_present = dav_dbm_exists(lockdb->info->db, key); 1158 1159 return NULL; 1160} 1161 1162static dav_error * dav_fs_append_locks(dav_lockdb *lockdb, 1163 const dav_resource *resource, 1164 int make_indirect, 1165 const dav_lock *lock) 1166{ 1167 apr_pool_t *p = lockdb->info->pool; 1168 dav_error *err; 1169 dav_lock_indirect *ip; 1170 dav_lock_discovery *dp; 1171 apr_datum_t key; 1172 1173 key = dav_fs_build_key(lockdb->info->pool, resource); 1174 if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) { 1175 /* ### maybe add in a higher-level description */ 1176 return err; 1177 } 1178 1179 /* 1180 ** ### when we store the lock more directly, we need to update 1181 ** ### lock->rectype and lock->is_locknull 1182 */ 1183 1184 if (make_indirect) { 1185 for (; lock != NULL; lock = lock->next) { 1186 1187 /* ### this works for any <lock> rectype */ 1188 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi)); 1189 1190 /* ### shut off the const warning for now */ 1191 newi->locktoken = (dav_locktoken *)lock->locktoken; 1192 newi->timeout = lock->timeout; 1193 newi->key = lock->info->key; 1194 newi->next = ip; 1195 ip = newi; 1196 } 1197 } 1198 else { 1199 for (; lock != NULL; lock = lock->next) { 1200 /* create and link in the right kind of lock */ 1201 1202 if (lock->rectype == DAV_LOCKREC_DIRECT) { 1203 dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd)); 1204 1205 newd->f.scope = lock->scope; 1206 newd->f.type = lock->type; 1207 newd->f.depth = lock->depth; 1208 newd->f.timeout = lock->timeout; 1209 /* ### shut off the const warning for now */ 1210 newd->locktoken = (dav_locktoken *)lock->locktoken; 1211 newd->owner = lock->owner; 1212 newd->auth_user = lock->auth_user; 1213 newd->next = dp; 1214 dp = newd; 1215 } 1216 else { 1217 /* DAV_LOCKREC_INDIRECT(_PARTIAL) */ 1218 1219 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi)); 1220 1221 /* ### shut off the const warning for now */ 1222 newi->locktoken = (dav_locktoken *)lock->locktoken; 1223 newi->key = lock->info->key; 1224 newi->next = ip; 1225 ip = newi; 1226 } 1227 } 1228 } 1229 1230 if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) { 1231 /* ### maybe add a higher-level description */ 1232 return err; 1233 } 1234 1235 /* we have a special list for recording locknull resources */ 1236 /* ### ack! this can add two copies to the locknull list */ 1237 if (!resource->exists 1238 && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) { 1239 /* ### maybe add a higher-level description */ 1240 return err; 1241 } 1242 1243 return NULL; 1244} 1245 1246static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb, 1247 const dav_resource *resource, 1248 const dav_locktoken *locktoken) 1249{ 1250 dav_error *err; 1251 dav_buffer buf = { 0 }; 1252 dav_lock_discovery *dh = NULL; 1253 dav_lock_indirect *ih = NULL; 1254 apr_datum_t key; 1255 1256 key = dav_fs_build_key(lockdb->info->pool, resource); 1257 1258 if (locktoken != NULL) { 1259 dav_lock_discovery *dp; 1260 dav_lock_discovery *dprev = NULL; 1261 dav_lock_indirect *ip; 1262 dav_lock_indirect *iprev = NULL; 1263 1264 if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST, 1265 &dh, &ih)) != NULL) { 1266 /* ### maybe add a higher-level description */ 1267 return err; 1268 } 1269 1270 for (dp = dh; dp != NULL; dp = dp->next) { 1271 if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) { 1272 if (dprev) 1273 dprev->next = dp->next; 1274 else 1275 dh = dh->next; 1276 } 1277 dprev = dp; 1278 } 1279 1280 for (ip = ih; ip != NULL; ip = ip->next) { 1281 if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) { 1282 if (iprev) 1283 iprev->next = ip->next; 1284 else 1285 ih = ih->next; 1286 } 1287 iprev = ip; 1288 } 1289 1290 } 1291 1292 /* save the modified locks, or remove all locks (dh=ih=NULL). */ 1293 if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) { 1294 /* ### maybe add a higher-level description */ 1295 return err; 1296 } 1297 1298 /* 1299 ** If this resource is a locknull resource AND no more locks exist, 1300 ** then remove the locknull member. 1301 ** 1302 ** Note: remove_locknull_state() attempts to convert a locknull member 1303 ** to a real member. In this case, all locks are gone, so the 1304 ** locknull resource returns to the null state (ie. doesn't exist), 1305 ** so there is no need to update the lockdb (and it won't find 1306 ** any because a precondition is that none exist). 1307 */ 1308 if (!resource->exists && dh == NULL && ih == NULL 1309 && (err = dav_fs_remove_locknull_member(lockdb->info->pool, 1310 dav_fs_pathname(resource), 1311 &buf)) != NULL) { 1312 /* ### maybe add a higher-level description */ 1313 return err; 1314 } 1315 1316 return NULL; 1317} 1318 1319static int dav_fs_do_refresh(dav_lock_discovery *dp, 1320 const dav_locktoken_list *ltl, 1321 time_t new_time) 1322{ 1323 int dirty = 0; 1324 1325 for (; ltl != NULL; ltl = ltl->next) { 1326 if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0) 1327 { 1328 dp->f.timeout = new_time; 1329 dirty = 1; 1330 break; 1331 } 1332 } 1333 1334 return dirty; 1335} 1336 1337static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb, 1338 const dav_resource *resource, 1339 const dav_locktoken_list *ltl, 1340 time_t new_time, 1341 dav_lock **locks) 1342{ 1343 dav_error *err; 1344 apr_datum_t key; 1345 dav_lock_discovery *dp; 1346 dav_lock_discovery *dp_scan; 1347 dav_lock_indirect *ip; 1348 int dirty = 0; 1349 dav_lock *newlock; 1350 1351 *locks = NULL; 1352 1353 key = dav_fs_build_key(lockdb->info->pool, resource); 1354 if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST, 1355 &dp, &ip)) != NULL) { 1356 /* ### maybe add in a higher-level description */ 1357 return err; 1358 } 1359 1360 /* ### we should be refreshing direct AND (resolved) indirect locks! */ 1361 1362 /* refresh all of the direct locks on this resource */ 1363 for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) { 1364 if (dav_fs_do_refresh(dp_scan, ltl, new_time)) { 1365 /* the lock was refreshed. return the lock. */ 1366 newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken); 1367 newlock->is_locknull = !resource->exists; 1368 newlock->scope = dp_scan->f.scope; 1369 newlock->type = dp_scan->f.type; 1370 newlock->depth = dp_scan->f.depth; 1371 newlock->timeout = dp_scan->f.timeout; 1372 newlock->owner = dp_scan->owner; 1373 newlock->auth_user = dp_scan->auth_user; 1374 1375 newlock->next = *locks; 1376 *locks = newlock; 1377 1378 dirty = 1; 1379 } 1380 } 1381 1382 /* if we refreshed any locks, then save them back. */ 1383 if (dirty 1384 && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) { 1385 /* ### maybe add in a higher-level description */ 1386 return err; 1387 } 1388 1389 /* for each indirect lock, find its direct lock and refresh it. */ 1390 for (; ip != NULL; ip = ip->next) { 1391 dav_lock_discovery *ref_dp; 1392 dav_lock_indirect *ref_ip; 1393 1394 if ((err = dav_fs_resolve(lockdb, ip, &dp_scan, 1395 &ref_dp, &ref_ip)) != NULL) { 1396 /* ### push a higher-level desc? */ 1397 return err; 1398 } 1399 if (dav_fs_do_refresh(dp_scan, ltl, new_time)) { 1400 /* the lock was refreshed. return the lock. */ 1401 newlock = dav_fs_alloc_lock(lockdb, ip->key, dp_scan->locktoken); 1402 newlock->is_locknull = !resource->exists; 1403 newlock->scope = dp_scan->f.scope; 1404 newlock->type = dp_scan->f.type; 1405 newlock->depth = dp_scan->f.depth; 1406 newlock->timeout = dp_scan->f.timeout; 1407 newlock->owner = dp_scan->owner; 1408 newlock->auth_user = dp_scan->auth_user; 1409 1410 newlock->next = *locks; 1411 *locks = newlock; 1412 1413 /* save the (resolved) direct lock back */ 1414 if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp, 1415 ref_ip)) != NULL) { 1416 /* ### push a higher-level desc? */ 1417 return err; 1418 } 1419 } 1420 } 1421 1422 return NULL; 1423} 1424 1425 1426const dav_hooks_locks dav_hooks_locks_fs = 1427{ 1428 dav_fs_get_supportedlock, 1429 dav_fs_parse_locktoken, 1430 dav_fs_format_locktoken, 1431 dav_fs_compare_locktoken, 1432 dav_fs_open_lockdb, 1433 dav_fs_close_lockdb, 1434 dav_fs_remove_locknull_state, 1435 dav_fs_create_lock, 1436 dav_fs_get_locks, 1437 dav_fs_find_lock, 1438 dav_fs_has_locks, 1439 dav_fs_append_locks, 1440 dav_fs_remove_lock, 1441 dav_fs_refresh_locks, 1442 NULL, /* lookup_resource */ 1443 1444 NULL /* ctx */ 1445}; 1446