1/* lock.c : functions for manipulating filesystem locks. 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23#include "svn_pools.h" 24#include "svn_error.h" 25#include "svn_dirent_uri.h" 26#include "svn_path.h" 27#include "svn_fs.h" 28#include "svn_hash.h" 29#include "svn_time.h" 30#include "svn_utf.h" 31 32#include <apr_uuid.h> 33#include <apr_file_io.h> 34#include <apr_file_info.h> 35 36#include "lock.h" 37#include "tree.h" 38#include "fs_x.h" 39#include "transaction.h" 40#include "util.h" 41#include "../libsvn_fs/fs-loader.h" 42 43#include "private/svn_fs_util.h" 44#include "private/svn_fspath.h" 45#include "private/svn_sorts_private.h" 46#include "svn_private_config.h" 47 48/* Names of hash keys used to store a lock for writing to disk. */ 49#define PATH_KEY "path" 50#define TOKEN_KEY "token" 51#define OWNER_KEY "owner" 52#define CREATION_DATE_KEY "creation_date" 53#define EXPIRATION_DATE_KEY "expiration_date" 54#define COMMENT_KEY "comment" 55#define IS_DAV_COMMENT_KEY "is_dav_comment" 56#define CHILDREN_KEY "children" 57 58/* Number of characters from the head of a digest file name used to 59 calculate a subdirectory in which to drop that file. */ 60#define DIGEST_SUBDIR_LEN 3 61 62 63 64/*** Generic helper functions. ***/ 65 66/* Set *DIGEST to the MD5 hash of STR. */ 67static svn_error_t * 68make_digest(const char **digest, 69 const char *str, 70 apr_pool_t *pool) 71{ 72 svn_checksum_t *checksum; 73 74 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool)); 75 76 *digest = svn_checksum_to_cstring_display(checksum, pool); 77 return SVN_NO_ERROR; 78} 79 80 81/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING 82 if unknown) to an svn_string_t-ized version of VALUE (whose size is 83 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value 84 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE 85 is NULL, this function will do nothing. */ 86static void 87hash_store(apr_hash_t *hash, 88 const char *key, 89 apr_ssize_t key_len, 90 const char *value, 91 apr_ssize_t value_len, 92 apr_pool_t *pool) 93{ 94 if (! (key && value)) 95 return; 96 if (value_len == APR_HASH_KEY_STRING) 97 value_len = strlen(value); 98 apr_hash_set(hash, key, key_len, 99 svn_string_ncreate(value, value_len, pool)); 100} 101 102 103/* Fetch the value of KEY from HASH, returning only the cstring data 104 of that value (if it exists). */ 105static const char * 106hash_fetch(apr_hash_t *hash, 107 const char *key) 108{ 109 svn_string_t *str = svn_hash_gets(hash, key); 110 return str ? str->data : NULL; 111} 112 113 114/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */ 115static svn_error_t * 116err_corrupt_lockfile(const char *fs_path, 117 const char *path) 118{ 119 return 120 svn_error_createf( 121 SVN_ERR_FS_CORRUPT, 0, 122 _("Corrupt lockfile for path '%s' in filesystem '%s'"), 123 path, fs_path); 124} 125 126 127/*** Digest file handling functions. ***/ 128 129/* Return the path of the lock/entries file for which DIGEST is the 130 hashed repository relative path. */ 131static const char * 132digest_path_from_digest(const char *fs_path, 133 const char *digest, 134 apr_pool_t *pool) 135{ 136 return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 137 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN), 138 digest, SVN_VA_NULL); 139} 140 141 142/* Set *DIGEST_PATH to the path to the lock/entries digest file associate 143 with PATH, where PATH is the path to the lock file or lock entries file 144 in FS. */ 145static svn_error_t * 146digest_path_from_path(const char **digest_path, 147 const char *fs_path, 148 const char *path, 149 apr_pool_t *pool) 150{ 151 const char *digest; 152 SVN_ERR(make_digest(&digest, path, pool)); 153 *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 154 apr_pstrmemdup(pool, digest, 155 DIGEST_SUBDIR_LEN), 156 digest, SVN_VA_NULL); 157 return SVN_NO_ERROR; 158} 159 160 161/* Write to DIGEST_PATH a representation of CHILDREN (which may be 162 empty, if the versioned path in FS represented by DIGEST_PATH has 163 no children) and LOCK (which may be NULL if that versioned path is 164 lock itself locked). Set the permissions of DIGEST_PATH to those of 165 PERMS_REFERENCE. Use POOL for temporary allocations. 166 */ 167static svn_error_t * 168write_digest_file(apr_hash_t *children, 169 svn_lock_t *lock, 170 const char *fs_path, 171 const char *digest_path, 172 const char *perms_reference, 173 apr_pool_t *scratch_pool) 174{ 175 svn_error_t *err = SVN_NO_ERROR; 176 svn_stream_t *stream; 177 apr_hash_index_t *hi; 178 apr_hash_t *hash = apr_hash_make(scratch_pool); 179 const char *tmp_path; 180 181 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR, 182 scratch_pool), 183 fs_path, scratch_pool)); 184 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path, 185 scratch_pool), 186 fs_path, scratch_pool)); 187 188 if (lock) 189 { 190 const char *creation_date = NULL, *expiration_date = NULL; 191 if (lock->creation_date) 192 creation_date = svn_time_to_cstring(lock->creation_date, 193 scratch_pool); 194 if (lock->expiration_date) 195 expiration_date = svn_time_to_cstring(lock->expiration_date, 196 scratch_pool); 197 198 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1, 199 lock->path, APR_HASH_KEY_STRING, scratch_pool); 200 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1, 201 lock->token, APR_HASH_KEY_STRING, scratch_pool); 202 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1, 203 lock->owner, APR_HASH_KEY_STRING, scratch_pool); 204 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1, 205 lock->comment, APR_HASH_KEY_STRING, scratch_pool); 206 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1, 207 lock->is_dav_comment ? "1" : "0", 1, scratch_pool); 208 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1, 209 creation_date, APR_HASH_KEY_STRING, scratch_pool); 210 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1, 211 expiration_date, APR_HASH_KEY_STRING, scratch_pool); 212 } 213 if (apr_hash_count(children)) 214 { 215 svn_stringbuf_t *children_list 216 = svn_stringbuf_create_empty(scratch_pool); 217 for (hi = apr_hash_first(scratch_pool, children); 218 hi; 219 hi = apr_hash_next(hi)) 220 { 221 svn_stringbuf_appendbytes(children_list, 222 apr_hash_this_key(hi), 223 apr_hash_this_key_len(hi)); 224 svn_stringbuf_appendbyte(children_list, '\n'); 225 } 226 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1, 227 children_list->data, children_list->len, scratch_pool); 228 } 229 230 SVN_ERR(svn_stream_open_unique(&stream, &tmp_path, 231 svn_dirent_dirname(digest_path, 232 scratch_pool), 233 svn_io_file_del_none, scratch_pool, 234 scratch_pool)); 235 if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, 236 scratch_pool))) 237 { 238 err = svn_error_compose_create(err, svn_stream_close(stream)); 239 return svn_error_createf(err->apr_err, 240 err, 241 _("Cannot write lock/entries hashfile '%s'"), 242 svn_dirent_local_style(tmp_path, 243 scratch_pool)); 244 } 245 246 SVN_ERR(svn_stream_close(stream)); 247 SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, scratch_pool)); 248 SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool)); 249 return SVN_NO_ERROR; 250} 251 252 253/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that 254 file (if it exists, and if *LOCK_P is non-NULL) and the hash of 255 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL 256 for all allocations. */ 257static svn_error_t * 258read_digest_file(apr_hash_t **children_p, 259 svn_lock_t **lock_p, 260 const char *fs_path, 261 const char *digest_path, 262 apr_pool_t *pool) 263{ 264 svn_error_t *err = SVN_NO_ERROR; 265 svn_lock_t *lock; 266 apr_hash_t *hash; 267 svn_stream_t *stream; 268 const char *val; 269 svn_node_kind_t kind; 270 271 if (lock_p) 272 *lock_p = NULL; 273 if (children_p) 274 *children_p = apr_hash_make(pool); 275 276 SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 277 if (kind == svn_node_none) 278 return SVN_NO_ERROR; 279 280 /* If our caller doesn't care about anything but the presence of the 281 file... whatever. */ 282 if (kind == svn_node_file && !lock_p && !children_p) 283 return SVN_NO_ERROR; 284 285 SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool)); 286 287 hash = apr_hash_make(pool); 288 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool))) 289 { 290 err = svn_error_compose_create(err, svn_stream_close(stream)); 291 return svn_error_createf(err->apr_err, 292 err, 293 _("Can't parse lock/entries hashfile '%s'"), 294 svn_dirent_local_style(digest_path, pool)); 295 } 296 SVN_ERR(svn_stream_close(stream)); 297 298 /* If our caller cares, see if we have a lock path in our hash. If 299 so, we'll assume we have a lock here. */ 300 val = hash_fetch(hash, PATH_KEY); 301 if (val && lock_p) 302 { 303 const char *path = val; 304 305 /* Create our lock and load it up. */ 306 lock = svn_lock_create(pool); 307 lock->path = path; 308 309 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY)))) 310 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 311 312 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY)))) 313 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 314 315 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY)))) 316 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 317 lock->is_dav_comment = (val[0] == '1'); 318 319 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY)))) 320 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 321 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool)); 322 323 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY))) 324 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool)); 325 326 lock->comment = hash_fetch(hash, COMMENT_KEY); 327 328 *lock_p = lock; 329 } 330 331 /* If our caller cares, see if we have any children for this path. */ 332 val = hash_fetch(hash, CHILDREN_KEY); 333 if (val && children_p) 334 { 335 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool); 336 int i; 337 338 for (i = 0; i < kiddos->nelts; i++) 339 { 340 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *), 341 (void *)1); 342 } 343 } 344 return SVN_NO_ERROR; 345} 346 347 348 349/*** Lock helper functions (path here are still FS paths, not on-disk 350 schema-supporting paths) ***/ 351 352 353/* Write LOCK in FS to the actual OS filesystem. 354 355 Use PERMS_REFERENCE for the permissions of any digest files. 356 */ 357static svn_error_t * 358set_lock(const char *fs_path, 359 svn_lock_t *lock, 360 const char *perms_reference, 361 apr_pool_t *scratch_pool) 362{ 363 const char *digest_path; 364 apr_hash_t *children; 365 366 SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, 367 scratch_pool)); 368 369 /* We could get away without reading the file as children should 370 always come back empty. */ 371 SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, 372 scratch_pool)); 373 374 SVN_ERR(write_digest_file(children, lock, fs_path, digest_path, 375 perms_reference, scratch_pool)); 376 377 return SVN_NO_ERROR; 378} 379 380static svn_error_t * 381delete_lock(const char *fs_path, 382 const char *path, 383 apr_pool_t *scratch_pool) 384{ 385 const char *digest_path; 386 387 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool)); 388 389 SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool)); 390 391 return SVN_NO_ERROR; 392} 393 394static svn_error_t * 395add_to_digest(const char *fs_path, 396 apr_array_header_t *paths, 397 const char *index_path, 398 const char *perms_reference, 399 apr_pool_t *scratch_pool) 400{ 401 const char *index_digest_path; 402 apr_hash_t *children; 403 svn_lock_t *lock; 404 int i; 405 unsigned int original_count; 406 407 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, 408 scratch_pool)); 409 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, 410 scratch_pool)); 411 412 original_count = apr_hash_count(children); 413 414 for (i = 0; i < paths->nelts; ++i) 415 { 416 const char *path = APR_ARRAY_IDX(paths, i, const char *); 417 const char *digest_path, *digest_file; 418 419 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, 420 scratch_pool)); 421 digest_file = svn_dirent_basename(digest_path, NULL); 422 svn_hash_sets(children, digest_file, (void *)1); 423 } 424 425 if (apr_hash_count(children) != original_count) 426 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 427 perms_reference, scratch_pool)); 428 429 return SVN_NO_ERROR; 430} 431 432static svn_error_t * 433delete_from_digest(const char *fs_path, 434 apr_array_header_t *paths, 435 const char *index_path, 436 const char *perms_reference, 437 apr_pool_t *scratch_pool) 438{ 439 const char *index_digest_path; 440 apr_hash_t *children; 441 svn_lock_t *lock; 442 int i; 443 444 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, 445 scratch_pool)); 446 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, 447 scratch_pool)); 448 449 for (i = 0; i < paths->nelts; ++i) 450 { 451 const char *path = APR_ARRAY_IDX(paths, i, const char *); 452 const char *digest_path, *digest_file; 453 454 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, 455 scratch_pool)); 456 digest_file = svn_dirent_basename(digest_path, NULL); 457 svn_hash_sets(children, digest_file, NULL); 458 } 459 460 if (apr_hash_count(children) || lock) 461 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 462 perms_reference, scratch_pool)); 463 else 464 SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool)); 465 466 return SVN_NO_ERROR; 467} 468 469static svn_error_t * 470unlock_single(svn_fs_t *fs, 471 svn_lock_t *lock, 472 apr_pool_t *pool); 473 474/* Check if LOCK has been already expired. */ 475static svn_boolean_t lock_expired(const svn_lock_t *lock) 476{ 477 return lock->expiration_date && (apr_time_now() > lock->expiration_date); 478} 479 480/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 481 TRUE if the caller (or one of its callers) has taken out the 482 repository-wide write lock, FALSE otherwise. If MUST_EXIST is 483 not set, the function will simply return NULL in *LOCK_P instead 484 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock 485 was not found (much faster). Use POOL for allocations. */ 486static svn_error_t * 487get_lock(svn_lock_t **lock_p, 488 svn_fs_t *fs, 489 const char *path, 490 svn_boolean_t have_write_lock, 491 svn_boolean_t must_exist, 492 apr_pool_t *pool) 493{ 494 svn_lock_t *lock = NULL; 495 const char *digest_path; 496 svn_node_kind_t kind; 497 498 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 499 SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 500 501 *lock_p = NULL; 502 if (kind != svn_node_none) 503 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool)); 504 505 if (! lock) 506 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR; 507 508 /* Don't return an expired lock. */ 509 if (lock_expired(lock)) 510 { 511 /* Only remove the lock if we have the write lock. 512 Read operations shouldn't change the filesystem. */ 513 if (have_write_lock) 514 SVN_ERR(unlock_single(fs, lock, pool)); 515 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token); 516 } 517 518 *lock_p = lock; 519 return SVN_NO_ERROR; 520} 521 522 523/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 524 TRUE if the caller (or one of its callers) has taken out the 525 repository-wide write lock, FALSE otherwise. Use POOL for 526 allocations. */ 527static svn_error_t * 528get_lock_helper(svn_fs_t *fs, 529 svn_lock_t **lock_p, 530 const char *path, 531 svn_boolean_t have_write_lock, 532 apr_pool_t *pool) 533{ 534 svn_lock_t *lock; 535 svn_error_t *err; 536 537 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool); 538 539 /* We've deliberately decided that this function doesn't tell the 540 caller *why* the lock is unavailable. */ 541 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) 542 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED))) 543 { 544 svn_error_clear(err); 545 *lock_p = NULL; 546 return SVN_NO_ERROR; 547 } 548 else 549 SVN_ERR(err); 550 551 *lock_p = lock; 552 return SVN_NO_ERROR; 553} 554 555 556/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for 557 all locks in and under PATH in FS. 558 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 559 has the FS write lock. */ 560static svn_error_t * 561walk_locks(svn_fs_t *fs, 562 const char *digest_path, 563 svn_fs_get_locks_callback_t get_locks_func, 564 void *get_locks_baton, 565 svn_boolean_t have_write_lock, 566 apr_pool_t *pool) 567{ 568 apr_hash_index_t *hi; 569 apr_hash_t *children; 570 apr_pool_t *subpool; 571 svn_lock_t *lock; 572 573 /* First, send up any locks in the current digest file. */ 574 SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool)); 575 576 if (lock && lock_expired(lock)) 577 { 578 /* Only remove the lock if we have the write lock. 579 Read operations shouldn't change the filesystem. */ 580 if (have_write_lock) 581 SVN_ERR(unlock_single(fs, lock, pool)); 582 } 583 else if (lock) 584 { 585 SVN_ERR(get_locks_func(get_locks_baton, lock, pool)); 586 } 587 588 /* Now, report all the child entries (if any; bail otherwise). */ 589 if (! apr_hash_count(children)) 590 return SVN_NO_ERROR; 591 subpool = svn_pool_create(pool); 592 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) 593 { 594 const char *digest = apr_hash_this_key(hi); 595 svn_pool_clear(subpool); 596 597 SVN_ERR(read_digest_file 598 (NULL, &lock, fs->path, 599 digest_path_from_digest(fs->path, digest, subpool), subpool)); 600 601 if (lock && lock_expired(lock)) 602 { 603 /* Only remove the lock if we have the write lock. 604 Read operations shouldn't change the filesystem. */ 605 if (have_write_lock) 606 SVN_ERR(unlock_single(fs, lock, pool)); 607 } 608 else if (lock) 609 { 610 SVN_ERR(get_locks_func(get_locks_baton, lock, pool)); 611 } 612 } 613 svn_pool_destroy(subpool); 614 return SVN_NO_ERROR; 615} 616 617/* Utility function: verify that a lock can be used. Interesting 618 errors returned from this function: 619 620 SVN_ERR_FS_NO_USER: No username attached to FS. 621 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner. 622 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK. 623 */ 624static svn_error_t * 625verify_lock(svn_fs_t *fs, 626 svn_lock_t *lock) 627{ 628 if ((! fs->access_ctx) || (! fs->access_ctx->username)) 629 return svn_error_createf 630 (SVN_ERR_FS_NO_USER, NULL, 631 _("Cannot verify lock on path '%s'; no username available"), 632 lock->path); 633 634 else if (strcmp(fs->access_ctx->username, lock->owner) != 0) 635 return svn_error_createf 636 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 637 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), 638 fs->access_ctx->username, lock->path, lock->owner); 639 640 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) 641 return svn_error_createf 642 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 643 _("Cannot verify lock on path '%s'; no matching lock-token available"), 644 lock->path); 645 646 return SVN_NO_ERROR; 647} 648 649 650/* This implements the svn_fs_get_locks_callback_t interface, where 651 BATON is just an svn_fs_t object. */ 652static svn_error_t * 653get_locks_callback(void *baton, 654 svn_lock_t *lock, 655 apr_pool_t *pool) 656{ 657 return verify_lock(baton, lock); 658} 659 660 661/* The main routine for lock enforcement, used throughout libsvn_fs_x. */ 662svn_error_t * 663svn_fs_x__allow_locked_operation(const char *path, 664 svn_fs_t *fs, 665 svn_boolean_t recurse, 666 svn_boolean_t have_write_lock, 667 apr_pool_t *scratch_pool) 668{ 669 path = svn_fs__canonicalize_abspath(path, scratch_pool); 670 if (recurse) 671 { 672 /* Discover all locks at or below the path. */ 673 const char *digest_path; 674 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, 675 scratch_pool)); 676 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback, 677 fs, have_write_lock, scratch_pool)); 678 } 679 else 680 { 681 /* Discover and verify any lock attached to the path. */ 682 svn_lock_t *lock; 683 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, 684 scratch_pool)); 685 if (lock) 686 SVN_ERR(verify_lock(fs, lock)); 687 } 688 return SVN_NO_ERROR; 689} 690 691/* Helper function called from the lock and unlock code. 692 UPDATES is a map from "const char *" parent paths to "apr_array_header_t *" 693 arrays of child paths. For all of the parent paths of PATH this function 694 adds PATH to the corresponding array of child paths. */ 695static void 696schedule_index_update(apr_hash_t *updates, 697 const char *path, 698 apr_pool_t *scratch_pool) 699{ 700 apr_pool_t *hashpool = apr_hash_pool_get(updates); 701 const char *parent_path = path; 702 703 while (! svn_fspath__is_root(parent_path, strlen(parent_path))) 704 { 705 apr_array_header_t *children; 706 707 parent_path = svn_fspath__dirname(parent_path, scratch_pool); 708 children = svn_hash_gets(updates, parent_path); 709 710 if (! children) 711 { 712 children = apr_array_make(hashpool, 8, sizeof(const char *)); 713 svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children); 714 } 715 716 APR_ARRAY_PUSH(children, const char *) = path; 717 } 718} 719 720/* The effective arguments for lock_body() below. */ 721typedef struct lock_baton_t { 722 svn_fs_t *fs; 723 apr_array_header_t *targets; 724 apr_array_header_t *infos; 725 const char *comment; 726 svn_boolean_t is_dav_comment; 727 apr_time_t expiration_date; 728 svn_boolean_t steal_lock; 729 apr_pool_t *result_pool; 730} lock_baton_t; 731 732static svn_error_t * 733check_lock(svn_error_t **fs_err, 734 const char *path, 735 const svn_fs_lock_target_t *target, 736 lock_baton_t *lb, 737 svn_fs_root_t *root, 738 svn_revnum_t youngest_rev, 739 apr_pool_t *pool) 740{ 741 svn_node_kind_t kind; 742 svn_lock_t *existing_lock; 743 744 *fs_err = SVN_NO_ERROR; 745 746 SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool)); 747 if (kind == svn_node_dir) 748 { 749 *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path); 750 return SVN_NO_ERROR; 751 } 752 753 /* While our locking implementation easily supports the locking of 754 nonexistent paths, we deliberately choose not to allow such madness. */ 755 if (kind == svn_node_none) 756 { 757 if (SVN_IS_VALID_REVNUM(target->current_rev)) 758 *fs_err = svn_error_createf( 759 SVN_ERR_FS_OUT_OF_DATE, NULL, 760 _("Path '%s' doesn't exist in HEAD revision"), 761 path); 762 else 763 *fs_err = svn_error_createf( 764 SVN_ERR_FS_NOT_FOUND, NULL, 765 _("Path '%s' doesn't exist in HEAD revision"), 766 path); 767 768 return SVN_NO_ERROR; 769 } 770 771 /* Is the caller attempting to lock an out-of-date working file? */ 772 if (SVN_IS_VALID_REVNUM(target->current_rev)) 773 { 774 svn_revnum_t created_rev; 775 776 if (target->current_rev > youngest_rev) 777 { 778 *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 779 _("No such revision %ld"), 780 target->current_rev); 781 return SVN_NO_ERROR; 782 } 783 784 SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path, 785 pool)); 786 787 /* SVN_INVALID_REVNUM means the path doesn't exist. So 788 apparently somebody is trying to lock something in their 789 working copy, but somebody else has deleted the thing 790 from HEAD. That counts as being 'out of date'. */ 791 if (! SVN_IS_VALID_REVNUM(created_rev)) 792 { 793 *fs_err = svn_error_createf 794 (SVN_ERR_FS_OUT_OF_DATE, NULL, 795 _("Path '%s' doesn't exist in HEAD revision"), path); 796 797 return SVN_NO_ERROR; 798 } 799 800 if (target->current_rev < created_rev) 801 { 802 *fs_err = svn_error_createf 803 (SVN_ERR_FS_OUT_OF_DATE, NULL, 804 _("Lock failed: newer version of '%s' exists"), path); 805 806 return SVN_NO_ERROR; 807 } 808 } 809 810 /* If the caller provided a TOKEN, we *really* need to see 811 if a lock already exists with that token, and if so, verify that 812 the lock's path matches PATH. Otherwise we run the risk of 813 breaking the 1-to-1 mapping of lock tokens to locked paths. */ 814 /* ### TODO: actually do this check. This is tough, because the 815 schema doesn't supply a lookup-by-token mechanism. */ 816 817 /* Is the path already locked? 818 819 Note that this next function call will automatically ignore any 820 errors about {the path not existing as a key, the path's token 821 not existing as a key, the lock just having been expired}. And 822 that's totally fine. Any of these three errors are perfectly 823 acceptable to ignore; it means that the path is now free and 824 clear for locking, because the fsx funcs just cleared out both 825 of the tables for us. */ 826 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool)); 827 if (existing_lock) 828 { 829 if (! lb->steal_lock) 830 { 831 /* Sorry, the path is already locked. */ 832 *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock); 833 return SVN_NO_ERROR; 834 } 835 } 836 837 return SVN_NO_ERROR; 838} 839 840typedef struct lock_info_t { 841 const char *path; 842 svn_lock_t *lock; 843 svn_error_t *fs_err; 844} lock_info_t; 845 846/* The body of svn_fs_x__lock(), which see. 847 848 BATON is a 'lock_baton_t *' holding the effective arguments. 849 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 850 path, mapping canonical path to 'svn_fs_lock_target_t'. Set 851 BATON->infos to an array of 'lock_info_t' holding the results. For 852 the other arguments, see svn_fs_lock_many(). 853 854 This implements the svn_fs_x__with_write_lock() 'body' callback 855 type, and assumes that the write lock is held. 856 */ 857static svn_error_t * 858lock_body(void *baton, 859 apr_pool_t *pool) 860{ 861 lock_baton_t *lb = baton; 862 svn_fs_root_t *root; 863 svn_revnum_t youngest; 864 const char *rev_0_path; 865 int i; 866 apr_hash_t *index_updates = apr_hash_make(pool); 867 apr_hash_index_t *hi; 868 apr_pool_t *iterpool = svn_pool_create(pool); 869 870 /* Until we implement directory locks someday, we only allow locks 871 on files. */ 872 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular 873 library dependencies, which are not portable. */ 874 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool)); 875 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool)); 876 877 for (i = 0; i < lb->targets->nelts; ++i) 878 { 879 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, 880 svn_sort__item_t); 881 lock_info_t info; 882 883 svn_pool_clear(iterpool); 884 885 info.path = item->key; 886 info.lock = NULL; 887 info.fs_err = SVN_NO_ERROR; 888 889 SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root, 890 youngest, iterpool)); 891 892 /* If no error occurred while pre-checking, schedule the index updates for 893 this path. */ 894 if (!info.fs_err) 895 schedule_index_update(index_updates, info.path, iterpool); 896 897 APR_ARRAY_PUSH(lb->infos, lock_info_t) = info; 898 } 899 900 rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool); 901 902 /* We apply the scheduled index updates before writing the actual locks. 903 904 Writing indices before locks is correct: if interrupted it leaves 905 indices without locks rather than locks without indices. An 906 index without a lock is consistent in that it always shows up as 907 unlocked in svn_fs_x__allow_locked_operation. A lock without an 908 index is inconsistent, svn_fs_x__allow_locked_operation will 909 show locked on the file but unlocked on the parent. */ 910 911 for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi)) 912 { 913 const char *path = apr_hash_this_key(hi); 914 apr_array_header_t *children = apr_hash_this_val(hi); 915 916 svn_pool_clear(iterpool); 917 SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path, 918 iterpool)); 919 } 920 921 for (i = 0; i < lb->infos->nelts; ++i) 922 { 923 struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, 924 struct lock_info_t); 925 svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t); 926 svn_fs_lock_target_t *target = item->value; 927 928 svn_pool_clear(iterpool); 929 930 if (! info->fs_err) 931 { 932 info->lock = svn_lock_create(lb->result_pool); 933 if (target->token) 934 info->lock->token = apr_pstrdup(lb->result_pool, target->token); 935 else 936 SVN_ERR(svn_fs_x__generate_lock_token(&(info->lock->token), lb->fs, 937 lb->result_pool)); 938 939 /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result 940 of svn_fspath__canonicalize() (see svn_fs_x__lock()). */ 941 info->lock->path = info->path; 942 info->lock->owner = apr_pstrdup(lb->result_pool, 943 lb->fs->access_ctx->username); 944 info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment); 945 info->lock->is_dav_comment = lb->is_dav_comment; 946 info->lock->creation_date = apr_time_now(); 947 info->lock->expiration_date = lb->expiration_date; 948 949 info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path, 950 iterpool); 951 } 952 } 953 954 svn_pool_destroy(iterpool); 955 return SVN_NO_ERROR; 956} 957 958/* The effective arguments for unlock_body() below. */ 959typedef struct unlock_baton_t { 960 svn_fs_t *fs; 961 apr_array_header_t *targets; 962 apr_array_header_t *infos; 963 /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */ 964 svn_boolean_t skip_check; 965 svn_boolean_t break_lock; 966 apr_pool_t *result_pool; 967} unlock_baton_t; 968 969static svn_error_t * 970check_unlock(svn_error_t **fs_err, 971 const char *path, 972 const char *token, 973 unlock_baton_t *ub, 974 svn_fs_root_t *root, 975 apr_pool_t *pool) 976{ 977 svn_lock_t *lock; 978 979 *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool); 980 if (!*fs_err && !ub->break_lock) 981 { 982 if (strcmp(token, lock->token) != 0) 983 *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path); 984 else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0) 985 *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs, 986 ub->fs->access_ctx->username, 987 lock->owner); 988 } 989 990 return SVN_NO_ERROR; 991} 992 993typedef struct unlock_info_t { 994 const char *path; 995 svn_error_t *fs_err; 996 svn_boolean_t done; 997} unlock_info_t; 998 999/* The body of svn_fs_x__unlock(), which see. 1000 1001 BATON is a 'unlock_baton_t *' holding the effective arguments. 1002 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 1003 path, mapping canonical path to (const char *) token. Set 1004 BATON->infos to an array of 'unlock_info_t' results. For the other 1005 arguments, see svn_fs_unlock_many(). 1006 1007 This implements the svn_fs_x__with_write_lock() 'body' callback 1008 type, and assumes that the write lock is held. 1009 */ 1010static svn_error_t * 1011unlock_body(void *baton, 1012 apr_pool_t *pool) 1013{ 1014 unlock_baton_t *ub = baton; 1015 svn_fs_root_t *root; 1016 svn_revnum_t youngest; 1017 const char *rev_0_path; 1018 int i; 1019 apr_hash_t *indices_updates = apr_hash_make(pool); 1020 apr_hash_index_t *hi; 1021 apr_pool_t *iterpool = svn_pool_create(pool); 1022 1023 SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool)); 1024 SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool)); 1025 1026 for (i = 0; i < ub->targets->nelts; ++i) 1027 { 1028 const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i, 1029 svn_sort__item_t); 1030 const char *token = item->value; 1031 unlock_info_t info; 1032 1033 svn_pool_clear(iterpool); 1034 1035 info.path = item->key; 1036 info.fs_err = SVN_NO_ERROR; 1037 info.done = FALSE; 1038 1039 if (!ub->skip_check) 1040 SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root, 1041 iterpool)); 1042 1043 /* If no error occurred while pre-checking, schedule the index updates for 1044 this path. */ 1045 if (!info.fs_err) 1046 schedule_index_update(indices_updates, info.path, iterpool); 1047 1048 APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info; 1049 } 1050 1051 rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool); 1052 1053 /* Unlike the lock_body(), we need to delete locks *before* we start to 1054 update indices. */ 1055 1056 for (i = 0; i < ub->infos->nelts; ++i) 1057 { 1058 struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i, 1059 struct unlock_info_t); 1060 1061 svn_pool_clear(iterpool); 1062 1063 if (! info->fs_err) 1064 { 1065 SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool)); 1066 info->done = TRUE; 1067 } 1068 } 1069 1070 for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi)) 1071 { 1072 const char *path = apr_hash_this_key(hi); 1073 apr_array_header_t *children = apr_hash_this_val(hi); 1074 1075 svn_pool_clear(iterpool); 1076 SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path, 1077 iterpool)); 1078 } 1079 1080 svn_pool_destroy(iterpool); 1081 return SVN_NO_ERROR; 1082} 1083 1084/* Unlock the lock described by LOCK->path and LOCK->token in FS. 1085 1086 This assumes that the write lock is held. 1087 */ 1088static svn_error_t * 1089unlock_single(svn_fs_t *fs, 1090 svn_lock_t *lock, 1091 apr_pool_t *scratch_pool) 1092{ 1093 unlock_baton_t ub; 1094 svn_sort__item_t item; 1095 apr_array_header_t *targets = apr_array_make(scratch_pool, 1, 1096 sizeof(svn_sort__item_t)); 1097 item.key = lock->path; 1098 item.klen = strlen(item.key); 1099 item.value = (char*)lock->token; 1100 APR_ARRAY_PUSH(targets, svn_sort__item_t) = item; 1101 1102 ub.fs = fs; 1103 ub.targets = targets; 1104 ub.infos = apr_array_make(scratch_pool, targets->nelts, 1105 sizeof(struct unlock_info_t)); 1106 ub.skip_check = TRUE; 1107 ub.result_pool = scratch_pool; 1108 1109 /* No ub.infos[].fs_err error because skip_check is TRUE. */ 1110 SVN_ERR(unlock_body(&ub, scratch_pool)); 1111 1112 return SVN_NO_ERROR; 1113} 1114 1115 1116/*** Public API implementations ***/ 1117 1118svn_error_t * 1119svn_fs_x__lock(svn_fs_t *fs, 1120 apr_hash_t *targets, 1121 const char *comment, 1122 svn_boolean_t is_dav_comment, 1123 apr_time_t expiration_date, 1124 svn_boolean_t steal_lock, 1125 svn_fs_lock_callback_t lock_callback, 1126 void *lock_baton, 1127 apr_pool_t *result_pool, 1128 apr_pool_t *scratch_pool) 1129{ 1130 lock_baton_t lb; 1131 apr_array_header_t *sorted_targets; 1132 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1133 apr_hash_index_t *hi; 1134 apr_pool_t *iterpool; 1135 svn_error_t *err, *cb_err = SVN_NO_ERROR; 1136 int i; 1137 1138 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1139 1140 /* We need to have a username attached to the fs. */ 1141 if (!fs->access_ctx || !fs->access_ctx->username) 1142 return SVN_FS__ERR_NO_USER(fs); 1143 1144 /* The FS locking API allows both canonical and non-canonical 1145 paths which means that the same canonical path could be 1146 represented more than once in the TARGETS hash. We just keep 1147 one, choosing one with a token if possible. */ 1148 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1149 { 1150 const char *path = apr_hash_this_key(hi); 1151 const svn_fs_lock_target_t *target = apr_hash_this_val(hi); 1152 const svn_fs_lock_target_t *other; 1153 1154 path = svn_fspath__canonicalize(path, result_pool); 1155 other = svn_hash_gets(canonical_targets, path); 1156 1157 if (!other || (!other->token && target->token)) 1158 svn_hash_sets(canonical_targets, path, target); 1159 } 1160 1161 sorted_targets = svn_sort__hash(canonical_targets, 1162 svn_sort_compare_items_as_paths, 1163 scratch_pool); 1164 1165 lb.fs = fs; 1166 lb.targets = sorted_targets; 1167 lb.infos = apr_array_make(result_pool, sorted_targets->nelts, 1168 sizeof(struct lock_info_t)); 1169 lb.comment = comment; 1170 lb.is_dav_comment = is_dav_comment; 1171 lb.expiration_date = expiration_date; 1172 lb.steal_lock = steal_lock; 1173 lb.result_pool = result_pool; 1174 1175 iterpool = svn_pool_create(scratch_pool); 1176 err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool); 1177 for (i = 0; i < lb.infos->nelts; ++i) 1178 { 1179 struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i, 1180 struct lock_info_t); 1181 svn_pool_clear(iterpool); 1182 if (!cb_err && lock_callback) 1183 { 1184 if (!info->lock && !info->fs_err) 1185 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1186 0, _("Failed to lock '%s'"), 1187 info->path); 1188 1189 cb_err = lock_callback(lock_baton, info->path, info->lock, 1190 info->fs_err, iterpool); 1191 } 1192 svn_error_clear(info->fs_err); 1193 } 1194 svn_pool_destroy(iterpool); 1195 1196 if (err && cb_err) 1197 svn_error_compose(err, cb_err); 1198 else if (!err) 1199 err = cb_err; 1200 1201 return svn_error_trace(err); 1202} 1203 1204 1205svn_error_t * 1206svn_fs_x__generate_lock_token(const char **token, 1207 svn_fs_t *fs, 1208 apr_pool_t *pool) 1209{ 1210 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1211 1212 /* Notice that 'fs' is currently unused. But perhaps someday, we'll 1213 want to use the fs UUID + some incremented number? For now, we 1214 generate a URI that matches the DAV RFC. We could change this to 1215 some other URI scheme someday, if we wish. */ 1216 *token = apr_pstrcat(pool, "opaquelocktoken:", 1217 svn_uuid_generate(pool), SVN_VA_NULL); 1218 return SVN_NO_ERROR; 1219} 1220 1221svn_error_t * 1222svn_fs_x__unlock(svn_fs_t *fs, 1223 apr_hash_t *targets, 1224 svn_boolean_t break_lock, 1225 svn_fs_lock_callback_t lock_callback, 1226 void *lock_baton, 1227 apr_pool_t *result_pool, 1228 apr_pool_t *scratch_pool) 1229{ 1230 unlock_baton_t ub; 1231 apr_array_header_t *sorted_targets; 1232 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1233 apr_hash_index_t *hi; 1234 apr_pool_t *iterpool; 1235 svn_error_t *err, *cb_err = SVN_NO_ERROR; 1236 int i; 1237 1238 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1239 1240 /* We need to have a username attached to the fs. */ 1241 if (!fs->access_ctx || !fs->access_ctx->username) 1242 return SVN_FS__ERR_NO_USER(fs); 1243 1244 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1245 { 1246 const char *path = apr_hash_this_key(hi); 1247 const char *token = apr_hash_this_val(hi); 1248 const char *other; 1249 1250 path = svn_fspath__canonicalize(path, result_pool); 1251 other = svn_hash_gets(canonical_targets, path); 1252 1253 if (!other) 1254 svn_hash_sets(canonical_targets, path, token); 1255 } 1256 1257 sorted_targets = svn_sort__hash(canonical_targets, 1258 svn_sort_compare_items_as_paths, 1259 scratch_pool); 1260 1261 ub.fs = fs; 1262 ub.targets = sorted_targets; 1263 ub.infos = apr_array_make(result_pool, sorted_targets->nelts, 1264 sizeof(struct unlock_info_t)); 1265 ub.skip_check = FALSE; 1266 ub.break_lock = break_lock; 1267 ub.result_pool = result_pool; 1268 1269 iterpool = svn_pool_create(scratch_pool); 1270 err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool); 1271 for (i = 0; i < ub.infos->nelts; ++i) 1272 { 1273 unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t); 1274 svn_pool_clear(iterpool); 1275 if (!cb_err && lock_callback) 1276 { 1277 if (!info->done && !info->fs_err) 1278 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1279 0, _("Failed to unlock '%s'"), 1280 info->path); 1281 cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err, 1282 iterpool); 1283 } 1284 svn_error_clear(info->fs_err); 1285 } 1286 svn_pool_destroy(iterpool); 1287 1288 if (err && cb_err) 1289 svn_error_compose(err, cb_err); 1290 else if (!err) 1291 err = cb_err; 1292 1293 return svn_error_trace(err); 1294} 1295 1296 1297svn_error_t * 1298svn_fs_x__get_lock(svn_lock_t **lock_p, 1299 svn_fs_t *fs, 1300 const char *path, 1301 apr_pool_t *pool) 1302{ 1303 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1304 path = svn_fs__canonicalize_abspath(path, pool); 1305 return get_lock_helper(fs, lock_p, path, FALSE, pool); 1306} 1307 1308 1309/* Baton for get_locks_filter_func(). */ 1310typedef struct get_locks_filter_baton_t 1311{ 1312 const char *path; 1313 svn_depth_t requested_depth; 1314 svn_fs_get_locks_callback_t get_locks_func; 1315 void *get_locks_baton; 1316 1317} get_locks_filter_baton_t; 1318 1319 1320/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks() 1321 which filters out locks on paths that aren't within 1322 BATON->requested_depth of BATON->path before called 1323 BATON->get_locks_func() with BATON->get_locks_baton. 1324 1325 NOTE: See issue #3660 for details about how the FSX lock 1326 management code is inconsistent. Until that inconsistency is 1327 resolved, we take this filtering approach rather than honoring 1328 depth requests closer to the crawling code. In other words, once 1329 we decide how to resolve issue #3660, there might be a more 1330 performant way to honor the depth passed to svn_fs_x__get_locks(). */ 1331static svn_error_t * 1332get_locks_filter_func(void *baton, 1333 svn_lock_t *lock, 1334 apr_pool_t *pool) 1335{ 1336 get_locks_filter_baton_t *b = baton; 1337 1338 /* Filter out unwanted paths. Since Subversion only allows 1339 locks on files, we can treat depth=immediates the same as 1340 depth=files for filtering purposes. Meaning, we'll keep 1341 this lock if: 1342 1343 a) its path is the very path we queried, or 1344 b) we've asked for a fully recursive answer, or 1345 c) we've asked for depth=files or depth=immediates, and this 1346 lock is on an immediate child of our query path. 1347 */ 1348 if ((strcmp(b->path, lock->path) == 0) 1349 || (b->requested_depth == svn_depth_infinity)) 1350 { 1351 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1352 } 1353 else if ((b->requested_depth == svn_depth_files) || 1354 (b->requested_depth == svn_depth_immediates)) 1355 { 1356 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path); 1357 if (rel_uri && (svn_path_component_count(rel_uri) == 1)) 1358 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1359 } 1360 1361 return SVN_NO_ERROR; 1362} 1363 1364svn_error_t * 1365svn_fs_x__get_locks(svn_fs_t *fs, 1366 const char *path, 1367 svn_depth_t depth, 1368 svn_fs_get_locks_callback_t get_locks_func, 1369 void *get_locks_baton, 1370 apr_pool_t *scratch_pool) 1371{ 1372 const char *digest_path; 1373 get_locks_filter_baton_t glfb; 1374 1375 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1376 path = svn_fs__canonicalize_abspath(path, scratch_pool); 1377 1378 glfb.path = path; 1379 glfb.requested_depth = depth; 1380 glfb.get_locks_func = get_locks_func; 1381 glfb.get_locks_baton = get_locks_baton; 1382 1383 /* Get the top digest path in our tree of interest, and then walk it. */ 1384 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool)); 1385 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb, 1386 FALSE, scratch_pool)); 1387 return SVN_NO_ERROR; 1388} 1389