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; 50 svn_client_ctx_t *ctx; 51 apr_pool_t *pool; 52}; 53 54 55/* This callback is called by the ra_layer for each path locked. 56 * BATON is a 'struct lock_baton *', PATH is the path being locked, 57 * and LOCK is the lock itself. 58 * 59 * If BATON->base_dir_abspath is not null, then this function either 60 * stores the LOCK on REL_URL or removes any lock tokens from REL_URL 61 * (depending on whether DO_LOCK is true or false respectively), but 62 * only if RA_ERR is null, or (in the unlock case) is something other 63 * than SVN_ERR_FS_LOCK_OWNER_MISMATCH. 64 * 65 * Implements svn_ra_lock_callback_t. 66 */ 67static svn_error_t * 68store_locks_callback(void *baton, 69 const char *rel_url, 70 svn_boolean_t do_lock, 71 const svn_lock_t *lock, 72 svn_error_t *ra_err, apr_pool_t *pool) 73{ 74 struct lock_baton *lb = baton; 75 svn_wc_notify_t *notify; 76 77 /* Create the notify struct first, so we can tweak it below. */ 78 notify = svn_wc_create_notify(rel_url, 79 do_lock 80 ? (ra_err 81 ? svn_wc_notify_failed_lock 82 : svn_wc_notify_locked) 83 : (ra_err 84 ? svn_wc_notify_failed_unlock 85 : svn_wc_notify_unlocked), 86 pool); 87 notify->lock = lock; 88 notify->err = ra_err; 89 90 if (lb->base_dir_abspath) 91 { 92 char *path = svn_hash_gets(lb->urls_to_paths, rel_url); 93 const char *local_abspath; 94 95 local_abspath = svn_dirent_join(lb->base_dir_abspath, path, pool); 96 97 /* Notify a valid working copy path */ 98 notify->path = local_abspath; 99 notify->path_prefix = lb->base_dir_abspath; 100 101 if (do_lock) 102 { 103 if (!ra_err) 104 { 105 SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock, 106 lb->pool)); 107 notify->lock_state = svn_wc_notify_lock_state_locked; 108 } 109 else 110 notify->lock_state = svn_wc_notify_lock_state_unchanged; 111 } 112 else /* unlocking */ 113 { 114 /* Remove our wc lock token either a) if we got no error, or b) if 115 we got any error except for owner mismatch. Note that the only 116 errors that are handed to this callback will be locking-related 117 errors. */ 118 119 if (!ra_err || 120 (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH))) 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 = rel_url; /* Notify that path is actually a url */ 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}; 199 200/* Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS. 201 * If TARGETS are local paths, then the entry for each path is examined 202 * and *COMMON_PARENT is set to the common parent URL for all the 203 * targets (as opposed to the common local path). 204 * 205 * If there is no common parent, either because the targets are a 206 * mixture of URLs and local paths, or because they simply do not 207 * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE. 208 * 209 * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them. 210 * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise. 211 * 212 * Each key stored in *REL_TARGETS_P is a path relative to 213 * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is 214 * true, the value is a pointer to the corresponding base_revision 215 * (allocated in POOL) for the path, else the value is the lock token 216 * (or "" if no token found in the wc). 217 * 218 * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL. 219 * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to 220 * COMMON_PARENT) mapped to the target path for TARGET (relative to 221 * the common parent WC path). working copy targets that they "belong" to. 222 * 223 * If *COMMON_PARENT is a URL, then the values are a pointer to 224 * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "". 225 * 226 * TARGETS may not be empty. 227 */ 228static svn_error_t * 229organize_lock_targets(const char **common_parent_url, 230 const char **base_dir, 231 apr_hash_t **rel_targets_p, 232 apr_hash_t **rel_fs_paths_p, 233 const apr_array_header_t *targets, 234 svn_boolean_t do_lock, 235 svn_boolean_t force, 236 svn_wc_context_t *wc_ctx, 237 apr_pool_t *result_pool, 238 apr_pool_t *scratch_pool) 239{ 240 const char *common_url = NULL; 241 const char *common_dirent = NULL; 242 apr_hash_t *rel_targets_ret = apr_hash_make(result_pool); 243 apr_hash_t *rel_fs_paths = NULL; 244 apr_array_header_t *rel_targets; 245 apr_hash_t *wc_info = apr_hash_make(scratch_pool); 246 svn_boolean_t url_mode; 247 int i; 248 249 SVN_ERR_ASSERT(targets->nelts); 250 SVN_ERR(svn_client__assert_homogeneous_target_type(targets)); 251 252 url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); 253 254 if (url_mode) 255 { 256 svn_revnum_t *invalid_revnum = 257 apr_palloc(result_pool, sizeof(*invalid_revnum)); 258 259 *invalid_revnum = SVN_INVALID_REVNUM; 260 261 /* Get the common parent URL and a bunch of relpaths, one per target. */ 262 SVN_ERR(condense_targets(&common_url, &rel_targets, targets, 263 TRUE, TRUE, result_pool, scratch_pool)); 264 if (! (common_url && *common_url)) 265 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 266 _("No common parent found, unable to operate " 267 "on disjoint arguments")); 268 269 /* Create mapping of the target relpaths to either 270 SVN_INVALID_REVNUM (if our caller is locking) or to an empty 271 lock token string (if the caller is unlocking). */ 272 for (i = 0; i < rel_targets->nelts; i++) 273 { 274 svn_hash_sets(rel_targets_ret, 275 APR_ARRAY_IDX(rel_targets, i, const char *), 276 do_lock 277 ? (const void *)invalid_revnum 278 : (const void *)""); 279 } 280 } 281 else 282 { 283 apr_array_header_t *rel_urls, *target_urls; 284 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 285 286 /* Get the common parent dirent and a bunch of relpaths, one per 287 target. */ 288 SVN_ERR(condense_targets(&common_dirent, &rel_targets, targets, 289 FALSE, TRUE, result_pool, scratch_pool)); 290 if (! (common_dirent && *common_dirent)) 291 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 292 _("No common parent found, unable to operate " 293 "on disjoint arguments")); 294 295 /* Get the URL for each target (which also serves to verify that 296 the dirent targets are sane). */ 297 target_urls = apr_array_make(scratch_pool, rel_targets->nelts, 298 sizeof(const char *)); 299 for (i = 0; i < rel_targets->nelts; i++) 300 { 301 const char *rel_target; 302 const char *repos_relpath; 303 const char *repos_root_url; 304 const char *target_url; 305 struct wc_lock_item_t *wli; 306 const char *local_abspath; 307 svn_node_kind_t kind; 308 309 svn_pool_clear(iterpool); 310 311 rel_target = APR_ARRAY_IDX(rel_targets, i, const char *); 312 local_abspath = svn_dirent_join(common_dirent, rel_target, scratch_pool); 313 wli = apr_pcalloc(scratch_pool, sizeof(*wli)); 314 315 SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, &repos_relpath, 316 &repos_root_url, NULL, 317 &wli->lock_token, 318 wc_ctx, local_abspath, 319 FALSE /* ignore_enoent */, 320 FALSE /* show_hidden */, 321 result_pool, iterpool)); 322 323 if (kind != svn_node_file) 324 return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL, 325 _("The node '%s' is not a file"), 326 svn_dirent_local_style(local_abspath, 327 iterpool)); 328 329 svn_hash_sets(wc_info, local_abspath, wli); 330 331 target_url = svn_path_url_add_component2(repos_root_url, 332 repos_relpath, 333 scratch_pool); 334 335 APR_ARRAY_PUSH(target_urls, const char *) = target_url; 336 } 337 338 /* Now that we have a bunch of URLs for our dirent targets, 339 condense those into a single common parent URL and a bunch of 340 paths relative to that. */ 341 SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls, 342 TRUE, FALSE, result_pool, scratch_pool)); 343 if (! (common_url && *common_url)) 344 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 345 _("Unable to lock/unlock across multiple " 346 "repositories")); 347 348 /* Now we need to create a couple of different hash mappings. */ 349 rel_fs_paths = apr_hash_make(result_pool); 350 for (i = 0; i < rel_targets->nelts; i++) 351 { 352 const char *rel_target, *rel_url; 353 const char *local_abspath; 354 355 svn_pool_clear(iterpool); 356 357 /* First, we need to map our REL_URL (which is relative to 358 COMMON_URL) to our REL_TARGET (which is relative to 359 COMMON_DIRENT). */ 360 rel_target = APR_ARRAY_IDX(rel_targets, i, const char *); 361 rel_url = APR_ARRAY_IDX(rel_urls, i, const char *); 362 svn_hash_sets(rel_fs_paths, rel_url, 363 apr_pstrdup(result_pool, rel_target)); 364 365 /* Then, we map our REL_URL (again) to either the base 366 revision of the dirent target with which it is associated 367 (if our caller is locking) or to a (possible empty) lock 368 token string (if the caller is unlocking). */ 369 local_abspath = svn_dirent_join(common_dirent, rel_target, iterpool); 370 371 if (do_lock) /* Lock. */ 372 { 373 svn_revnum_t *revnum; 374 struct wc_lock_item_t *wli; 375 revnum = apr_palloc(result_pool, sizeof(* revnum)); 376 377 wli = svn_hash_gets(wc_info, local_abspath); 378 379 SVN_ERR_ASSERT(wli != NULL); 380 381 *revnum = wli->revision; 382 383 svn_hash_sets(rel_targets_ret, rel_url, revnum); 384 } 385 else /* Unlock. */ 386 { 387 const char *lock_token; 388 struct wc_lock_item_t *wli; 389 390 /* If not forcing the unlock, get the lock token. */ 391 if (! force) 392 { 393 wli = svn_hash_gets(wc_info, local_abspath); 394 395 SVN_ERR_ASSERT(wli != NULL); 396 397 if (! wli->lock_token) 398 return svn_error_createf( 399 SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 400 _("'%s' is not locked in this working copy"), 401 svn_dirent_local_style(local_abspath, 402 scratch_pool)); 403 404 lock_token = wli->lock_token 405 ? apr_pstrdup(result_pool, wli->lock_token) 406 : NULL; 407 } 408 else 409 lock_token = NULL; 410 411 /* If breaking a lock, we shouldn't pass any lock token. */ 412 svn_hash_sets(rel_targets_ret, rel_url, 413 lock_token ? lock_token : ""); 414 } 415 } 416 417 svn_pool_destroy(iterpool); 418 } 419 420 /* Set our return variables. */ 421 *common_parent_url = common_url; 422 *base_dir = common_dirent; 423 *rel_targets_p = rel_targets_ret; 424 *rel_fs_paths_p = rel_fs_paths; 425 426 return SVN_NO_ERROR; 427} 428 429/* Fetch lock tokens from the repository for the paths in PATH_TOKENS, 430 setting the values to the fetched tokens, allocated in pool. */ 431static svn_error_t * 432fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens, 433 apr_pool_t *pool) 434{ 435 apr_hash_index_t *hi; 436 apr_pool_t *iterpool = svn_pool_create(pool); 437 438 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 439 { 440 const char *path = svn__apr_hash_index_key(hi); 441 svn_lock_t *lock; 442 443 svn_pool_clear(iterpool); 444 445 SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool)); 446 447 if (! lock) 448 return svn_error_createf 449 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 450 _("'%s' is not locked"), path); 451 452 svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token)); 453 } 454 455 svn_pool_destroy(iterpool); 456 return SVN_NO_ERROR; 457} 458 459 460svn_error_t * 461svn_client_lock(const apr_array_header_t *targets, 462 const char *comment, 463 svn_boolean_t steal_lock, 464 svn_client_ctx_t *ctx, 465 apr_pool_t *pool) 466{ 467 const char *base_dir; 468 const char *base_dir_abspath = NULL; 469 const char *common_parent_url; 470 svn_ra_session_t *ra_session; 471 apr_hash_t *path_revs, *urls_to_paths; 472 struct lock_baton cb; 473 474 if (apr_is_empty_array(targets)) 475 return SVN_NO_ERROR; 476 477 /* Enforce that the comment be xml-escapable. */ 478 if (comment) 479 { 480 if (! svn_xml_is_xml_safe(comment, strlen(comment))) 481 return svn_error_create 482 (SVN_ERR_XML_UNESCAPABLE_DATA, NULL, 483 _("Lock comment contains illegal characters")); 484 } 485 486 SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_revs, 487 &urls_to_paths, targets, TRUE, steal_lock, 488 ctx->wc_ctx, pool, pool)); 489 490 /* Open an RA session to the common parent of TARGETS. */ 491 if (base_dir) 492 SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool)); 493 SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url, 494 base_dir_abspath, ctx, pool, pool)); 495 496 cb.base_dir_abspath = base_dir_abspath; 497 cb.urls_to_paths = urls_to_paths; 498 cb.ctx = ctx; 499 cb.pool = pool; 500 501 /* Lock the paths. */ 502 SVN_ERR(svn_ra_lock(ra_session, path_revs, comment, 503 steal_lock, store_locks_callback, &cb, pool)); 504 505 return SVN_NO_ERROR; 506} 507 508svn_error_t * 509svn_client_unlock(const apr_array_header_t *targets, 510 svn_boolean_t break_lock, 511 svn_client_ctx_t *ctx, 512 apr_pool_t *pool) 513{ 514 const char *base_dir; 515 const char *base_dir_abspath = NULL; 516 const char *common_parent_url; 517 svn_ra_session_t *ra_session; 518 apr_hash_t *path_tokens, *urls_to_paths; 519 struct lock_baton cb; 520 521 if (apr_is_empty_array(targets)) 522 return SVN_NO_ERROR; 523 524 SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_tokens, 525 &urls_to_paths, targets, FALSE, break_lock, 526 ctx->wc_ctx, pool, pool)); 527 528 /* Open an RA session. */ 529 if (base_dir) 530 SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool)); 531 SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url, 532 base_dir_abspath, ctx, pool, pool)); 533 534 /* If break_lock is not set, lock tokens are required by the server. 535 If the targets were all URLs, ensure that we provide lock tokens, 536 so the repository will only check that the user owns the 537 locks. */ 538 if (! base_dir && !break_lock) 539 SVN_ERR(fetch_tokens(ra_session, path_tokens, pool)); 540 541 cb.base_dir_abspath = base_dir_abspath; 542 cb.urls_to_paths = urls_to_paths; 543 cb.ctx = ctx; 544 cb.pool = pool; 545 546 /* Unlock the paths. */ 547 SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock, 548 store_locks_callback, &cb, pool)); 549 550 return SVN_NO_ERROR; 551} 552 553