fs-wrap.c revision 299742
1/* fs-wrap.c --- filesystem interface wrappers. 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#include <stdio.h> 24#include <string.h> 25#include <ctype.h> 26 27#include "svn_hash.h" 28#include "svn_pools.h" 29#include "svn_error.h" 30#include "svn_fs.h" 31#include "svn_path.h" 32#include "svn_props.h" 33#include "svn_repos.h" 34#include "svn_time.h" 35#include "svn_sorts.h" 36#include "repos.h" 37#include "svn_private_config.h" 38#include "private/svn_repos_private.h" 39#include "private/svn_sorts_private.h" 40#include "private/svn_utf_private.h" 41#include "private/svn_fspath.h" 42 43 44/*** Commit wrappers ***/ 45 46svn_error_t * 47svn_repos_fs_commit_txn(const char **conflict_p, 48 svn_repos_t *repos, 49 svn_revnum_t *new_rev, 50 svn_fs_txn_t *txn, 51 apr_pool_t *pool) 52{ 53 svn_error_t *err, *err2; 54 const char *txn_name; 55 apr_hash_t *props; 56 apr_pool_t *iterpool; 57 apr_hash_index_t *hi; 58 apr_hash_t *hooks_env; 59 60 *new_rev = SVN_INVALID_REVNUM; 61 62 /* Parse the hooks-env file (if any). */ 63 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 64 pool, pool)); 65 66 /* Run pre-commit hooks. */ 67 SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); 68 SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool)); 69 70 /* Remove any ephemeral transaction properties. If the commit fails 71 we will attempt to restore the properties but if that fails, or 72 the process is killed, the properties will be lost. */ 73 SVN_ERR(svn_fs_txn_proplist(&props, txn, pool)); 74 iterpool = svn_pool_create(pool); 75 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 76 { 77 const char *key = apr_hash_this_key(hi); 78 79 svn_pool_clear(iterpool); 80 81 if (strncmp(key, SVN_PROP_TXN_PREFIX, 82 (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0) 83 { 84 SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool)); 85 } 86 } 87 svn_pool_destroy(iterpool); 88 89 /* Commit. */ 90 err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool); 91 if (! SVN_IS_VALID_REVNUM(*new_rev)) 92 { 93 /* The commit failed, try to restore the ephemeral properties. */ 94 iterpool = svn_pool_create(pool); 95 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 96 { 97 const char *key = apr_hash_this_key(hi); 98 svn_string_t *val = apr_hash_this_val(hi); 99 100 svn_pool_clear(iterpool); 101 102 if (strncmp(key, SVN_PROP_TXN_PREFIX, 103 (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0) 104 svn_error_clear(svn_fs_change_txn_prop(txn, key, val, iterpool)); 105 } 106 svn_pool_destroy(iterpool); 107 108 return err; 109 } 110 111 /* Run post-commit hooks. */ 112 if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env, 113 *new_rev, txn_name, pool))) 114 { 115 err2 = svn_error_create 116 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2, 117 _("Commit succeeded, but post-commit hook failed")); 118 } 119 120 return svn_error_compose_create(err, err2); 121} 122 123 124 125/*** Transaction creation wrappers. ***/ 126 127 128svn_error_t * 129svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p, 130 svn_repos_t *repos, 131 svn_revnum_t rev, 132 apr_hash_t *revprop_table, 133 apr_pool_t *pool) 134{ 135 apr_array_header_t *revprops; 136 const char *txn_name; 137 svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR); 138 apr_hash_t *hooks_env; 139 svn_error_t *err; 140 svn_fs_txn_t *txn; 141 142 /* Parse the hooks-env file (if any). */ 143 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 144 pool, pool)); 145 146 /* Begin the transaction, ask for the fs to do on-the-fly lock checks. 147 We fetch its name, too, so the start-commit hook can use it. */ 148 SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev, 149 SVN_FS_TXN_CHECK_LOCKS, pool)); 150 err = svn_fs_txn_name(&txn_name, txn, pool); 151 if (err) 152 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool)); 153 154 /* We pass the revision properties to the filesystem by adding them 155 as properties on the txn. Later, when we commit the txn, these 156 properties will be copied into the newly created revision. */ 157 revprops = svn_prop_hash_to_array(revprop_table, pool); 158 err = svn_repos_fs_change_txn_props(txn, revprops, pool); 159 if (err) 160 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool)); 161 162 /* Run start-commit hooks. */ 163 err = svn_repos__hooks_start_commit(repos, hooks_env, 164 author ? author->data : NULL, 165 repos->client_capabilities, txn_name, 166 pool); 167 if (err) 168 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool)); 169 170 /* We have API promise that *TXN_P is unaffected on failure. */ 171 *txn_p = txn; 172 return SVN_NO_ERROR; 173} 174 175 176svn_error_t * 177svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p, 178 svn_repos_t *repos, 179 svn_revnum_t rev, 180 const char *author, 181 const char *log_msg, 182 apr_pool_t *pool) 183{ 184 apr_hash_t *revprop_table = apr_hash_make(pool); 185 if (author) 186 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 187 svn_string_create(author, pool)); 188 if (log_msg) 189 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, 190 svn_string_create(log_msg, pool)); 191 return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table, 192 pool); 193} 194 195 196/*** Property wrappers ***/ 197 198svn_error_t * 199svn_repos__validate_prop(const char *name, 200 const svn_string_t *value, 201 apr_pool_t *pool) 202{ 203 svn_prop_kind_t kind = svn_property_kind2(name); 204 205 /* Allow deleting any property, even a property we don't allow to set. */ 206 if (value == NULL) 207 return SVN_NO_ERROR; 208 209 /* Disallow setting non-regular properties. */ 210 if (kind != svn_prop_regular_kind) 211 return svn_error_createf 212 (SVN_ERR_REPOS_BAD_ARGS, NULL, 213 _("Storage of non-regular property '%s' is disallowed through the " 214 "repository interface, and could indicate a bug in your client"), 215 name); 216 217 /* Validate "svn:" properties. */ 218 if (svn_prop_is_svn_prop(name) && value != NULL) 219 { 220 /* Validate that translated props (e.g., svn:log) are UTF-8 with 221 * LF line endings. */ 222 if (svn_prop_needs_translation(name)) 223 { 224 if (!svn_utf__is_valid(value->data, value->len)) 225 { 226 return svn_error_createf 227 (SVN_ERR_BAD_PROPERTY_VALUE, NULL, 228 _("Cannot accept '%s' property because it is not encoded in " 229 "UTF-8"), name); 230 } 231 232 /* Disallow inconsistent line ending style, by simply looking for 233 * carriage return characters ('\r'). */ 234 if (strchr(value->data, '\r') != NULL) 235 { 236 return svn_error_createf 237 (SVN_ERR_BAD_PROPERTY_VALUE, NULL, 238 _("Cannot accept non-LF line endings in '%s' property"), 239 name); 240 } 241 } 242 243 /* "svn:date" should be a valid date. */ 244 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) 245 { 246 apr_time_t temp; 247 svn_error_t *err; 248 249 err = svn_time_from_cstring(&temp, value->data, pool); 250 if (err) 251 return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE, 252 err, NULL); 253 } 254 } 255 256 return SVN_NO_ERROR; 257} 258 259 260/* Verify the mergeinfo property value VALUE and return an error if it 261 * is invalid. The PATH on which that property is set is used for error 262 * messages only. Use SCRATCH_POOL for temporary allocations. */ 263static svn_error_t * 264verify_mergeinfo(const svn_string_t *value, 265 const char *path, 266 apr_pool_t *scratch_pool) 267{ 268 svn_error_t *err; 269 svn_mergeinfo_t mergeinfo; 270 271 /* It's okay to delete svn:mergeinfo. */ 272 if (value == NULL) 273 return SVN_NO_ERROR; 274 275 /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen() 276 * should match VALUE->LEN. Prevents trailing garbage in the property. */ 277 if (strlen(value->data) != value->len) 278 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, 279 _("Commit rejected because mergeinfo on '%s' " 280 "contains unexpected string terminator"), 281 path); 282 283 err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool); 284 if (err) 285 return svn_error_createf(err->apr_err, err, 286 _("Commit rejected because mergeinfo on '%s' " 287 "is syntactically invalid"), 288 path); 289 return SVN_NO_ERROR; 290} 291 292 293svn_error_t * 294svn_repos_fs_change_node_prop(svn_fs_root_t *root, 295 const char *path, 296 const char *name, 297 const svn_string_t *value, 298 apr_pool_t *pool) 299{ 300 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0) 301 SVN_ERR(verify_mergeinfo(value, path, pool)); 302 303 /* Validate the property, then call the wrapped function. */ 304 SVN_ERR(svn_repos__validate_prop(name, value, pool)); 305 return svn_fs_change_node_prop(root, path, name, value, pool); 306} 307 308 309svn_error_t * 310svn_repos_fs_change_txn_props(svn_fs_txn_t *txn, 311 const apr_array_header_t *txnprops, 312 apr_pool_t *pool) 313{ 314 int i; 315 316 for (i = 0; i < txnprops->nelts; i++) 317 { 318 svn_prop_t *prop = &APR_ARRAY_IDX(txnprops, i, svn_prop_t); 319 SVN_ERR(svn_repos__validate_prop(prop->name, prop->value, pool)); 320 } 321 322 return svn_fs_change_txn_props(txn, txnprops, pool); 323} 324 325 326svn_error_t * 327svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn, 328 const char *name, 329 const svn_string_t *value, 330 apr_pool_t *pool) 331{ 332 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t)); 333 svn_prop_t prop; 334 335 prop.name = name; 336 prop.value = value; 337 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 338 339 return svn_repos_fs_change_txn_props(txn, props, pool); 340} 341 342 343svn_error_t * 344svn_repos_fs_change_rev_prop4(svn_repos_t *repos, 345 svn_revnum_t rev, 346 const char *author, 347 const char *name, 348 const svn_string_t *const *old_value_p, 349 const svn_string_t *new_value, 350 svn_boolean_t use_pre_revprop_change_hook, 351 svn_boolean_t use_post_revprop_change_hook, 352 svn_repos_authz_func_t authz_read_func, 353 void *authz_read_baton, 354 apr_pool_t *pool) 355{ 356 svn_repos_revision_access_level_t readability; 357 358 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev, 359 authz_read_func, authz_read_baton, 360 pool)); 361 362 if (readability == svn_repos_revision_access_full) 363 { 364 const svn_string_t *old_value; 365 char action; 366 apr_hash_t *hooks_env; 367 368 SVN_ERR(svn_repos__validate_prop(name, new_value, pool)); 369 370 /* Fetch OLD_VALUE for svn_fs_change_rev_prop2(). */ 371 if (old_value_p) 372 { 373 old_value = *old_value_p; 374 } 375 else 376 { 377 /* Get OLD_VALUE anyway, in order for ACTION and OLD_VALUE arguments 378 * to the hooks to be accurate. */ 379 svn_string_t *old_value2; 380 381 SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool)); 382 old_value = old_value2; 383 } 384 385 /* Prepare ACTION. */ 386 if (! new_value) 387 action = 'D'; 388 else if (! old_value) 389 action = 'A'; 390 else 391 action = 'M'; 392 393 /* Parse the hooks-env file (if any, and if to be used). */ 394 if (use_pre_revprop_change_hook || use_post_revprop_change_hook) 395 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 396 pool, pool)); 397 398 /* ### currently not passing the old_value to hooks */ 399 if (use_pre_revprop_change_hook) 400 SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev, 401 author, name, new_value, 402 action, pool)); 403 404 SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name, 405 &old_value, new_value, pool)); 406 407 if (use_post_revprop_change_hook) 408 SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev, 409 author, name, old_value, 410 action, pool)); 411 } 412 else /* rev is either unreadable or only partially readable */ 413 { 414 return svn_error_createf 415 (SVN_ERR_AUTHZ_UNREADABLE, NULL, 416 _("Write denied: not authorized to read all of revision %ld"), rev); 417 } 418 419 return SVN_NO_ERROR; 420} 421 422 423svn_error_t * 424svn_repos_fs_revision_prop(svn_string_t **value_p, 425 svn_repos_t *repos, 426 svn_revnum_t rev, 427 const char *propname, 428 svn_repos_authz_func_t authz_read_func, 429 void *authz_read_baton, 430 apr_pool_t *pool) 431{ 432 svn_repos_revision_access_level_t readability; 433 434 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev, 435 authz_read_func, authz_read_baton, 436 pool)); 437 438 if (readability == svn_repos_revision_access_none) 439 { 440 /* Property? What property? */ 441 *value_p = NULL; 442 } 443 else if (readability == svn_repos_revision_access_partial) 444 { 445 /* Only svn:author and svn:date are fetchable. */ 446 if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0) 447 && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0)) 448 *value_p = NULL; 449 450 else 451 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, 452 rev, propname, pool)); 453 } 454 else /* wholly readable revision */ 455 { 456 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool)); 457 } 458 459 return SVN_NO_ERROR; 460} 461 462 463 464svn_error_t * 465svn_repos_fs_revision_proplist(apr_hash_t **table_p, 466 svn_repos_t *repos, 467 svn_revnum_t rev, 468 svn_repos_authz_func_t authz_read_func, 469 void *authz_read_baton, 470 apr_pool_t *pool) 471{ 472 svn_repos_revision_access_level_t readability; 473 474 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev, 475 authz_read_func, authz_read_baton, 476 pool)); 477 478 if (readability == svn_repos_revision_access_none) 479 { 480 /* Return an empty hash. */ 481 *table_p = apr_hash_make(pool); 482 } 483 else if (readability == svn_repos_revision_access_partial) 484 { 485 apr_hash_t *tmphash; 486 svn_string_t *value; 487 488 /* Produce two property hashtables, both in POOL. */ 489 SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool)); 490 *table_p = apr_hash_make(pool); 491 492 /* If they exist, we only copy svn:author and svn:date into the 493 'real' hashtable being returned. */ 494 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR); 495 if (value) 496 svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value); 497 498 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE); 499 if (value) 500 svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value); 501 } 502 else /* wholly readable revision */ 503 { 504 SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool)); 505 } 506 507 return SVN_NO_ERROR; 508} 509 510struct lock_many_baton_t { 511 svn_boolean_t need_lock; 512 apr_array_header_t *paths; 513 svn_fs_lock_callback_t lock_callback; 514 void *lock_baton; 515 svn_error_t *cb_err; 516 apr_pool_t *pool; 517}; 518 519/* Implements svn_fs_lock_callback_t. Used by svn_repos_fs_lock_many 520 and svn_repos_fs_unlock_many to record the paths for use by post- 521 hooks, forward to the supplied callback and record any callback 522 error. */ 523static svn_error_t * 524lock_many_cb(void *lock_baton, 525 const char *path, 526 const svn_lock_t *lock, 527 svn_error_t *fs_err, 528 apr_pool_t *pool) 529{ 530 struct lock_many_baton_t *b = lock_baton; 531 532 if (!b->cb_err && b->lock_callback) 533 b->cb_err = b->lock_callback(b->lock_baton, path, lock, fs_err, pool); 534 535 if ((b->need_lock && lock) || (!b->need_lock && !fs_err)) 536 APR_ARRAY_PUSH(b->paths, const char *) = apr_pstrdup(b->pool, path); 537 538 return SVN_NO_ERROR; 539} 540 541svn_error_t * 542svn_repos_fs_lock_many(svn_repos_t *repos, 543 apr_hash_t *targets, 544 const char *comment, 545 svn_boolean_t is_dav_comment, 546 apr_time_t expiration_date, 547 svn_boolean_t steal_lock, 548 svn_fs_lock_callback_t lock_callback, 549 void *lock_baton, 550 apr_pool_t *result_pool, 551 apr_pool_t *scratch_pool) 552{ 553 svn_error_t *err, *cb_err = SVN_NO_ERROR; 554 svn_fs_access_t *access_ctx = NULL; 555 const char *username = NULL; 556 apr_hash_t *hooks_env; 557 apr_hash_t *pre_targets = apr_hash_make(scratch_pool); 558 apr_hash_index_t *hi; 559 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 560 struct lock_many_baton_t baton; 561 562 if (!apr_hash_count(targets)) 563 return SVN_NO_ERROR; 564 565 /* Parse the hooks-env file (if any). */ 566 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 567 scratch_pool, scratch_pool)); 568 569 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs)); 570 if (access_ctx) 571 SVN_ERR(svn_fs_access_get_username(&username, access_ctx)); 572 573 if (! username) 574 return svn_error_create 575 (SVN_ERR_FS_NO_USER, NULL, 576 "Cannot lock path, no authenticated username available."); 577 578 /* Run pre-lock hook. This could throw error, preventing 579 svn_fs_lock2() from happening for that path. */ 580 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 581 { 582 const char *new_token; 583 svn_fs_lock_target_t *target; 584 const char *path = apr_hash_this_key(hi); 585 586 svn_pool_clear(iterpool); 587 588 err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path, 589 username, comment, steal_lock, iterpool); 590 if (err) 591 { 592 if (!cb_err && lock_callback) 593 cb_err = lock_callback(lock_baton, path, NULL, err, iterpool); 594 svn_error_clear(err); 595 596 continue; 597 } 598 599 target = apr_hash_this_val(hi); 600 if (*new_token) 601 svn_fs_lock_target_set_token(target, new_token); 602 svn_hash_sets(pre_targets, path, target); 603 } 604 605 if (!apr_hash_count(pre_targets)) 606 return svn_error_trace(cb_err); 607 608 baton.need_lock = TRUE; 609 baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets), 610 sizeof(const char *)); 611 baton.lock_callback = lock_callback; 612 baton.lock_baton = lock_baton; 613 baton.cb_err = cb_err; 614 baton.pool = scratch_pool; 615 616 err = svn_fs_lock_many(repos->fs, pre_targets, comment, 617 is_dav_comment, expiration_date, steal_lock, 618 lock_many_cb, &baton, result_pool, iterpool); 619 620 /* If there are locks run the post-lock even if there is an error. */ 621 if (baton.paths->nelts) 622 { 623 svn_error_t *perr = svn_repos__hooks_post_lock(repos, hooks_env, 624 baton.paths, username, 625 iterpool); 626 if (perr) 627 { 628 perr = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, perr, 629 _("Locking succeeded, but post-lock hook failed")); 630 err = svn_error_compose_create(err, perr); 631 } 632 } 633 634 svn_pool_destroy(iterpool); 635 636 if (err && cb_err) 637 svn_error_compose(err, cb_err); 638 else if (!err) 639 err = cb_err; 640 641 return svn_error_trace(err); 642} 643 644struct lock_baton_t { 645 const svn_lock_t *lock; 646 svn_error_t *fs_err; 647}; 648 649/* Implements svn_fs_lock_callback_t. Used by svn_repos_fs_lock and 650 svn_repos_fs_unlock to record the lock and error from 651 svn_repos_fs_lock_many and svn_repos_fs_unlock_many. */ 652static svn_error_t * 653lock_cb(void *lock_baton, 654 const char *path, 655 const svn_lock_t *lock, 656 svn_error_t *fs_err, 657 apr_pool_t *pool) 658{ 659 struct lock_baton_t *b = lock_baton; 660 661 b->lock = lock; 662 b->fs_err = svn_error_dup(fs_err); 663 664 return SVN_NO_ERROR; 665} 666 667svn_error_t * 668svn_repos_fs_lock(svn_lock_t **lock, 669 svn_repos_t *repos, 670 const char *path, 671 const char *token, 672 const char *comment, 673 svn_boolean_t is_dav_comment, 674 apr_time_t expiration_date, 675 svn_revnum_t current_rev, 676 svn_boolean_t steal_lock, 677 apr_pool_t *pool) 678{ 679 apr_hash_t *targets = apr_hash_make(pool); 680 svn_fs_lock_target_t *target = svn_fs_lock_target_create(token, current_rev, 681 pool); 682 svn_error_t *err; 683 struct lock_baton_t baton = {0}; 684 685 svn_hash_sets(targets, path, target); 686 687 err = svn_repos_fs_lock_many(repos, targets, comment, is_dav_comment, 688 expiration_date, steal_lock, lock_cb, &baton, 689 pool, pool); 690 691 if (baton.lock) 692 *lock = (svn_lock_t*)baton.lock; 693 694 if (err && baton.fs_err) 695 svn_error_compose(err, baton.fs_err); 696 else if (!err) 697 err = baton.fs_err; 698 699 return svn_error_trace(err); 700} 701 702 703svn_error_t * 704svn_repos_fs_unlock_many(svn_repos_t *repos, 705 apr_hash_t *targets, 706 svn_boolean_t break_lock, 707 svn_fs_lock_callback_t lock_callback, 708 void *lock_baton, 709 apr_pool_t *result_pool, 710 apr_pool_t *scratch_pool) 711{ 712 svn_error_t *err, *cb_err = SVN_NO_ERROR; 713 svn_fs_access_t *access_ctx = NULL; 714 const char *username = NULL; 715 apr_hash_t *hooks_env; 716 apr_hash_t *pre_targets = apr_hash_make(scratch_pool); 717 apr_hash_index_t *hi; 718 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 719 struct lock_many_baton_t baton; 720 721 if (!apr_hash_count(targets)) 722 return SVN_NO_ERROR; 723 724 /* Parse the hooks-env file (if any). */ 725 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 726 scratch_pool, scratch_pool)); 727 728 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs)); 729 if (access_ctx) 730 SVN_ERR(svn_fs_access_get_username(&username, access_ctx)); 731 732 if (! break_lock && ! username) 733 return svn_error_create 734 (SVN_ERR_FS_NO_USER, NULL, 735 _("Cannot unlock, no authenticated username available")); 736 737 /* Run pre-unlock hook. This could throw error, preventing 738 svn_fs_unlock_many() from happening for that path. */ 739 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 740 { 741 const char *path = apr_hash_this_key(hi); 742 const char *token = apr_hash_this_val(hi); 743 744 svn_pool_clear(iterpool); 745 746 err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token, 747 break_lock, iterpool); 748 if (err) 749 { 750 if (!cb_err && lock_callback) 751 cb_err = lock_callback(lock_baton, path, NULL, err, iterpool); 752 svn_error_clear(err); 753 754 continue; 755 } 756 757 svn_hash_sets(pre_targets, path, token); 758 } 759 760 if (!apr_hash_count(pre_targets)) 761 return svn_error_trace(cb_err); 762 763 baton.need_lock = FALSE; 764 baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets), 765 sizeof(const char *)); 766 baton.lock_callback = lock_callback; 767 baton.lock_baton = lock_baton; 768 baton.cb_err = cb_err; 769 baton.pool = scratch_pool; 770 771 err = svn_fs_unlock_many(repos->fs, pre_targets, break_lock, 772 lock_many_cb, &baton, result_pool, iterpool); 773 774 /* If there are 'unlocks' run the post-unlock even if there is an error. */ 775 if (baton.paths->nelts) 776 { 777 svn_error_t *perr = svn_repos__hooks_post_unlock(repos, hooks_env, 778 baton.paths, 779 username, iterpool); 780 if (perr) 781 { 782 perr = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, perr, 783 _("Unlock succeeded, but post-unlock hook failed")); 784 err = svn_error_compose_create(err, perr); 785 } 786 } 787 788 svn_pool_destroy(iterpool); 789 790 if (err && cb_err) 791 svn_error_compose(err, cb_err); 792 else if (!err) 793 err = cb_err; 794 795 return svn_error_trace(err); 796} 797 798svn_error_t * 799svn_repos_fs_unlock(svn_repos_t *repos, 800 const char *path, 801 const char *token, 802 svn_boolean_t break_lock, 803 apr_pool_t *pool) 804{ 805 apr_hash_t *targets = apr_hash_make(pool); 806 svn_error_t *err; 807 struct lock_baton_t baton = {0}; 808 809 if (!token) 810 token = ""; 811 812 svn_hash_sets(targets, path, token); 813 814 err = svn_repos_fs_unlock_many(repos, targets, break_lock, lock_cb, &baton, 815 pool, pool); 816 817 if (err && baton.fs_err) 818 svn_error_compose(err, baton.fs_err); 819 else if (!err) 820 err = baton.fs_err; 821 822 return svn_error_trace(err); 823} 824 825 826struct get_locks_baton_t 827{ 828 svn_fs_t *fs; 829 svn_fs_root_t *head_root; 830 svn_repos_authz_func_t authz_read_func; 831 void *authz_read_baton; 832 apr_hash_t *locks; 833}; 834 835 836/* This implements the svn_fs_get_locks_callback_t interface. */ 837static svn_error_t * 838get_locks_callback(void *baton, 839 svn_lock_t *lock, 840 apr_pool_t *pool) 841{ 842 struct get_locks_baton_t *b = baton; 843 svn_boolean_t readable = TRUE; 844 apr_pool_t *hash_pool = apr_hash_pool_get(b->locks); 845 846 /* If there's auth to deal with, deal with it. */ 847 if (b->authz_read_func) 848 SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path, 849 b->authz_read_baton, pool)); 850 851 /* If we can read this lock path, add the lock to the return hash. */ 852 if (readable) 853 svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path), 854 svn_lock_dup(lock, hash_pool)); 855 856 return SVN_NO_ERROR; 857} 858 859 860svn_error_t * 861svn_repos_fs_get_locks2(apr_hash_t **locks, 862 svn_repos_t *repos, 863 const char *path, 864 svn_depth_t depth, 865 svn_repos_authz_func_t authz_read_func, 866 void *authz_read_baton, 867 apr_pool_t *pool) 868{ 869 apr_hash_t *all_locks = apr_hash_make(pool); 870 svn_revnum_t head_rev; 871 struct get_locks_baton_t baton; 872 873 SVN_ERR_ASSERT((depth == svn_depth_empty) || 874 (depth == svn_depth_files) || 875 (depth == svn_depth_immediates) || 876 (depth == svn_depth_infinity)); 877 878 SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool)); 879 880 /* Populate our callback baton. */ 881 baton.fs = repos->fs; 882 baton.locks = all_locks; 883 baton.authz_read_func = authz_read_func; 884 baton.authz_read_baton = authz_read_baton; 885 SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs, 886 head_rev, pool)); 887 888 /* Get all the locks. */ 889 SVN_ERR(svn_fs_get_locks2(repos->fs, path, depth, 890 get_locks_callback, &baton, pool)); 891 892 *locks = baton.locks; 893 return SVN_NO_ERROR; 894} 895 896 897svn_error_t * 898svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo, 899 svn_repos_t *repos, 900 const apr_array_header_t *paths, 901 svn_revnum_t rev, 902 svn_mergeinfo_inheritance_t inherit, 903 svn_boolean_t include_descendants, 904 svn_repos_authz_func_t authz_read_func, 905 void *authz_read_baton, 906 apr_pool_t *pool) 907{ 908 /* Here we cast away 'const', but won't try to write through this pointer 909 * without first allocating a new array. */ 910 apr_array_header_t *readable_paths = (apr_array_header_t *) paths; 911 svn_fs_root_t *root; 912 apr_pool_t *iterpool = svn_pool_create(pool); 913 914 if (!SVN_IS_VALID_REVNUM(rev)) 915 SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool)); 916 SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool)); 917 918 /* Filter out unreadable paths before divining merge tracking info. */ 919 if (authz_read_func) 920 { 921 int i; 922 923 for (i = 0; i < paths->nelts; i++) 924 { 925 svn_boolean_t readable; 926 const char *path = APR_ARRAY_IDX(paths, i, char *); 927 svn_pool_clear(iterpool); 928 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, 929 iterpool)); 930 if (readable && readable_paths != paths) 931 APR_ARRAY_PUSH(readable_paths, const char *) = path; 932 else if (!readable && readable_paths == paths) 933 { 934 /* Requested paths differ from readable paths. Fork 935 list of readable paths from requested paths. */ 936 int j; 937 readable_paths = apr_array_make(pool, paths->nelts - 1, 938 sizeof(char *)); 939 for (j = 0; j < i; j++) 940 { 941 path = APR_ARRAY_IDX(paths, j, char *); 942 APR_ARRAY_PUSH(readable_paths, const char *) = path; 943 } 944 } 945 } 946 } 947 948 /* We consciously do not perform authz checks on the paths returned 949 in *MERGEINFO, avoiding massive authz overhead which would allow 950 us to protect the name of where a change was merged from, but not 951 the change itself. */ 952 /* ### TODO(reint): ... but how about descendant merged-to paths? */ 953 if (readable_paths->nelts > 0) 954 SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit, 955 include_descendants, TRUE, pool, pool)); 956 else 957 *mergeinfo = apr_hash_make(pool); 958 959 svn_pool_destroy(iterpool); 960 return SVN_NO_ERROR; 961} 962 963struct pack_notify_baton 964{ 965 svn_repos_notify_func_t notify_func; 966 void *notify_baton; 967}; 968 969/* Implements svn_fs_pack_notify_t. */ 970static svn_error_t * 971pack_notify_func(void *baton, 972 apr_int64_t shard, 973 svn_fs_pack_notify_action_t pack_action, 974 apr_pool_t *pool) 975{ 976 struct pack_notify_baton *pnb = baton; 977 svn_repos_notify_t *notify; 978 979 /* Simple conversion works for these values. */ 980 SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start 981 && pack_action <= svn_fs_pack_notify_end_revprop); 982 983 notify = svn_repos_notify_create(pack_action 984 + svn_repos_notify_pack_shard_start 985 - svn_fs_pack_notify_start, 986 pool); 987 notify->shard = shard; 988 pnb->notify_func(pnb->notify_baton, notify, pool); 989 990 return SVN_NO_ERROR; 991} 992 993svn_error_t * 994svn_repos_fs_pack2(svn_repos_t *repos, 995 svn_repos_notify_func_t notify_func, 996 void *notify_baton, 997 svn_cancel_func_t cancel_func, 998 void *cancel_baton, 999 apr_pool_t *pool) 1000{ 1001 struct pack_notify_baton pnb; 1002 1003 pnb.notify_func = notify_func; 1004 pnb.notify_baton = notify_baton; 1005 1006 return svn_fs_pack(repos->db_path, 1007 notify_func ? pack_notify_func : NULL, 1008 notify_func ? &pnb : NULL, 1009 cancel_func, cancel_baton, pool); 1010} 1011 1012svn_error_t * 1013svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p, 1014 svn_fs_root_t *root, 1015 const char *path, 1016 const char *propname, 1017 svn_repos_authz_func_t authz_read_func, 1018 void *authz_read_baton, 1019 apr_pool_t *result_pool, 1020 apr_pool_t *scratch_pool) 1021{ 1022 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1023 apr_array_header_t *inherited_props; 1024 const char *parent_path = path; 1025 1026 inherited_props = apr_array_make(result_pool, 1, 1027 sizeof(svn_prop_inherited_item_t *)); 1028 while (!(parent_path[0] == '/' && parent_path[1] == '\0')) 1029 { 1030 svn_boolean_t allowed = TRUE; 1031 apr_hash_t *parent_properties = NULL; 1032 1033 svn_pool_clear(iterpool); 1034 parent_path = svn_fspath__dirname(parent_path, scratch_pool); 1035 1036 if (authz_read_func) 1037 SVN_ERR(authz_read_func(&allowed, root, parent_path, 1038 authz_read_baton, iterpool)); 1039 if (allowed) 1040 { 1041 if (propname) 1042 { 1043 svn_string_t *propval; 1044 1045 SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname, 1046 result_pool)); 1047 if (propval) 1048 { 1049 parent_properties = apr_hash_make(result_pool); 1050 svn_hash_sets(parent_properties, propname, propval); 1051 } 1052 } 1053 else 1054 { 1055 SVN_ERR(svn_fs_node_proplist(&parent_properties, root, 1056 parent_path, result_pool)); 1057 } 1058 1059 if (parent_properties && apr_hash_count(parent_properties)) 1060 { 1061 svn_prop_inherited_item_t *i_props = 1062 apr_pcalloc(result_pool, sizeof(*i_props)); 1063 i_props->path_or_url = 1064 apr_pstrdup(result_pool, parent_path + 1); 1065 i_props->prop_hash = parent_properties; 1066 /* Build the output array in depth-first order. */ 1067 svn_sort__array_insert(inherited_props, &i_props, 0); 1068 } 1069 } 1070 } 1071 1072 svn_pool_destroy(iterpool); 1073 1074 *inherited_props_p = inherited_props; 1075 return SVN_NO_ERROR; 1076} 1077 1078/* 1079 * vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq 1080 * vim:isk=a-z,A-Z,48-57,_,.,-,> 1081 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0 1082 */ 1083