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 24#include "svn_pools.h" 25#include "svn_error.h" 26#include "svn_dirent_uri.h" 27#include "svn_path.h" 28#include "svn_fs.h" 29#include "svn_hash.h" 30#include "svn_time.h" 31#include "svn_utf.h" 32 33#include <apr_uuid.h> 34#include <apr_file_io.h> 35#include <apr_file_info.h> 36 37#include "lock.h" 38#include "tree.h" 39#include "fs_fs.h" 40#include "../libsvn_fs/fs-loader.h" 41 42#include "private/svn_fs_util.h" 43#include "private/svn_fspath.h" 44#include "svn_private_config.h" 45 46/* Names of hash keys used to store a lock for writing to disk. */ 47#define PATH_KEY "path" 48#define TOKEN_KEY "token" 49#define OWNER_KEY "owner" 50#define CREATION_DATE_KEY "creation_date" 51#define EXPIRATION_DATE_KEY "expiration_date" 52#define COMMENT_KEY "comment" 53#define IS_DAV_COMMENT_KEY "is_dav_comment" 54#define CHILDREN_KEY "children" 55 56/* Number of characters from the head of a digest file name used to 57 calculate a subdirectory in which to drop that file. */ 58#define DIGEST_SUBDIR_LEN 3 59 60 61 62/*** Generic helper functions. ***/ 63 64/* Set *DIGEST to the MD5 hash of STR. */ 65static svn_error_t * 66make_digest(const char **digest, 67 const char *str, 68 apr_pool_t *pool) 69{ 70 svn_checksum_t *checksum; 71 72 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool)); 73 74 *digest = svn_checksum_to_cstring_display(checksum, pool); 75 return SVN_NO_ERROR; 76} 77 78 79/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING 80 if unknown) to an svn_string_t-ized version of VALUE (whose size is 81 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value 82 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE 83 is NULL, this function will do nothing. */ 84static void 85hash_store(apr_hash_t *hash, 86 const char *key, 87 apr_ssize_t key_len, 88 const char *value, 89 apr_ssize_t value_len, 90 apr_pool_t *pool) 91{ 92 if (! (key && value)) 93 return; 94 if (value_len == APR_HASH_KEY_STRING) 95 value_len = strlen(value); 96 apr_hash_set(hash, key, key_len, 97 svn_string_ncreate(value, value_len, pool)); 98} 99 100 101/* Fetch the value of KEY from HASH, returning only the cstring data 102 of that value (if it exists). */ 103static const char * 104hash_fetch(apr_hash_t *hash, 105 const char *key, 106 apr_pool_t *pool) 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, 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, 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 svn__apr_hash_index_key(hi), 213 svn__apr_hash_index_klen(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 256 if (lock_p) 257 *lock_p = NULL; 258 if (children_p) 259 *children_p = apr_hash_make(pool); 260 261 err = svn_stream_open_readonly(&stream, digest_path, pool, pool); 262 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 263 { 264 svn_error_clear(err); 265 return SVN_NO_ERROR; 266 } 267 SVN_ERR(err); 268 269 /* If our caller doesn't care about anything but the presence of the 270 file... whatever. */ 271 if (! (lock_p || children_p)) 272 return svn_stream_close(stream); 273 274 hash = apr_hash_make(pool); 275 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool))) 276 { 277 svn_error_clear(svn_stream_close(stream)); 278 return svn_error_createf(err->apr_err, 279 err, 280 _("Can't parse lock/entries hashfile '%s'"), 281 svn_dirent_local_style(digest_path, pool)); 282 } 283 SVN_ERR(svn_stream_close(stream)); 284 285 /* If our caller cares, see if we have a lock path in our hash. If 286 so, we'll assume we have a lock here. */ 287 val = hash_fetch(hash, PATH_KEY, pool); 288 if (val && lock_p) 289 { 290 const char *path = val; 291 292 /* Create our lock and load it up. */ 293 lock = svn_lock_create(pool); 294 lock->path = path; 295 296 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool)))) 297 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 298 299 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool)))) 300 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 301 302 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool)))) 303 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 304 lock->is_dav_comment = (val[0] == '1'); 305 306 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool)))) 307 return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 308 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool)); 309 310 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool))) 311 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool)); 312 313 lock->comment = hash_fetch(hash, COMMENT_KEY, pool); 314 315 *lock_p = lock; 316 } 317 318 /* If our caller cares, see if we have any children for this path. */ 319 val = hash_fetch(hash, CHILDREN_KEY, pool); 320 if (val && children_p) 321 { 322 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool); 323 int i; 324 325 for (i = 0; i < kiddos->nelts; i++) 326 { 327 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *), 328 (void *)1); 329 } 330 } 331 return SVN_NO_ERROR; 332} 333 334 335 336/*** Lock helper functions (path here are still FS paths, not on-disk 337 schema-supporting paths) ***/ 338 339 340/* Write LOCK in FS to the actual OS filesystem. 341 342 Use PERMS_REFERENCE for the permissions of any digest files. 343 344 Note: this takes an FS_PATH because it's called from the hotcopy logic. 345 */ 346static svn_error_t * 347set_lock(const char *fs_path, 348 svn_lock_t *lock, 349 const char *perms_reference, 350 apr_pool_t *pool) 351{ 352 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool); 353 const char *lock_digest_path = NULL; 354 apr_pool_t *subpool; 355 356 SVN_ERR_ASSERT(lock); 357 358 /* Iterate in reverse, creating the lock for LOCK->path, and then 359 just adding entries for its parent, until we reach a parent 360 that's already listed in *its* parent. */ 361 subpool = svn_pool_create(pool); 362 while (1729) 363 { 364 const char *digest_path, *digest_file; 365 apr_hash_t *this_children; 366 svn_lock_t *this_lock; 367 368 svn_pool_clear(subpool); 369 370 /* Calculate the DIGEST_PATH for the currently FS path, and then 371 get its DIGEST_FILE basename. */ 372 SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data, 373 subpool)); 374 digest_file = svn_dirent_basename(digest_path, subpool); 375 376 SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path, 377 digest_path, subpool)); 378 379 /* We're either writing a new lock (first time through only) or 380 a new entry (every time but the first). */ 381 if (lock) 382 { 383 this_lock = lock; 384 lock = NULL; 385 lock_digest_path = apr_pstrdup(pool, digest_file); 386 } 387 else 388 { 389 /* If we already have an entry for this path, we're done. */ 390 if (svn_hash_gets(this_children, lock_digest_path)) 391 break; 392 svn_hash_sets(this_children, lock_digest_path, (void *)1); 393 } 394 SVN_ERR(write_digest_file(this_children, this_lock, fs_path, 395 digest_path, perms_reference, subpool)); 396 397 /* Prep for next iteration, or bail if we're done. */ 398 if (svn_fspath__is_root(this_path->data, this_path->len)) 399 break; 400 svn_stringbuf_set(this_path, 401 svn_fspath__dirname(this_path->data, subpool)); 402 } 403 404 svn_pool_destroy(subpool); 405 return SVN_NO_ERROR; 406} 407 408/* Delete LOCK from FS in the actual OS filesystem. */ 409static svn_error_t * 410delete_lock(svn_fs_t *fs, 411 svn_lock_t *lock, 412 apr_pool_t *pool) 413{ 414 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool); 415 const char *child_to_kill = NULL; 416 apr_pool_t *subpool; 417 418 SVN_ERR_ASSERT(lock); 419 420 /* Iterate in reverse, deleting the lock for LOCK->path, and then 421 deleting its entry as it appears in each of its parents. */ 422 subpool = svn_pool_create(pool); 423 while (1729) 424 { 425 const char *digest_path, *digest_file; 426 apr_hash_t *this_children; 427 svn_lock_t *this_lock; 428 429 svn_pool_clear(subpool); 430 431 /* Calculate the DIGEST_PATH for the currently FS path, and then 432 get its DIGEST_FILE basename. */ 433 SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data, 434 subpool)); 435 digest_file = svn_dirent_basename(digest_path, subpool); 436 437 SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path, 438 digest_path, subpool)); 439 440 /* Delete the lock (first time through only). */ 441 if (lock) 442 { 443 this_lock = NULL; 444 lock = NULL; 445 child_to_kill = apr_pstrdup(pool, digest_file); 446 } 447 448 if (child_to_kill) 449 svn_hash_sets(this_children, child_to_kill, NULL); 450 451 if (! (this_lock || apr_hash_count(this_children) != 0)) 452 { 453 /* Special case: no goodz, no file. And remember to nix 454 the entry for it in its parent. */ 455 SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool)); 456 } 457 else 458 { 459 const char *rev_0_path; 460 SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool)); 461 SVN_ERR(write_digest_file(this_children, this_lock, fs->path, 462 digest_path, rev_0_path, subpool)); 463 } 464 465 /* Prep for next iteration, or bail if we're done. */ 466 if (svn_fspath__is_root(this_path->data, this_path->len)) 467 break; 468 svn_stringbuf_set(this_path, 469 svn_fspath__dirname(this_path->data, subpool)); 470 } 471 472 svn_pool_destroy(subpool); 473 return SVN_NO_ERROR; 474} 475 476/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 477 TRUE if the caller (or one of its callers) has taken out the 478 repository-wide write lock, FALSE otherwise. If MUST_EXIST is 479 not set, the function will simply return NULL in *LOCK_P instead 480 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock 481 was not found (much faster). Use POOL for allocations. */ 482static svn_error_t * 483get_lock(svn_lock_t **lock_p, 484 svn_fs_t *fs, 485 const char *path, 486 svn_boolean_t have_write_lock, 487 svn_boolean_t must_exist, 488 apr_pool_t *pool) 489{ 490 svn_lock_t *lock = NULL; 491 const char *digest_path; 492 svn_node_kind_t kind; 493 494 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 495 SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 496 497 *lock_p = NULL; 498 if (kind != svn_node_none) 499 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool)); 500 501 if (! lock) 502 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR; 503 504 /* Don't return an expired lock. */ 505 if (lock->expiration_date && (apr_time_now() > lock->expiration_date)) 506 { 507 /* Only remove the lock if we have the write lock. 508 Read operations shouldn't change the filesystem. */ 509 if (have_write_lock) 510 SVN_ERR(delete_lock(fs, lock, pool)); 511 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token); 512 } 513 514 *lock_p = lock; 515 return SVN_NO_ERROR; 516} 517 518 519/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 520 TRUE if the caller (or one of its callers) has taken out the 521 repository-wide write lock, FALSE otherwise. Use POOL for 522 allocations. */ 523static svn_error_t * 524get_lock_helper(svn_fs_t *fs, 525 svn_lock_t **lock_p, 526 const char *path, 527 svn_boolean_t have_write_lock, 528 apr_pool_t *pool) 529{ 530 svn_lock_t *lock; 531 svn_error_t *err; 532 533 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool); 534 535 /* We've deliberately decided that this function doesn't tell the 536 caller *why* the lock is unavailable. */ 537 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) 538 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED))) 539 { 540 svn_error_clear(err); 541 *lock_p = NULL; 542 return SVN_NO_ERROR; 543 } 544 else 545 SVN_ERR(err); 546 547 *lock_p = lock; 548 return SVN_NO_ERROR; 549} 550 551 552/* Baton for locks_walker(). */ 553struct walk_locks_baton { 554 svn_fs_get_locks_callback_t get_locks_func; 555 void *get_locks_baton; 556 svn_fs_t *fs; 557}; 558 559/* Implements walk_digests_callback_t. */ 560static svn_error_t * 561locks_walker(void *baton, 562 const char *fs_path, 563 const char *digest_path, 564 apr_hash_t *children, 565 svn_lock_t *lock, 566 svn_boolean_t have_write_lock, 567 apr_pool_t *pool) 568{ 569 struct walk_locks_baton *wlb = baton; 570 571 if (lock) 572 { 573 /* Don't report an expired lock. */ 574 if (lock->expiration_date == 0 575 || (apr_time_now() <= lock->expiration_date)) 576 { 577 if (wlb->get_locks_func) 578 SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool)); 579 } 580 else 581 { 582 /* Only remove the lock if we have the write lock. 583 Read operations shouldn't change the filesystem. */ 584 if (have_write_lock) 585 SVN_ERR(delete_lock(wlb->fs, lock, pool)); 586 } 587 } 588 589 return SVN_NO_ERROR; 590} 591 592/* Callback type for walk_digest_files(). 593 * 594 * CHILDREN and LOCK come from a read_digest_file(digest_path) call. 595 */ 596typedef svn_error_t *(*walk_digests_callback_t)(void *baton, 597 const char *fs_path, 598 const char *digest_path, 599 apr_hash_t *children, 600 svn_lock_t *lock, 601 svn_boolean_t have_write_lock, 602 apr_pool_t *pool); 603 604/* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for 605 all lock digest files in and under PATH in FS. 606 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 607 has the FS write lock. */ 608static svn_error_t * 609walk_digest_files(const char *fs_path, 610 const char *digest_path, 611 walk_digests_callback_t walk_digests_func, 612 void *walk_digests_baton, 613 svn_boolean_t have_write_lock, 614 apr_pool_t *pool) 615{ 616 apr_hash_index_t *hi; 617 apr_hash_t *children; 618 apr_pool_t *subpool; 619 svn_lock_t *lock; 620 621 /* First, send up any locks in the current digest file. */ 622 SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool)); 623 624 SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, 625 children, lock, 626 have_write_lock, pool)); 627 628 /* Now, recurse on this thing's child entries (if any; bail otherwise). */ 629 if (! apr_hash_count(children)) 630 return SVN_NO_ERROR; 631 subpool = svn_pool_create(pool); 632 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) 633 { 634 const char *digest = svn__apr_hash_index_key(hi); 635 svn_pool_clear(subpool); 636 SVN_ERR(walk_digest_files 637 (fs_path, digest_path_from_digest(fs_path, digest, subpool), 638 walk_digests_func, walk_digests_baton, have_write_lock, subpool)); 639 } 640 svn_pool_destroy(subpool); 641 return SVN_NO_ERROR; 642} 643 644/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for 645 all locks in and under PATH in FS. 646 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 647 has the FS write lock. */ 648static svn_error_t * 649walk_locks(svn_fs_t *fs, 650 const char *digest_path, 651 svn_fs_get_locks_callback_t get_locks_func, 652 void *get_locks_baton, 653 svn_boolean_t have_write_lock, 654 apr_pool_t *pool) 655{ 656 struct walk_locks_baton wlb; 657 658 wlb.get_locks_func = get_locks_func; 659 wlb.get_locks_baton = get_locks_baton; 660 wlb.fs = fs; 661 SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb, 662 have_write_lock, pool)); 663 return SVN_NO_ERROR; 664} 665 666 667/* Utility function: verify that a lock can be used. Interesting 668 errors returned from this function: 669 670 SVN_ERR_FS_NO_USER: No username attached to FS. 671 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner. 672 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK. 673 */ 674static svn_error_t * 675verify_lock(svn_fs_t *fs, 676 svn_lock_t *lock, 677 apr_pool_t *pool) 678{ 679 if ((! fs->access_ctx) || (! fs->access_ctx->username)) 680 return svn_error_createf 681 (SVN_ERR_FS_NO_USER, NULL, 682 _("Cannot verify lock on path '%s'; no username available"), 683 lock->path); 684 685 else if (strcmp(fs->access_ctx->username, lock->owner) != 0) 686 return svn_error_createf 687 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 688 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), 689 fs->access_ctx->username, lock->path, lock->owner); 690 691 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) 692 return svn_error_createf 693 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 694 _("Cannot verify lock on path '%s'; no matching lock-token available"), 695 lock->path); 696 697 return SVN_NO_ERROR; 698} 699 700 701/* This implements the svn_fs_get_locks_callback_t interface, where 702 BATON is just an svn_fs_t object. */ 703static svn_error_t * 704get_locks_callback(void *baton, 705 svn_lock_t *lock, 706 apr_pool_t *pool) 707{ 708 return verify_lock(baton, lock, pool); 709} 710 711 712/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */ 713svn_error_t * 714svn_fs_fs__allow_locked_operation(const char *path, 715 svn_fs_t *fs, 716 svn_boolean_t recurse, 717 svn_boolean_t have_write_lock, 718 apr_pool_t *pool) 719{ 720 path = svn_fs__canonicalize_abspath(path, pool); 721 if (recurse) 722 { 723 /* Discover all locks at or below the path. */ 724 const char *digest_path; 725 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 726 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback, 727 fs, have_write_lock, pool)); 728 } 729 else 730 { 731 /* Discover and verify any lock attached to the path. */ 732 svn_lock_t *lock; 733 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool)); 734 if (lock) 735 SVN_ERR(verify_lock(fs, lock, pool)); 736 } 737 return SVN_NO_ERROR; 738} 739 740/* Baton used for lock_body below. */ 741struct lock_baton { 742 svn_lock_t **lock_p; 743 svn_fs_t *fs; 744 const char *path; 745 const char *token; 746 const char *comment; 747 svn_boolean_t is_dav_comment; 748 apr_time_t expiration_date; 749 svn_revnum_t current_rev; 750 svn_boolean_t steal_lock; 751 apr_pool_t *pool; 752}; 753 754 755/* This implements the svn_fs_fs__with_write_lock() 'body' callback 756 type, and assumes that the write lock is held. 757 BATON is a 'struct lock_baton *'. */ 758static svn_error_t * 759lock_body(void *baton, apr_pool_t *pool) 760{ 761 struct lock_baton *lb = baton; 762 svn_node_kind_t kind; 763 svn_lock_t *existing_lock; 764 svn_lock_t *lock; 765 svn_fs_root_t *root; 766 svn_revnum_t youngest; 767 const char *rev_0_path; 768 769 /* Until we implement directory locks someday, we only allow locks 770 on files or non-existent paths. */ 771 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular 772 library dependencies, which are not portable. */ 773 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool)); 774 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool)); 775 SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool)); 776 if (kind == svn_node_dir) 777 return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path); 778 779 /* While our locking implementation easily supports the locking of 780 nonexistent paths, we deliberately choose not to allow such madness. */ 781 if (kind == svn_node_none) 782 { 783 if (SVN_IS_VALID_REVNUM(lb->current_rev)) 784 return svn_error_createf( 785 SVN_ERR_FS_OUT_OF_DATE, NULL, 786 _("Path '%s' doesn't exist in HEAD revision"), 787 lb->path); 788 else 789 return svn_error_createf( 790 SVN_ERR_FS_NOT_FOUND, NULL, 791 _("Path '%s' doesn't exist in HEAD revision"), 792 lb->path); 793 } 794 795 /* We need to have a username attached to the fs. */ 796 if (!lb->fs->access_ctx || !lb->fs->access_ctx->username) 797 return SVN_FS__ERR_NO_USER(lb->fs); 798 799 /* Is the caller attempting to lock an out-of-date working file? */ 800 if (SVN_IS_VALID_REVNUM(lb->current_rev)) 801 { 802 svn_revnum_t created_rev; 803 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path, 804 pool)); 805 806 /* SVN_INVALID_REVNUM means the path doesn't exist. So 807 apparently somebody is trying to lock something in their 808 working copy, but somebody else has deleted the thing 809 from HEAD. That counts as being 'out of date'. */ 810 if (! SVN_IS_VALID_REVNUM(created_rev)) 811 return svn_error_createf 812 (SVN_ERR_FS_OUT_OF_DATE, NULL, 813 _("Path '%s' doesn't exist in HEAD revision"), lb->path); 814 815 if (lb->current_rev < created_rev) 816 return svn_error_createf 817 (SVN_ERR_FS_OUT_OF_DATE, NULL, 818 _("Lock failed: newer version of '%s' exists"), lb->path); 819 } 820 821 /* If the caller provided a TOKEN, we *really* need to see 822 if a lock already exists with that token, and if so, verify that 823 the lock's path matches PATH. Otherwise we run the risk of 824 breaking the 1-to-1 mapping of lock tokens to locked paths. */ 825 /* ### TODO: actually do this check. This is tough, because the 826 schema doesn't supply a lookup-by-token mechanism. */ 827 828 /* Is the path already locked? 829 830 Note that this next function call will automatically ignore any 831 errors about {the path not existing as a key, the path's token 832 not existing as a key, the lock just having been expired}. And 833 that's totally fine. Any of these three errors are perfectly 834 acceptable to ignore; it means that the path is now free and 835 clear for locking, because the fsfs funcs just cleared out both 836 of the tables for us. */ 837 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool)); 838 if (existing_lock) 839 { 840 if (! lb->steal_lock) 841 { 842 /* Sorry, the path is already locked. */ 843 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock); 844 } 845 else 846 { 847 /* STEAL_LOCK was passed, so fs_username is "stealing" the 848 lock from lock->owner. Destroy the existing lock. */ 849 SVN_ERR(delete_lock(lb->fs, existing_lock, pool)); 850 } 851 } 852 853 /* Create our new lock, and add it to the tables. 854 Ensure that the lock is created in the correct pool. */ 855 lock = svn_lock_create(lb->pool); 856 if (lb->token) 857 lock->token = apr_pstrdup(lb->pool, lb->token); 858 else 859 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs, 860 lb->pool)); 861 lock->path = apr_pstrdup(lb->pool, lb->path); 862 lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username); 863 lock->comment = apr_pstrdup(lb->pool, lb->comment); 864 lock->is_dav_comment = lb->is_dav_comment; 865 lock->creation_date = apr_time_now(); 866 lock->expiration_date = lb->expiration_date; 867 SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool)); 868 SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool)); 869 *lb->lock_p = lock; 870 871 return SVN_NO_ERROR; 872} 873 874/* Baton used for unlock_body below. */ 875struct unlock_baton { 876 svn_fs_t *fs; 877 const char *path; 878 const char *token; 879 svn_boolean_t break_lock; 880}; 881 882/* This implements the svn_fs_fs__with_write_lock() 'body' callback 883 type, and assumes that the write lock is held. 884 BATON is a 'struct unlock_baton *'. */ 885static svn_error_t * 886unlock_body(void *baton, apr_pool_t *pool) 887{ 888 struct unlock_baton *ub = baton; 889 svn_lock_t *lock; 890 891 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */ 892 SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool)); 893 894 /* Unless breaking the lock, we do some checks. */ 895 if (! ub->break_lock) 896 { 897 /* Sanity check: the incoming token should match lock->token. */ 898 if (strcmp(ub->token, lock->token) != 0) 899 return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path); 900 901 /* There better be a username attached to the fs. */ 902 if (! (ub->fs->access_ctx && ub->fs->access_ctx->username)) 903 return SVN_FS__ERR_NO_USER(ub->fs); 904 905 /* And that username better be the same as the lock's owner. */ 906 if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0) 907 return SVN_FS__ERR_LOCK_OWNER_MISMATCH( 908 ub->fs, ub->fs->access_ctx->username, lock->owner); 909 } 910 911 /* Remove lock and lock token files. */ 912 return delete_lock(ub->fs, lock, pool); 913} 914 915 916/*** Public API implementations ***/ 917 918svn_error_t * 919svn_fs_fs__lock(svn_lock_t **lock_p, 920 svn_fs_t *fs, 921 const char *path, 922 const char *token, 923 const char *comment, 924 svn_boolean_t is_dav_comment, 925 apr_time_t expiration_date, 926 svn_revnum_t current_rev, 927 svn_boolean_t steal_lock, 928 apr_pool_t *pool) 929{ 930 struct lock_baton lb; 931 932 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 933 path = svn_fs__canonicalize_abspath(path, pool); 934 935 lb.lock_p = lock_p; 936 lb.fs = fs; 937 lb.path = path; 938 lb.token = token; 939 lb.comment = comment; 940 lb.is_dav_comment = is_dav_comment; 941 lb.expiration_date = expiration_date; 942 lb.current_rev = current_rev; 943 lb.steal_lock = steal_lock; 944 lb.pool = pool; 945 946 return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool); 947} 948 949 950svn_error_t * 951svn_fs_fs__generate_lock_token(const char **token, 952 svn_fs_t *fs, 953 apr_pool_t *pool) 954{ 955 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 956 957 /* Notice that 'fs' is currently unused. But perhaps someday, we'll 958 want to use the fs UUID + some incremented number? For now, we 959 generate a URI that matches the DAV RFC. We could change this to 960 some other URI scheme someday, if we wish. */ 961 *token = apr_pstrcat(pool, "opaquelocktoken:", 962 svn_uuid_generate(pool), (char *)NULL); 963 return SVN_NO_ERROR; 964} 965 966 967svn_error_t * 968svn_fs_fs__unlock(svn_fs_t *fs, 969 const char *path, 970 const char *token, 971 svn_boolean_t break_lock, 972 apr_pool_t *pool) 973{ 974 struct unlock_baton ub; 975 976 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 977 path = svn_fs__canonicalize_abspath(path, pool); 978 979 ub.fs = fs; 980 ub.path = path; 981 ub.token = token; 982 ub.break_lock = break_lock; 983 984 return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool); 985} 986 987 988svn_error_t * 989svn_fs_fs__get_lock(svn_lock_t **lock_p, 990 svn_fs_t *fs, 991 const char *path, 992 apr_pool_t *pool) 993{ 994 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 995 path = svn_fs__canonicalize_abspath(path, pool); 996 return get_lock_helper(fs, lock_p, path, FALSE, pool); 997} 998 999 1000/* Baton for get_locks_filter_func(). */ 1001typedef struct get_locks_filter_baton_t 1002{ 1003 const char *path; 1004 svn_depth_t requested_depth; 1005 svn_fs_get_locks_callback_t get_locks_func; 1006 void *get_locks_baton; 1007 1008} get_locks_filter_baton_t; 1009 1010 1011/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks() 1012 which filters out locks on paths that aren't within 1013 BATON->requested_depth of BATON->path before called 1014 BATON->get_locks_func() with BATON->get_locks_baton. 1015 1016 NOTE: See issue #3660 for details about how the FSFS lock 1017 management code is inconsistent. Until that inconsistency is 1018 resolved, we take this filtering approach rather than honoring 1019 depth requests closer to the crawling code. In other words, once 1020 we decide how to resolve issue #3660, there might be a more 1021 performant way to honor the depth passed to svn_fs_fs__get_locks(). */ 1022static svn_error_t * 1023get_locks_filter_func(void *baton, 1024 svn_lock_t *lock, 1025 apr_pool_t *pool) 1026{ 1027 get_locks_filter_baton_t *b = baton; 1028 1029 /* Filter out unwanted paths. Since Subversion only allows 1030 locks on files, we can treat depth=immediates the same as 1031 depth=files for filtering purposes. Meaning, we'll keep 1032 this lock if: 1033 1034 a) its path is the very path we queried, or 1035 b) we've asked for a fully recursive answer, or 1036 c) we've asked for depth=files or depth=immediates, and this 1037 lock is on an immediate child of our query path. 1038 */ 1039 if ((strcmp(b->path, lock->path) == 0) 1040 || (b->requested_depth == svn_depth_infinity)) 1041 { 1042 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1043 } 1044 else if ((b->requested_depth == svn_depth_files) || 1045 (b->requested_depth == svn_depth_immediates)) 1046 { 1047 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path); 1048 if (rel_uri && (svn_path_component_count(rel_uri) == 1)) 1049 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1050 } 1051 1052 return SVN_NO_ERROR; 1053} 1054 1055svn_error_t * 1056svn_fs_fs__get_locks(svn_fs_t *fs, 1057 const char *path, 1058 svn_depth_t depth, 1059 svn_fs_get_locks_callback_t get_locks_func, 1060 void *get_locks_baton, 1061 apr_pool_t *pool) 1062{ 1063 const char *digest_path; 1064 get_locks_filter_baton_t glfb; 1065 1066 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1067 path = svn_fs__canonicalize_abspath(path, pool); 1068 1069 glfb.path = path; 1070 glfb.requested_depth = depth; 1071 glfb.get_locks_func = get_locks_func; 1072 glfb.get_locks_baton = get_locks_baton; 1073 1074 /* Get the top digest path in our tree of interest, and then walk it. */ 1075 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 1076 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb, 1077 FALSE, pool)); 1078 return SVN_NO_ERROR; 1079} 1080