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