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