1/* 2 * locking_commands.c: Implementation of lock and unlock. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_client.h" 31#include "svn_hash.h" 32#include "client.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_xml.h" 36#include "svn_pools.h" 37 38#include "svn_private_config.h" 39#include "private/svn_client_private.h" 40#include "private/svn_wc_private.h" 41 42 43/*** Code. ***/ 44 45/* For use with store_locks_callback, below. */ 46struct lock_baton 47{ 48 const char *base_dir_abspath; 49 apr_hash_t *urls_to_paths; /* url -> abspath */ 50 const char *base_url; 51 svn_client_ctx_t *ctx; 52 apr_pool_t *pool; 53}; 54 55 56/* This callback is called by the ra_layer for each path locked. 57 * BATON is a 'struct lock_baton *', PATH is the path being locked, 58 * and LOCK is the lock itself. 59 * 60 * If BATON->urls_to_paths is not null, then this function either 61 * stores the LOCK on REL_URL or removes any lock tokens from REL_URL 62 * (depending on whether DO_LOCK is true or false respectively), but 63 * only if RA_ERR is null, or (in the unlock case) is something other 64 * than SVN_ERR_FS_LOCK_OWNER_MISMATCH. 65 * 66 * Implements svn_ra_lock_callback_t. 67 */ 68static svn_error_t * 69store_locks_callback(void *baton, 70 const char *rel_url, 71 svn_boolean_t do_lock, 72 const svn_lock_t *lock, 73 svn_error_t *ra_err, apr_pool_t *pool) 74{ 75 struct lock_baton *lb = baton; 76 svn_wc_notify_t *notify; 77 const char *local_abspath = lb->urls_to_paths 78 ? svn_hash_gets(lb->urls_to_paths, rel_url) 79 : NULL; 80 81 /* Create the notify struct first, so we can tweak it below. */ 82 notify = svn_wc_create_notify(local_abspath ? local_abspath : rel_url, 83 do_lock 84 ? (ra_err 85 ? svn_wc_notify_failed_lock 86 : svn_wc_notify_locked) 87 : (ra_err 88 ? svn_wc_notify_failed_unlock 89 : svn_wc_notify_unlocked), 90 pool); 91 notify->lock = lock; 92 notify->err = ra_err; 93 94 if (local_abspath) 95 { 96 /* Notify a valid working copy path */ 97 notify->path_prefix = lb->base_dir_abspath; 98 99 if (do_lock) 100 { 101 if (!ra_err && lock) 102 { 103 SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock, 104 lb->pool)); 105 notify->lock_state = svn_wc_notify_lock_state_locked; 106 } 107 else 108 notify->lock_state = svn_wc_notify_lock_state_unchanged; 109 } 110 else /* unlocking */ 111 { 112 /* Remove our wc lock token either a) if we got no error, or b) if 113 we got any error except for owner mismatch or hook failure (the 114 hook would be pre-unlock rather than post-unlock). Note that the 115 only errors that are handed to this callback will be 116 locking-related errors. */ 117 118 if (!ra_err || 119 (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH 120 && ra_err->apr_err != SVN_ERR_REPOS_HOOK_FAILURE))) 121 { 122 SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath, 123 lb->pool)); 124 notify->lock_state = svn_wc_notify_lock_state_unlocked; 125 } 126 else 127 notify->lock_state = svn_wc_notify_lock_state_unchanged; 128 } 129 } 130 else 131 notify->url = svn_path_url_add_component2(lb->base_url, rel_url, pool); 132 133 if (lb->ctx->notify_func2) 134 lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool); 135 136 return SVN_NO_ERROR; 137} 138 139 140/* This is a wrapper around svn_uri_condense_targets() and 141 * svn_dirent_condense_targets() (the choice of which is made based on 142 * the value of TARGETS_ARE_URIS) which takes care of the 143 * single-target special case. 144 * 145 * Callers are expected to check for an empty *COMMON_PARENT (which 146 * means, "there was nothing common") for themselves. 147 */ 148static svn_error_t * 149condense_targets(const char **common_parent, 150 apr_array_header_t **target_relpaths, 151 const apr_array_header_t *targets, 152 svn_boolean_t targets_are_uris, 153 svn_boolean_t remove_redundancies, 154 apr_pool_t *result_pool, 155 apr_pool_t *scratch_pool) 156{ 157 if (targets_are_uris) 158 { 159 SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths, 160 targets, remove_redundancies, 161 result_pool, scratch_pool)); 162 } 163 else 164 { 165 SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths, 166 targets, remove_redundancies, 167 result_pool, scratch_pool)); 168 } 169 170 /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only 171 had 1 member, so we special case that. */ 172 if (apr_is_empty_array(*target_relpaths)) 173 { 174 const char *base_name; 175 176 if (targets_are_uris) 177 { 178 svn_uri_split(common_parent, &base_name, 179 *common_parent, result_pool); 180 } 181 else 182 { 183 svn_dirent_split(common_parent, &base_name, 184 *common_parent, result_pool); 185 } 186 APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name; 187 } 188 189 return SVN_NO_ERROR; 190} 191 192/* Lock info. Used in organize_lock_targets. 193 ### Maybe return this instead of the ugly hashes? */ 194struct wc_lock_item_t 195{ 196 svn_revnum_t revision; 197 const char *lock_token; 198 const char *url; 199}; 200 201/* 202 * Sets LOCK_PATHS to an array of working copy paths that this function 203 * has obtained lock on. The caller is responsible to release the locks 204 * EVEN WHEN THIS FUNCTION RETURNS AN ERROR. 205 * 206 * Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS. 207 * If TARGETS are local paths, then the entry for each path is examined 208 * and *COMMON_PARENT is set to the common parent URL for all the 209 * targets (as opposed to the common local path). 210 * 211 * If there is no common parent, either because the targets are a 212 * mixture of URLs and local paths, or because they simply do not 213 * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE. 214 * 215 * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them. 216 * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise. 217 * 218 * Each key stored in *REL_TARGETS_P is a path relative to 219 * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is 220 * true, the value is a pointer to the corresponding base_revision 221 * (allocated in POOL) for the path, else the value is the lock token 222 * (or "" if no token found in the wc). 223 * 224 * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL. 225 * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to 226 * COMMON_PARENT) mapped to the absolute path for TARGET. 227 * 228 * If *COMMON_PARENT is a URL, then the values are a pointer to 229 * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "". 230 * 231 * TARGETS may not be empty. 232 */ 233static svn_error_t * 234organize_lock_targets(apr_array_header_t **lock_paths, 235 const char **common_parent_url, 236 const char **base_dir_abspath, 237 apr_hash_t **rel_targets_p, 238 apr_hash_t **rel_fs_paths_p, 239 const apr_array_header_t *targets, 240 svn_boolean_t do_lock, 241 svn_boolean_t force, 242 svn_wc_context_t *wc_ctx, 243 apr_pool_t *result_pool, 244 apr_pool_t *scratch_pool) 245{ 246 const char *common_url = NULL; 247 apr_hash_t *rel_targets_ret = apr_hash_make(result_pool); 248 apr_hash_t *rel_fs_paths = NULL; 249 apr_hash_t *wc_info = apr_hash_make(scratch_pool); 250 svn_boolean_t url_mode; 251 252 *lock_paths = NULL; 253 254 SVN_ERR_ASSERT(targets->nelts); 255 SVN_ERR(svn_client__assert_homogeneous_target_type(targets)); 256 257 url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); 258 259 if (url_mode) 260 { 261 apr_array_header_t *rel_targets; 262 int i; 263 svn_revnum_t *invalid_revnum = 264 apr_palloc(result_pool, sizeof(*invalid_revnum)); 265 266 *invalid_revnum = SVN_INVALID_REVNUM; 267 268 /* Get the common parent URL and a bunch of relpaths, one per target. */ 269 SVN_ERR(condense_targets(&common_url, &rel_targets, targets, 270 TRUE, TRUE, result_pool, scratch_pool)); 271 if (! (common_url && *common_url)) 272 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 273 _("No common parent found, unable to operate " 274 "on disjoint arguments")); 275 276 /* Create mapping of the target relpaths to either 277 SVN_INVALID_REVNUM (if our caller is locking) or to an empty 278 lock token string (if the caller is unlocking). */ 279 for (i = 0; i < rel_targets->nelts; i++) 280 { 281 svn_hash_sets(rel_targets_ret, 282 APR_ARRAY_IDX(rel_targets, i, const char *), 283 do_lock 284 ? (const void *)invalid_revnum 285 : (const void *)""); 286 } 287 } 288 else 289 { 290 apr_array_header_t *rel_urls, *target_urls; 291 apr_hash_t *wcroot_target = apr_hash_make(scratch_pool); 292 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 293 apr_hash_index_t *hi; 294 int i; 295 296 *lock_paths = apr_array_make(result_pool, 1, sizeof(const char *)); 297 298 for (i = 0; i < targets->nelts; i++) 299 { 300 const char *target_abspath; 301 const char *wcroot_abspath; 302 apr_array_header_t *wc_targets; 303 304 svn_pool_clear(iterpool); 305 306 SVN_ERR(svn_dirent_get_absolute(&target_abspath, 307 APR_ARRAY_IDX(targets, i, const char*), 308 result_pool)); 309 310 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath, 311 iterpool, iterpool)); 312 313 wc_targets = svn_hash_gets(wcroot_target, wcroot_abspath); 314 315 if (!wc_targets) 316 { 317 wc_targets = apr_array_make(scratch_pool, 1, sizeof(const char*)); 318 svn_hash_sets(wcroot_target, apr_pstrdup(scratch_pool, wcroot_abspath), 319 wc_targets); 320 } 321 322 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath; 323 } 324 325 for (hi = apr_hash_first(scratch_pool, wcroot_target); 326 hi; 327 hi = apr_hash_next(hi)) 328 { 329 const char *lock_abspath; 330 apr_array_header_t *paths = apr_hash_this_val(hi); 331 332 /* Use parent dir of a single file target */ 333 if (paths->nelts == 1) 334 lock_abspath = svn_dirent_dirname( 335 APR_ARRAY_IDX(paths, 0, const char *), 336 result_pool); 337 else 338 SVN_ERR(svn_dirent_condense_targets(&lock_abspath, NULL, paths, 339 FALSE, result_pool, 340 scratch_pool)); 341 342 SVN_ERR(svn_wc__acquire_write_lock(&lock_abspath, 343 wc_ctx, lock_abspath, FALSE, 344 result_pool, scratch_pool)); 345 346 APR_ARRAY_PUSH(*lock_paths, const char *) = lock_abspath; 347 } 348 349 /* Get the URL for each target (which also serves to verify that 350 the dirent targets are sane). */ 351 target_urls = apr_array_make(scratch_pool, targets->nelts, 352 sizeof(const char *)); 353 for (hi = apr_hash_first(scratch_pool, wcroot_target); 354 hi; 355 hi = apr_hash_next(hi)) 356 { 357 apr_array_header_t *wc_targets = apr_hash_this_val(hi); 358 359 for (i = 0; i < wc_targets->nelts; i++) 360 { 361 const char *repos_relpath; 362 const char *repos_root_url; 363 struct wc_lock_item_t *wli; 364 const char *local_abspath; 365 svn_node_kind_t kind; 366 367 svn_pool_clear(iterpool); 368 369 local_abspath = APR_ARRAY_IDX(wc_targets, i, const char *); 370 wli = apr_pcalloc(scratch_pool, sizeof(*wli)); 371 372 SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, 373 &repos_relpath, 374 &repos_root_url, NULL, 375 &wli->lock_token, 376 wc_ctx, local_abspath, 377 FALSE /* ignore_enoent */, 378 result_pool, iterpool)); 379 380 if (kind != svn_node_file) 381 return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL, 382 _("The node '%s' is not a file"), 383 svn_dirent_local_style(local_abspath, 384 iterpool)); 385 386 wli->url = svn_path_url_add_component2(repos_root_url, 387 repos_relpath, 388 scratch_pool); 389 svn_hash_sets(wc_info, local_abspath, wli); 390 391 APR_ARRAY_PUSH(target_urls, const char *) = wli->url; 392 } 393 } 394 395 /* Now that we have a bunch of URLs for our dirent targets, 396 condense those into a single common parent URL and a bunch of 397 paths relative to that. */ 398 SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls, 399 TRUE, FALSE, result_pool, scratch_pool)); 400 if (! (common_url && *common_url)) 401 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 402 _("Unable to lock/unlock across multiple " 403 "repositories")); 404 405 /* Now we need to create our mapping. */ 406 rel_fs_paths = apr_hash_make(result_pool); 407 408 for (hi = apr_hash_first(scratch_pool, wc_info); 409 hi; 410 hi = apr_hash_next(hi)) 411 { 412 const char *local_abspath = apr_hash_this_key(hi); 413 struct wc_lock_item_t *wli = apr_hash_this_val(hi); 414 const char *rel_url; 415 416 svn_pool_clear(iterpool); 417 418 rel_url = svn_uri_skip_ancestor(common_url, wli->url, result_pool); 419 420 svn_hash_sets(rel_fs_paths, rel_url, 421 apr_pstrdup(result_pool, local_abspath)); 422 423 if (do_lock) /* Lock. */ 424 { 425 svn_revnum_t *revnum; 426 revnum = apr_palloc(result_pool, sizeof(* revnum)); 427 428 *revnum = wli->revision; 429 430 svn_hash_sets(rel_targets_ret, rel_url, revnum); 431 } 432 else /* Unlock. */ 433 { 434 const char *lock_token; 435 436 /* If not forcing the unlock, get the lock token. */ 437 if (! force) 438 { 439 if (! wli->lock_token) 440 return svn_error_createf( 441 SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 442 _("'%s' is not locked in this working copy"), 443 svn_dirent_local_style(local_abspath, 444 scratch_pool)); 445 446 lock_token = wli->lock_token 447 ? apr_pstrdup(result_pool, wli->lock_token) 448 : NULL; 449 } 450 else 451 lock_token = NULL; 452 453 /* If breaking a lock, we shouldn't pass any lock token. */ 454 svn_hash_sets(rel_targets_ret, rel_url, 455 lock_token ? lock_token : ""); 456 } 457 } 458 459 svn_pool_destroy(iterpool); 460 } 461 462 /* Set our return variables. */ 463 *common_parent_url = common_url; 464 if (*lock_paths && (*lock_paths)->nelts == 1) 465 *base_dir_abspath = APR_ARRAY_IDX(*lock_paths, 0, const char*); 466 else 467 *base_dir_abspath = NULL; 468 *rel_targets_p = rel_targets_ret; 469 *rel_fs_paths_p = rel_fs_paths; 470 471 return SVN_NO_ERROR; 472} 473 474/* Fetch lock tokens from the repository for the paths in PATH_TOKENS, 475 setting the values to the fetched tokens, allocated in pool. */ 476static svn_error_t * 477fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens, 478 apr_pool_t *pool) 479{ 480 apr_hash_index_t *hi; 481 apr_pool_t *iterpool = svn_pool_create(pool); 482 483 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 484 { 485 const char *path = apr_hash_this_key(hi); 486 svn_lock_t *lock; 487 488 svn_pool_clear(iterpool); 489 490 SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool)); 491 492 if (! lock) 493 return svn_error_createf 494 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 495 _("'%s' is not locked"), path); 496 497 svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token)); 498 } 499 500 svn_pool_destroy(iterpool); 501 return SVN_NO_ERROR; 502} 503 504 505svn_error_t * 506svn_client_lock(const apr_array_header_t *targets, 507 const char *comment, 508 svn_boolean_t steal_lock, 509 svn_client_ctx_t *ctx, 510 apr_pool_t *pool) 511{ 512 const char *base_dir_abspath = NULL; 513 const char *common_parent_url; 514 svn_ra_session_t *ra_session; 515 apr_hash_t *path_revs, *urls_to_paths; 516 struct lock_baton cb; 517 apr_array_header_t *lock_abspaths; 518 svn_error_t *err; 519 520 if (apr_is_empty_array(targets)) 521 return SVN_NO_ERROR; 522 523 /* Enforce that the comment be xml-escapable. */ 524 if (comment) 525 { 526 if (! svn_xml_is_xml_safe(comment, strlen(comment))) 527 return svn_error_create 528 (SVN_ERR_XML_UNESCAPABLE_DATA, NULL, 529 _("Lock comment contains illegal characters")); 530 } 531 532 err = organize_lock_targets(&lock_abspaths, &common_parent_url, 533 &base_dir_abspath, &path_revs, 534 &urls_to_paths, 535 targets, TRUE, steal_lock, 536 ctx->wc_ctx, pool, pool); 537 538 if (err) 539 goto release_locks; 540 541 /* Open an RA session to the common parent URL of TARGETS. */ 542 err = svn_client_open_ra_session2(&ra_session, common_parent_url, 543 base_dir_abspath, ctx, pool, pool); 544 545 if (err) 546 goto release_locks; 547 548 cb.base_dir_abspath = base_dir_abspath; 549 cb.base_url = common_parent_url; 550 cb.urls_to_paths = urls_to_paths; 551 cb.ctx = ctx; 552 cb.pool = pool; 553 554 /* Lock the paths. */ 555 err = svn_ra_lock(ra_session, path_revs, comment, 556 steal_lock, store_locks_callback, &cb, pool); 557 558release_locks: 559 if (lock_abspaths) 560 { 561 int i; 562 563 for (i = 0; i < lock_abspaths->nelts; i++) 564 { 565 err = svn_error_compose_create( 566 err, 567 svn_wc__release_write_lock(ctx->wc_ctx, 568 APR_ARRAY_IDX(lock_abspaths, i, 569 const char *), 570 pool)); 571 } 572 } 573 574 return svn_error_trace(err); 575} 576 577svn_error_t * 578svn_client_unlock(const apr_array_header_t *targets, 579 svn_boolean_t break_lock, 580 svn_client_ctx_t *ctx, 581 apr_pool_t *pool) 582{ 583 const char *base_dir_abspath = NULL; 584 const char *common_parent_url; 585 svn_ra_session_t *ra_session; 586 apr_hash_t *path_tokens, *urls_to_paths; 587 apr_array_header_t *lock_abspaths; 588 struct lock_baton cb; 589 svn_error_t *err; 590 591 if (apr_is_empty_array(targets)) 592 return SVN_NO_ERROR; 593 594 err = organize_lock_targets(&lock_abspaths, &common_parent_url, 595 &base_dir_abspath, &path_tokens, &urls_to_paths, 596 targets, FALSE, break_lock, 597 ctx->wc_ctx, pool, pool); 598 599 if (err) 600 goto release_locks; 601 602 /* Open an RA session to the common parent URL of TARGETS. */ 603 err = svn_client_open_ra_session2(&ra_session, common_parent_url, 604 base_dir_abspath, ctx, pool, pool); 605 606 if (err) 607 goto release_locks; 608 609 /* If break_lock is not set, lock tokens are required by the server. 610 If the targets were all URLs, ensure that we provide lock tokens, 611 so the repository will only check that the user owns the 612 locks. */ 613 if (! lock_abspaths && !break_lock) 614 { 615 err = fetch_tokens(ra_session, path_tokens, pool); 616 617 if (err) 618 goto release_locks; 619 } 620 621 cb.base_dir_abspath = base_dir_abspath; 622 cb.base_url = common_parent_url; 623 cb.urls_to_paths = urls_to_paths; 624 cb.ctx = ctx; 625 cb.pool = pool; 626 627 /* Unlock the paths. */ 628 err = svn_ra_unlock(ra_session, path_tokens, break_lock, 629 store_locks_callback, &cb, pool); 630 631release_locks: 632 if (lock_abspaths) 633 { 634 int i; 635 636 for (i = 0; i < lock_abspaths->nelts; i++) 637 { 638 err = svn_error_compose_create( 639 err, 640 svn_wc__release_write_lock(ctx->wc_ctx, 641 APR_ARRAY_IDX(lock_abspaths, i, 642 const char *), 643 pool)); 644 } 645 } 646 647 return svn_error_trace(err); 648} 649 650