lock.c revision 362181
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_hash.h" 25#include "svn_pools.h" 26#include "svn_error.h" 27#include "svn_fs.h" 28#include "svn_private_config.h" 29 30#include <apr_uuid.h> 31 32#include "lock.h" 33#include "tree.h" 34#include "err.h" 35#include "bdb/locks-table.h" 36#include "bdb/lock-tokens-table.h" 37#include "util/fs_skels.h" 38#include "../libsvn_fs/fs-loader.h" 39#include "private/svn_fs_util.h" 40#include "private/svn_subr_private.h" 41#include "private/svn_dep_compat.h" 42#include "revs-txns.h" 43 44 45/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as 46 part of TRAIL. */ 47static svn_error_t * 48add_lock_and_token(svn_lock_t *lock, 49 const char *lock_token, 50 const char *path, 51 trail_t *trail) 52{ 53 SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock, 54 trail, trail->pool)); 55 return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token, 56 trail, trail->pool); 57} 58 59 60/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH, 61 whose KIND is supplied), as part of TRAIL. */ 62static svn_error_t * 63delete_lock_and_token(const char *lock_token, 64 const char *path, 65 trail_t *trail) 66{ 67 SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token, 68 trail, trail->pool)); 69 return svn_fs_bdb__lock_token_delete(trail->fs, path, 70 trail, trail->pool); 71} 72 73 74/* The effective arguments for txn_body_lock() below. */ 75struct lock_args 76{ 77 svn_lock_t **lock_p; 78 const char *path; 79 const char *token; 80 const char *comment; 81 svn_boolean_t is_dav_comment; 82 svn_boolean_t steal_lock; 83 apr_time_t expiration_date; 84 svn_revnum_t current_rev; 85 apr_pool_t *result_pool; 86}; 87 88 89/* The body of svn_fs_base__lock(), which see. 90 91 BATON is a 'struct lock_args *' holding the effective arguments. 92 BATON->path is the canonical abspath to lock. Set *BATON->lock_p 93 to the resulting lock. For the other arguments, see 94 svn_fs_lock_many(). 95 96 This implements the svn_fs_base__retry_txn() 'body' callback type. 97 */ 98static svn_error_t * 99txn_body_lock(void *baton, trail_t *trail) 100{ 101 struct lock_args *args = baton; 102 svn_node_kind_t kind = svn_node_file; 103 svn_lock_t *existing_lock; 104 svn_lock_t *lock; 105 106 *args->lock_p = NULL; 107 108 SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool)); 109 110 /* Until we implement directory locks someday, we only allow locks 111 on files. */ 112 if (kind == svn_node_dir) 113 return SVN_FS__ERR_NOT_FILE(trail->fs, args->path); 114 115 /* While our locking implementation easily supports the locking of 116 nonexistent paths, we deliberately choose not to allow such madness. */ 117 if (kind == svn_node_none) 118 { 119 if (SVN_IS_VALID_REVNUM(args->current_rev)) 120 return svn_error_createf( 121 SVN_ERR_FS_OUT_OF_DATE, NULL, 122 _("Path '%s' doesn't exist in HEAD revision"), 123 args->path); 124 else 125 return svn_error_createf( 126 SVN_ERR_FS_NOT_FOUND, NULL, 127 _("Path '%s' doesn't exist in HEAD revision"), 128 args->path); 129 } 130 131 /* There better be a username attached to the fs. */ 132 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username) 133 return SVN_FS__ERR_NO_USER(trail->fs); 134 135 /* Is the caller attempting to lock an out-of-date working file? */ 136 if (SVN_IS_VALID_REVNUM(args->current_rev)) 137 { 138 svn_revnum_t created_rev; 139 SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path, 140 trail, trail->pool)); 141 142 /* SVN_INVALID_REVNUM means the path doesn't exist. So 143 apparently somebody is trying to lock something in their 144 working copy, but somebody else has deleted the thing 145 from HEAD. That counts as being 'out of date'. */ 146 if (! SVN_IS_VALID_REVNUM(created_rev)) 147 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, 148 "Path '%s' doesn't exist in HEAD revision", 149 args->path); 150 151 if (args->current_rev < created_rev) 152 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, 153 "Lock failed: newer version of '%s' exists", 154 args->path); 155 } 156 157 /* If the caller provided a TOKEN, we *really* need to see 158 if a lock already exists with that token, and if so, verify that 159 the lock's path matches PATH. Otherwise we run the risk of 160 breaking the 1-to-1 mapping of lock tokens to locked paths. */ 161 if (args->token) 162 { 163 svn_lock_t *lock_from_token; 164 svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs, 165 args->token, trail, 166 trail->pool); 167 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) 168 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) 169 { 170 svn_error_clear(err); 171 } 172 else 173 { 174 SVN_ERR(err); 175 if (strcmp(lock_from_token->path, args->path) != 0) 176 return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 177 "Lock failed: token refers to existing " 178 "lock with non-matching path."); 179 } 180 } 181 182 /* Is the path already locked? 183 184 Note that this next function call will automatically ignore any 185 errors about {the path not existing as a key, the path's token 186 not existing as a key, the lock just having been expired}. And 187 that's totally fine. Any of these three errors are perfectly 188 acceptable to ignore; it means that the path is now free and 189 clear for locking, because the bdb funcs just cleared out both 190 of the tables for us. */ 191 SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path, 192 trail, trail->pool)); 193 if (existing_lock) 194 { 195 if (! args->steal_lock) 196 { 197 /* Sorry, the path is already locked. */ 198 return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs, 199 existing_lock); 200 } 201 else 202 { 203 /* ARGS->steal_lock is set, so fs_username is "stealing" the 204 lock from lock->owner. Destroy the existing lock. */ 205 SVN_ERR(delete_lock_and_token(existing_lock->token, 206 existing_lock->path, trail)); 207 } 208 } 209 210 /* Create a new lock, and add it to the tables. */ 211 lock = svn_lock_create(args->result_pool); 212 if (args->token) 213 lock->token = apr_pstrdup(args->result_pool, args->token); 214 else 215 SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs, 216 args->result_pool)); 217 lock->path = args->path; /* Already in result_pool. */ 218 lock->owner = apr_pstrdup(args->result_pool, trail->fs->access_ctx->username); 219 lock->comment = apr_pstrdup(args->result_pool, args->comment); 220 lock->is_dav_comment = args->is_dav_comment; 221 lock->creation_date = apr_time_now(); 222 lock->expiration_date = args->expiration_date; 223 SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail)); 224 *(args->lock_p) = lock; 225 226 return SVN_NO_ERROR; 227} 228 229 230 231svn_error_t * 232svn_fs_base__lock(svn_fs_t *fs, 233 apr_hash_t *targets, 234 const char *comment, 235 svn_boolean_t is_dav_comment, 236 apr_time_t expiration_date, 237 svn_boolean_t steal_lock, 238 svn_fs_lock_callback_t lock_callback, 239 void *lock_baton, 240 apr_pool_t *result_pool, 241 apr_pool_t *scratch_pool) 242{ 243 apr_hash_index_t *hi; 244 svn_error_t *cb_err = SVN_NO_ERROR; 245 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; 246 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 247 248 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 249 SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, scratch_pool)); 250 251 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 252 { 253 struct lock_args args; 254 const char *path = apr_hash_this_key(hi); 255 const svn_fs_lock_target_t *target = apr_hash_this_val(hi); 256 svn_lock_t *lock; 257 svn_error_t *err = NULL; 258 259 svn_pool_clear(iterpool); 260 args.lock_p = &lock; 261 args.path = svn_fs__canonicalize_abspath(path, result_pool); 262 args.token = target->token; 263 args.comment = comment; 264 args.is_dav_comment = is_dav_comment; 265 args.steal_lock = steal_lock; 266 args.expiration_date = expiration_date; 267 args.current_rev = target->current_rev; 268 args.result_pool = result_pool; 269 270 if (SVN_IS_VALID_REVNUM(target->current_rev)) 271 { 272 if (target->current_rev > youngest_rev) 273 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 274 _("No such revision %ld"), 275 target->current_rev); 276 } 277 278 if (!err) 279 err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, TRUE, 280 iterpool); 281 if (!cb_err && lock_callback) 282 cb_err = lock_callback(lock_baton, args.path, lock, err, iterpool); 283 svn_error_clear(err); 284 } 285 svn_pool_destroy(iterpool); 286 287 return svn_error_trace(cb_err); 288} 289 290 291svn_error_t * 292svn_fs_base__generate_lock_token(const char **token, 293 svn_fs_t *fs, 294 apr_pool_t *pool) 295{ 296 /* Notice that 'fs' is currently unused. But perhaps someday, we'll 297 want to use the fs UUID + some incremented number? For now, we 298 generate a URI that matches the DAV RFC. We could change this to 299 some other URI scheme someday, if we wish. */ 300 *token = apr_pstrcat(pool, "opaquelocktoken:", 301 svn_uuid_generate(pool), SVN_VA_NULL); 302 return SVN_NO_ERROR; 303} 304 305 306/* The effective arguments for txn_body_unlock() below. */ 307struct unlock_args 308{ 309 const char *path; 310 const char *token; 311 svn_boolean_t break_lock; 312}; 313 314 315/* The body of svn_fs_base__unlock(), which see. 316 317 BATON is a 'struct unlock_args *' holding the effective arguments. 318 BATON->path is the canonical path and BATON->token is the token. 319 For the other arguments, see svn_fs_unlock_many(). 320 321 This implements the svn_fs_base__retry_txn() 'body' callback type. 322 */ 323static svn_error_t * 324txn_body_unlock(void *baton, trail_t *trail) 325{ 326 struct unlock_args *args = baton; 327 const char *lock_token; 328 svn_lock_t *lock; 329 330 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */ 331 SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path, 332 trail, trail->pool)); 333 334 /* If not breaking the lock, we need to do some more checking. */ 335 if (!args->break_lock) 336 { 337 /* Sanity check: The lock token must exist, and must match. */ 338 if (args->token == NULL) 339 return svn_fs_base__err_no_lock_token(trail->fs, args->path); 340 else if (strcmp(lock_token, args->token) != 0) 341 return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path); 342 343 SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token, 344 trail, trail->pool)); 345 346 /* There better be a username attached to the fs. */ 347 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username) 348 return SVN_FS__ERR_NO_USER(trail->fs); 349 350 /* And that username better be the same as the lock's owner. */ 351 if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0) 352 return SVN_FS__ERR_LOCK_OWNER_MISMATCH( 353 trail->fs, 354 trail->fs->access_ctx->username, 355 lock->owner); 356 } 357 358 /* Remove a row from each of the locking tables. */ 359 return delete_lock_and_token(lock_token, args->path, trail); 360} 361 362 363svn_error_t * 364svn_fs_base__unlock(svn_fs_t *fs, 365 apr_hash_t *targets, 366 svn_boolean_t break_lock, 367 svn_fs_lock_callback_t lock_callback, 368 void *lock_baton, 369 apr_pool_t *result_pool, 370 apr_pool_t *scratch_pool) 371{ 372 apr_hash_index_t *hi; 373 svn_error_t *cb_err = SVN_NO_ERROR; 374 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 375 376 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 377 378 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 379 { 380 struct unlock_args args; 381 const char *path = apr_hash_this_key(hi); 382 const char *token = apr_hash_this_val(hi); 383 svn_error_t *err; 384 385 svn_pool_clear(iterpool); 386 args.path = svn_fs__canonicalize_abspath(path, result_pool); 387 args.token = token; 388 args.break_lock = break_lock; 389 390 err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, 391 iterpool); 392 if (!cb_err && lock_callback) 393 cb_err = lock_callback(lock_baton, path, NULL, err, iterpool); 394 svn_error_clear(err); 395 } 396 svn_pool_destroy(iterpool); 397 398 return svn_error_trace(cb_err); 399} 400 401 402svn_error_t * 403svn_fs_base__get_lock_helper(svn_lock_t **lock_p, 404 const char *path, 405 trail_t *trail, 406 apr_pool_t *pool) 407{ 408 const char *lock_token; 409 svn_error_t *err; 410 411 err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path, 412 trail, pool); 413 414 /* We've deliberately decided that this function doesn't tell the 415 caller *why* the lock is unavailable. */ 416 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) 417 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) 418 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) 419 { 420 svn_error_clear(err); 421 *lock_p = NULL; 422 return SVN_NO_ERROR; 423 } 424 else 425 SVN_ERR(err); 426 427 /* Same situation here. */ 428 err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool); 429 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) 430 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) 431 { 432 svn_error_clear(err); 433 *lock_p = NULL; 434 return SVN_NO_ERROR; 435 } 436 else 437 SVN_ERR(err); 438 439 return svn_error_trace(err); 440} 441 442 443struct lock_token_get_args 444{ 445 svn_lock_t **lock_p; 446 const char *path; 447}; 448 449 450static svn_error_t * 451txn_body_get_lock(void *baton, trail_t *trail) 452{ 453 struct lock_token_get_args *args = baton; 454 return svn_fs_base__get_lock_helper(args->lock_p, args->path, 455 trail, trail->pool); 456} 457 458 459svn_error_t * 460svn_fs_base__get_lock(svn_lock_t **lock, 461 svn_fs_t *fs, 462 const char *path, 463 apr_pool_t *pool) 464{ 465 struct lock_token_get_args args; 466 467 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 468 469 args.path = svn_fs__canonicalize_abspath(path, pool); 470 args.lock_p = lock; 471 return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool); 472} 473 474/* Implements `svn_fs_get_locks_callback_t', spooling lock information 475 to a stream as the filesystem provides it. BATON is an 'svn_stream_t *' 476 object pointing to the stream. We'll write the spool stream with a 477 format like so: 478 479 SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ... 480 481 where each skel is a lock skel (the same format we use to store 482 locks in the `locks' table). */ 483static svn_error_t * 484spool_locks_info(void *baton, 485 svn_lock_t *lock, 486 apr_pool_t *pool) 487{ 488 svn_skel_t *lock_skel; 489 svn_stream_t *stream = baton; 490 const char *skel_len; 491 svn_stringbuf_t *skel_buf; 492 apr_size_t len; 493 494 SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool)); 495 skel_buf = svn_skel__unparse(lock_skel, pool); 496 skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len); 497 len = strlen(skel_len); 498 SVN_ERR(svn_stream_write(stream, skel_len, &len)); 499 len = skel_buf->len; 500 SVN_ERR(svn_stream_write(stream, skel_buf->data, &len)); 501 len = 1; 502 return svn_stream_write(stream, "\n", &len); 503} 504 505 506struct locks_get_args 507{ 508 const char *path; 509 svn_depth_t depth; 510 svn_stream_t *stream; 511}; 512 513 514static svn_error_t * 515txn_body_get_locks(void *baton, trail_t *trail) 516{ 517 struct locks_get_args *args = baton; 518 return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth, 519 spool_locks_info, args->stream, 520 trail, trail->pool); 521} 522 523 524svn_error_t * 525svn_fs_base__get_locks(svn_fs_t *fs, 526 const char *path, 527 svn_depth_t depth, 528 svn_fs_get_locks_callback_t get_locks_func, 529 void *get_locks_baton, 530 apr_pool_t *pool) 531{ 532 struct locks_get_args args; 533 svn_stream_t *stream; 534 svn_stringbuf_t *buf; 535 svn_boolean_t eof; 536 apr_pool_t *iterpool = svn_pool_create(pool); 537 538 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 539 540 args.path = svn_fs__canonicalize_abspath(path, pool); 541 args.depth = depth; 542 /* Enough for 100+ locks if the comments are small. */ 543 args.stream = svn_stream__from_spillbuf(svn_spillbuf__create(4 * 1024 /* blocksize */, 544 64 * 1024 /* maxsize */, 545 pool), 546 pool); 547 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool)); 548 549 /* Read the stream calling GET_LOCKS_FUNC(). */ 550 stream = args.stream; 551 552 while (1) 553 { 554 apr_size_t len, skel_len; 555 char c, *skel_buf; 556 svn_skel_t *lock_skel; 557 svn_lock_t *lock; 558 apr_uint64_t ui64; 559 svn_error_t *err; 560 561 svn_pool_clear(iterpool); 562 563 /* Read a skel length line and parse it for the skel's length. */ 564 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool)); 565 if (eof) 566 break; 567 err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10); 568 if (err) 569 return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL); 570 skel_len = (apr_size_t)ui64; 571 572 /* Now read that much into a buffer. */ 573 skel_buf = apr_palloc(pool, skel_len + 1); 574 SVN_ERR(svn_stream_read_full(stream, skel_buf, &skel_len)); 575 skel_buf[skel_len] = '\0'; 576 577 /* Read the extra newline that follows the skel. */ 578 len = 1; 579 SVN_ERR(svn_stream_read_full(stream, &c, &len)); 580 if (c != '\n') 581 return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); 582 583 /* Parse the skel into a lock, and notify the caller. */ 584 lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool); 585 SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool)); 586 SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool)); 587 } 588 589 SVN_ERR(svn_stream_close(stream)); 590 svn_pool_destroy(iterpool); 591 return SVN_NO_ERROR; 592} 593 594 595 596/* Utility function: verify that a lock can be used. 597 598 If no username is attached to the FS, return SVN_ERR_FS_NO_USER. 599 600 If the FS username doesn't match LOCK's owner, return 601 SVN_ERR_FS_LOCK_OWNER_MISMATCH. 602 603 If FS hasn't been supplied with a matching lock-token for LOCK, 604 return SVN_ERR_FS_BAD_LOCK_TOKEN. 605 606 Otherwise return SVN_NO_ERROR. 607 */ 608static svn_error_t * 609verify_lock(svn_fs_t *fs, 610 svn_lock_t *lock, 611 apr_pool_t *pool) 612{ 613 if ((! fs->access_ctx) || (! fs->access_ctx->username)) 614 return svn_error_createf 615 (SVN_ERR_FS_NO_USER, NULL, 616 _("Cannot verify lock on path '%s'; no username available"), 617 lock->path); 618 619 else if (strcmp(fs->access_ctx->username, lock->owner) != 0) 620 return svn_error_createf 621 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 622 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), 623 fs->access_ctx->username, lock->path, lock->owner); 624 625 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) 626 return svn_error_createf 627 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 628 _("Cannot verify lock on path '%s'; no matching lock-token available"), 629 lock->path); 630 631 return SVN_NO_ERROR; 632} 633 634 635/* This implements the svn_fs_get_locks_callback_t interface, where 636 BATON is just an svn_fs_t object. */ 637static svn_error_t * 638get_locks_callback(void *baton, 639 svn_lock_t *lock, 640 apr_pool_t *pool) 641{ 642 return verify_lock(baton, lock, pool); 643} 644 645 646/* The main routine for lock enforcement, used throughout libsvn_fs_base. */ 647svn_error_t * 648svn_fs_base__allow_locked_operation(const char *path, 649 svn_boolean_t recurse, 650 trail_t *trail, 651 apr_pool_t *pool) 652{ 653 if (recurse) 654 { 655 /* Discover all locks at or below the path. */ 656 SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity, 657 get_locks_callback, 658 trail->fs, trail, pool)); 659 } 660 else 661 { 662 svn_lock_t *lock; 663 664 /* Discover any lock attached to the path. */ 665 SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool)); 666 if (lock) 667 SVN_ERR(verify_lock(trail->fs, lock, pool)); 668 } 669 return SVN_NO_ERROR; 670} 671