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