revs-txns.c revision 299742
1/* revs-txns.c : operations on revision and transactions 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 <string.h> 24 25#include <apr_tables.h> 26#include <apr_pools.h> 27 28#include "svn_pools.h" 29#include "svn_time.h" 30#include "svn_fs.h" 31#include "svn_props.h" 32#include "svn_hash.h" 33#include "svn_io.h" 34 35#include "fs.h" 36#include "dag.h" 37#include "err.h" 38#include "trail.h" 39#include "tree.h" 40#include "revs-txns.h" 41#include "key-gen.h" 42#include "id.h" 43#include "bdb/rev-table.h" 44#include "bdb/txn-table.h" 45#include "bdb/copies-table.h" 46#include "bdb/changes-table.h" 47#include "../libsvn_fs/fs-loader.h" 48 49#include "svn_private_config.h" 50#include "private/svn_fs_util.h" 51 52 53/*** Helpers ***/ 54 55/* Set *txn_p to a transaction object allocated in POOL for the 56 transaction in FS whose id is TXN_ID. If EXPECT_DEAD is set, this 57 transaction must be a dead one, else an error is returned. If 58 EXPECT_DEAD is not set, the transaction must *not* be a dead one, 59 else an error is returned. */ 60static svn_error_t * 61get_txn(transaction_t **txn_p, 62 svn_fs_t *fs, 63 const char *txn_id, 64 svn_boolean_t expect_dead, 65 trail_t *trail, 66 apr_pool_t *pool) 67{ 68 transaction_t *txn; 69 SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_id, trail, pool)); 70 if (expect_dead && (txn->kind != transaction_kind_dead)) 71 return svn_error_createf(SVN_ERR_FS_TRANSACTION_NOT_DEAD, 0, 72 _("Transaction is not dead: '%s'"), txn_id); 73 if ((! expect_dead) && (txn->kind == transaction_kind_dead)) 74 return svn_error_createf(SVN_ERR_FS_TRANSACTION_DEAD, 0, 75 _("Transaction is dead: '%s'"), txn_id); 76 *txn_p = txn; 77 return SVN_NO_ERROR; 78} 79 80 81/* This is only for symmetry with the get_txn() helper. */ 82#define put_txn svn_fs_bdb__put_txn 83 84 85 86/*** Revisions ***/ 87 88/* Return the committed transaction record *TXN_P and its ID *TXN_ID 89 (as long as those parameters aren't NULL) for the revision REV in 90 FS as part of TRAIL. */ 91static svn_error_t * 92get_rev_txn(transaction_t **txn_p, 93 const char **txn_id, 94 svn_fs_t *fs, 95 svn_revnum_t rev, 96 trail_t *trail, 97 apr_pool_t *pool) 98{ 99 revision_t *revision; 100 transaction_t *txn; 101 102 SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool)); 103 if (revision->txn_id == NULL) 104 return svn_fs_base__err_corrupt_fs_revision(fs, rev); 105 106 SVN_ERR(get_txn(&txn, fs, revision->txn_id, FALSE, trail, pool)); 107 if (txn->revision != rev) 108 return svn_fs_base__err_corrupt_txn(fs, revision->txn_id); 109 110 if (txn_p) 111 *txn_p = txn; 112 if (txn_id) 113 *txn_id = revision->txn_id; 114 return SVN_NO_ERROR; 115} 116 117 118svn_error_t * 119svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p, 120 svn_fs_t *fs, 121 svn_revnum_t rev, 122 trail_t *trail, 123 apr_pool_t *pool) 124{ 125 transaction_t *txn; 126 127 SVN_ERR(get_rev_txn(&txn, NULL, fs, rev, trail, pool)); 128 if (txn->root_id == NULL) 129 return svn_fs_base__err_corrupt_fs_revision(fs, rev); 130 131 *root_id_p = txn->root_id; 132 return SVN_NO_ERROR; 133} 134 135 136svn_error_t * 137svn_fs_base__rev_get_txn_id(const char **txn_id_p, 138 svn_fs_t *fs, 139 svn_revnum_t rev, 140 trail_t *trail, 141 apr_pool_t *pool) 142{ 143 revision_t *revision; 144 145 SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool)); 146 if (revision->txn_id == NULL) 147 return svn_fs_base__err_corrupt_fs_revision(fs, rev); 148 149 *txn_id_p = revision->txn_id; 150 return SVN_NO_ERROR; 151} 152 153 154static svn_error_t * 155txn_body_youngest_rev(void *baton, trail_t *trail) 156{ 157 return svn_fs_bdb__youngest_rev(baton, trail->fs, trail, trail->pool); 158} 159 160 161svn_error_t * 162svn_fs_base__youngest_rev(svn_revnum_t *youngest_p, 163 svn_fs_t *fs, 164 apr_pool_t *pool) 165{ 166 svn_revnum_t youngest; 167 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 168 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_youngest_rev, &youngest, 169 TRUE, pool)); 170 *youngest_p = youngest; 171 return SVN_NO_ERROR; 172} 173 174 175struct revision_proplist_args { 176 apr_hash_t **table_p; 177 svn_revnum_t rev; 178}; 179 180 181static svn_error_t * 182txn_body_revision_proplist(void *baton, trail_t *trail) 183{ 184 struct revision_proplist_args *args = baton; 185 transaction_t *txn; 186 187 SVN_ERR(get_rev_txn(&txn, NULL, trail->fs, args->rev, trail, trail->pool)); 188 *(args->table_p) = txn->proplist; 189 return SVN_NO_ERROR; 190} 191 192 193svn_error_t * 194svn_fs_base__revision_proplist(apr_hash_t **table_p, 195 svn_fs_t *fs, 196 svn_revnum_t rev, 197 apr_pool_t *pool) 198{ 199 struct revision_proplist_args args; 200 apr_hash_t *table; 201 202 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 203 204 args.table_p = &table; 205 args.rev = rev; 206 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args, 207 FALSE, pool)); 208 209 *table_p = table ? table : apr_hash_make(pool); 210 return SVN_NO_ERROR; 211} 212 213 214svn_error_t * 215svn_fs_base__revision_prop(svn_string_t **value_p, 216 svn_fs_t *fs, 217 svn_revnum_t rev, 218 const char *propname, 219 apr_pool_t *pool) 220{ 221 struct revision_proplist_args args; 222 apr_hash_t *table; 223 224 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 225 226 /* Get the proplist. */ 227 args.table_p = &table; 228 args.rev = rev; 229 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args, 230 FALSE, pool)); 231 232 /* And then the prop from that list (if there was a list). */ 233 *value_p = svn_hash_gets(table, propname); 234 235 return SVN_NO_ERROR; 236} 237 238 239svn_error_t * 240svn_fs_base__set_rev_prop(svn_fs_t *fs, 241 svn_revnum_t rev, 242 const char *name, 243 const svn_string_t *const *old_value_p, 244 const svn_string_t *value, 245 trail_t *trail, 246 apr_pool_t *pool) 247{ 248 transaction_t *txn; 249 const char *txn_id; 250 251 SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool)); 252 253 /* If there's no proplist, but we're just deleting a property, exit now. */ 254 if ((! txn->proplist) && (! value)) 255 return SVN_NO_ERROR; 256 257 /* Now, if there's no proplist, we know we need to make one. */ 258 if (! txn->proplist) 259 txn->proplist = apr_hash_make(pool); 260 261 /* Set the property. */ 262 if (old_value_p) 263 { 264 const svn_string_t *wanted_value = *old_value_p; 265 const svn_string_t *present_value = svn_hash_gets(txn->proplist, name); 266 if ((!wanted_value != !present_value) 267 || (wanted_value && present_value 268 && !svn_string_compare(wanted_value, present_value))) 269 { 270 /* What we expected isn't what we found. */ 271 return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, 272 _("revprop '%s' has unexpected value in " 273 "filesystem"), 274 name); 275 } 276 /* Fall through. */ 277 } 278 svn_hash_sets(txn->proplist, name, value); 279 280 /* Overwrite the revision. */ 281 return put_txn(fs, txn, txn_id, trail, pool); 282} 283 284 285struct change_rev_prop_args { 286 svn_revnum_t rev; 287 const char *name; 288 const svn_string_t *const *old_value_p; 289 const svn_string_t *value; 290}; 291 292 293static svn_error_t * 294txn_body_change_rev_prop(void *baton, trail_t *trail) 295{ 296 struct change_rev_prop_args *args = baton; 297 298 return svn_fs_base__set_rev_prop(trail->fs, args->rev, 299 args->name, args->old_value_p, args->value, 300 trail, trail->pool); 301} 302 303 304svn_error_t * 305svn_fs_base__change_rev_prop(svn_fs_t *fs, 306 svn_revnum_t rev, 307 const char *name, 308 const svn_string_t *const *old_value_p, 309 const svn_string_t *value, 310 apr_pool_t *pool) 311{ 312 struct change_rev_prop_args args; 313 314 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 315 316 args.rev = rev; 317 args.name = name; 318 args.old_value_p = old_value_p; 319 args.value = value; 320 return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args, 321 TRUE, pool); 322} 323 324 325 326/*** Transactions ***/ 327 328svn_error_t * 329svn_fs_base__txn_make_committed(svn_fs_t *fs, 330 const char *txn_name, 331 svn_revnum_t revision, 332 trail_t *trail, 333 apr_pool_t *pool) 334{ 335 transaction_t *txn; 336 337 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 338 339 /* Make sure the TXN is not committed already. */ 340 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 341 if (txn->kind != transaction_kind_normal) 342 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 343 344 /* Convert TXN to a committed transaction. */ 345 txn->base_id = NULL; 346 txn->revision = revision; 347 txn->kind = transaction_kind_committed; 348 return put_txn(fs, txn, txn_name, trail, pool); 349} 350 351 352svn_error_t * 353svn_fs_base__txn_get_revision(svn_revnum_t *revision, 354 svn_fs_t *fs, 355 const char *txn_name, 356 trail_t *trail, 357 apr_pool_t *pool) 358{ 359 transaction_t *txn; 360 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 361 *revision = txn->revision; 362 return SVN_NO_ERROR; 363} 364 365 366svn_error_t * 367svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p, 368 const svn_fs_id_t **base_root_id_p, 369 svn_fs_t *fs, 370 const char *txn_name, 371 trail_t *trail, 372 apr_pool_t *pool) 373{ 374 transaction_t *txn; 375 376 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 377 if (txn->kind != transaction_kind_normal) 378 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 379 380 *root_id_p = txn->root_id; 381 *base_root_id_p = txn->base_id; 382 return SVN_NO_ERROR; 383} 384 385 386svn_error_t * 387svn_fs_base__set_txn_root(svn_fs_t *fs, 388 const char *txn_name, 389 const svn_fs_id_t *new_id, 390 trail_t *trail, 391 apr_pool_t *pool) 392{ 393 transaction_t *txn; 394 395 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 396 if (txn->kind != transaction_kind_normal) 397 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 398 399 if (! svn_fs_base__id_eq(txn->root_id, new_id)) 400 { 401 txn->root_id = new_id; 402 SVN_ERR(put_txn(fs, txn, txn_name, trail, pool)); 403 } 404 return SVN_NO_ERROR; 405} 406 407 408svn_error_t * 409svn_fs_base__set_txn_base(svn_fs_t *fs, 410 const char *txn_name, 411 const svn_fs_id_t *new_id, 412 trail_t *trail, 413 apr_pool_t *pool) 414{ 415 transaction_t *txn; 416 417 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 418 if (txn->kind != transaction_kind_normal) 419 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 420 421 if (! svn_fs_base__id_eq(txn->base_id, new_id)) 422 { 423 txn->base_id = new_id; 424 SVN_ERR(put_txn(fs, txn, txn_name, trail, pool)); 425 } 426 return SVN_NO_ERROR; 427} 428 429 430svn_error_t * 431svn_fs_base__add_txn_copy(svn_fs_t *fs, 432 const char *txn_name, 433 const char *copy_id, 434 trail_t *trail, 435 apr_pool_t *pool) 436{ 437 transaction_t *txn; 438 439 /* Get the transaction and ensure its mutability. */ 440 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 441 if (txn->kind != transaction_kind_normal) 442 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 443 444 /* Allocate a new array if this transaction has no copies. */ 445 if (! txn->copies) 446 txn->copies = apr_array_make(pool, 1, sizeof(copy_id)); 447 448 /* Add COPY_ID to the array. */ 449 APR_ARRAY_PUSH(txn->copies, const char *) = copy_id; 450 451 /* Finally, write out the transaction. */ 452 return put_txn(fs, txn, txn_name, trail, pool); 453} 454 455 456 457/* Generic transaction operations. */ 458 459struct txn_proplist_args { 460 apr_hash_t **table_p; 461 const char *id; 462}; 463 464 465static svn_error_t * 466txn_body_txn_proplist(void *baton, trail_t *trail) 467{ 468 transaction_t *txn; 469 struct txn_proplist_args *args = baton; 470 471 SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool)); 472 if (txn->kind != transaction_kind_normal) 473 return svn_fs_base__err_txn_not_mutable(trail->fs, args->id); 474 475 *(args->table_p) = txn->proplist; 476 return SVN_NO_ERROR; 477} 478 479 480 481svn_error_t * 482svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p, 483 const char *txn_id, 484 trail_t *trail) 485{ 486 struct txn_proplist_args args; 487 apr_hash_t *table; 488 489 args.table_p = &table; 490 args.id = txn_id; 491 SVN_ERR(txn_body_txn_proplist(&args, trail)); 492 493 *table_p = table ? table : apr_hash_make(trail->pool); 494 return SVN_NO_ERROR; 495} 496 497 498 499svn_error_t * 500svn_fs_base__txn_proplist(apr_hash_t **table_p, 501 svn_fs_txn_t *txn, 502 apr_pool_t *pool) 503{ 504 struct txn_proplist_args args; 505 apr_hash_t *table; 506 svn_fs_t *fs = txn->fs; 507 508 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 509 510 args.table_p = &table; 511 args.id = txn->id; 512 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args, 513 FALSE, pool)); 514 515 *table_p = table ? table : apr_hash_make(pool); 516 return SVN_NO_ERROR; 517} 518 519 520svn_error_t * 521svn_fs_base__txn_prop(svn_string_t **value_p, 522 svn_fs_txn_t *txn, 523 const char *propname, 524 apr_pool_t *pool) 525{ 526 struct txn_proplist_args args; 527 apr_hash_t *table; 528 svn_fs_t *fs = txn->fs; 529 530 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 531 532 /* Get the proplist. */ 533 args.table_p = &table; 534 args.id = txn->id; 535 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args, 536 FALSE, pool)); 537 538 /* And then the prop from that list (if there was a list). */ 539 *value_p = svn_hash_gets(table, propname); 540 541 return SVN_NO_ERROR; 542} 543 544 545 546struct change_txn_prop_args { 547 svn_fs_t *fs; 548 const char *id; 549 const char *name; 550 const svn_string_t *value; 551}; 552 553 554svn_error_t * 555svn_fs_base__set_txn_prop(svn_fs_t *fs, 556 const char *txn_name, 557 const char *name, 558 const svn_string_t *value, 559 trail_t *trail, 560 apr_pool_t *pool) 561{ 562 transaction_t *txn; 563 564 SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); 565 if (txn->kind != transaction_kind_normal) 566 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 567 568 /* If there's no proplist, but we're just deleting a property, exit now. */ 569 if ((! txn->proplist) && (! value)) 570 return SVN_NO_ERROR; 571 572 /* Now, if there's no proplist, we know we need to make one. */ 573 if (! txn->proplist) 574 txn->proplist = apr_hash_make(pool); 575 576 /* Set the property. */ 577 if (svn_hash_gets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE) 578 && !strcmp(name, SVN_PROP_REVISION_DATE)) 579 svn_hash_sets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE, 580 svn_string_create("1", pool)); 581 svn_hash_sets(txn->proplist, name, value); 582 583 /* Now overwrite the transaction. */ 584 return put_txn(fs, txn, txn_name, trail, pool); 585} 586 587 588static svn_error_t * 589txn_body_change_txn_prop(void *baton, trail_t *trail) 590{ 591 struct change_txn_prop_args *args = baton; 592 return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name, 593 args->value, trail, trail->pool); 594} 595 596 597svn_error_t * 598svn_fs_base__change_txn_prop(svn_fs_txn_t *txn, 599 const char *name, 600 const svn_string_t *value, 601 apr_pool_t *pool) 602{ 603 struct change_txn_prop_args args; 604 svn_fs_t *fs = txn->fs; 605 606 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 607 608 args.id = txn->id; 609 args.name = name; 610 args.value = value; 611 return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args, 612 TRUE, pool); 613} 614 615 616svn_error_t * 617svn_fs_base__change_txn_props(svn_fs_txn_t *txn, 618 const apr_array_header_t *props, 619 apr_pool_t *pool) 620{ 621 apr_pool_t *iterpool = svn_pool_create(pool); 622 int i; 623 624 for (i = 0; i < props->nelts; i++) 625 { 626 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 627 628 svn_pool_clear(iterpool); 629 630 SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name, 631 prop->value, iterpool)); 632 } 633 svn_pool_destroy(iterpool); 634 635 return SVN_NO_ERROR; 636} 637 638 639/* Creating a transaction */ 640 641static txn_vtable_t txn_vtable = { 642 svn_fs_base__commit_txn, 643 svn_fs_base__abort_txn, 644 svn_fs_base__txn_prop, 645 svn_fs_base__txn_proplist, 646 svn_fs_base__change_txn_prop, 647 svn_fs_base__txn_root, 648 svn_fs_base__change_txn_props 649}; 650 651 652/* Allocate and return a new transaction object in POOL for FS whose 653 transaction ID is ID. ID is not copied. */ 654static svn_fs_txn_t * 655make_txn(svn_fs_t *fs, 656 const char *id, 657 svn_revnum_t base_rev, 658 apr_pool_t *pool) 659{ 660 svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn)); 661 662 txn->fs = fs; 663 txn->id = id; 664 txn->base_rev = base_rev; 665 txn->vtable = &txn_vtable; 666 txn->fsap_data = NULL; 667 668 return txn; 669} 670 671 672struct begin_txn_args 673{ 674 svn_fs_txn_t **txn_p; 675 svn_revnum_t base_rev; 676 apr_uint32_t flags; 677}; 678 679 680static svn_error_t * 681txn_body_begin_txn(void *baton, trail_t *trail) 682{ 683 struct begin_txn_args *args = baton; 684 const svn_fs_id_t *root_id; 685 const char *txn_id; 686 687 SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev, 688 trail, trail->pool)); 689 SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id, 690 trail, trail->pool)); 691 692 if (args->flags & SVN_FS_TXN_CHECK_OOD) 693 { 694 struct change_txn_prop_args cpargs; 695 cpargs.fs = trail->fs; 696 cpargs.id = txn_id; 697 cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD; 698 cpargs.value = svn_string_create("true", trail->pool); 699 700 SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); 701 } 702 703 if (args->flags & SVN_FS_TXN_CHECK_LOCKS) 704 { 705 struct change_txn_prop_args cpargs; 706 cpargs.fs = trail->fs; 707 cpargs.id = txn_id; 708 cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 709 cpargs.value = svn_string_create("true", trail->pool); 710 711 SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); 712 } 713 714 /* Put a datestamp on the newly created txn, so we always know 715 exactly how old it is. (This will help sysadmins identify 716 long-abandoned txns that may need to be manually removed.) Do 717 this before setting CLIENT_DATE so that it is not recorded as an 718 explicit setting. */ 719 { 720 struct change_txn_prop_args cpargs; 721 svn_string_t date; 722 cpargs.fs = trail->fs; 723 cpargs.id = txn_id; 724 cpargs.name = SVN_PROP_REVISION_DATE; 725 date.data = svn_time_to_cstring(apr_time_now(), trail->pool); 726 date.len = strlen(date.data); 727 cpargs.value = &date; 728 SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); 729 } 730 731 if (args->flags & SVN_FS_TXN_CLIENT_DATE) 732 { 733 struct change_txn_prop_args cpargs; 734 cpargs.fs = trail->fs; 735 cpargs.id = txn_id; 736 cpargs.name = SVN_FS__PROP_TXN_CLIENT_DATE; 737 cpargs.value = svn_string_create("0", trail->pool); 738 739 SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); 740 } 741 742 *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool); 743 return SVN_NO_ERROR; 744} 745 746/* Note: it is acceptable for this function to call back into 747 public FS API interfaces because it does not itself use trails. */ 748svn_error_t * 749svn_fs_base__begin_txn(svn_fs_txn_t **txn_p, 750 svn_fs_t *fs, 751 svn_revnum_t rev, 752 apr_uint32_t flags, 753 apr_pool_t *pool) 754{ 755 svn_fs_txn_t *txn; 756 struct begin_txn_args args; 757 758 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 759 760 args.txn_p = &txn; 761 args.base_rev = rev; 762 args.flags = flags; 763 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool)); 764 765 *txn_p = txn; 766 767 return SVN_NO_ERROR; 768} 769 770 771struct open_txn_args 772{ 773 svn_fs_txn_t **txn_p; 774 const char *name; 775}; 776 777 778static svn_error_t * 779txn_body_open_txn(void *baton, trail_t *trail) 780{ 781 struct open_txn_args *args = baton; 782 transaction_t *fstxn; 783 svn_revnum_t base_rev = SVN_INVALID_REVNUM; 784 const char *txn_id; 785 786 SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool)); 787 if (fstxn->kind != transaction_kind_committed) 788 { 789 txn_id = svn_fs_base__id_txn_id(fstxn->base_id); 790 SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id, 791 trail, trail->pool)); 792 } 793 794 *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool); 795 return SVN_NO_ERROR; 796} 797 798 799svn_error_t * 800svn_fs_base__open_txn(svn_fs_txn_t **txn_p, 801 svn_fs_t *fs, 802 const char *name, 803 apr_pool_t *pool) 804{ 805 svn_fs_txn_t *txn; 806 struct open_txn_args args; 807 808 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 809 810 args.txn_p = &txn; 811 args.name = name; 812 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool)); 813 814 *txn_p = txn; 815 return SVN_NO_ERROR; 816} 817 818 819struct cleanup_txn_args 820{ 821 transaction_t **txn_p; 822 const char *name; 823}; 824 825 826static svn_error_t * 827txn_body_cleanup_txn(void *baton, trail_t *trail) 828{ 829 struct cleanup_txn_args *args = baton; 830 return get_txn(args->txn_p, trail->fs, args->name, TRUE, 831 trail, trail->pool); 832} 833 834 835static svn_error_t * 836txn_body_cleanup_txn_copy(void *baton, trail_t *trail) 837{ 838 const char *copy_id = *(const char **)baton; 839 svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail, 840 trail->pool); 841 842 /* Copy doesn't exist? No sweat. */ 843 if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY)) 844 { 845 svn_error_clear(err); 846 err = SVN_NO_ERROR; 847 } 848 return svn_error_trace(err); 849} 850 851 852static svn_error_t * 853txn_body_cleanup_txn_changes(void *baton, trail_t *trail) 854{ 855 const char *key = *(const char **)baton; 856 857 return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool); 858} 859 860 861struct get_dirents_args 862{ 863 apr_hash_t **dirents; 864 const svn_fs_id_t *id; 865 const char *txn_id; 866}; 867 868 869static svn_error_t * 870txn_body_get_dirents(void *baton, trail_t *trail) 871{ 872 struct get_dirents_args *args = baton; 873 dag_node_t *node; 874 875 /* Get the node. */ 876 SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id, 877 trail, trail->pool)); 878 879 /* If immutable, do nothing and return. */ 880 if (! svn_fs_base__dag_check_mutable(node, args->txn_id)) 881 return SVN_NO_ERROR; 882 883 /* If a directory, do nothing and return. */ 884 *(args->dirents) = NULL; 885 if (svn_fs_base__dag_node_kind(node) != svn_node_dir) 886 return SVN_NO_ERROR; 887 888 /* Else it's mutable. Get its dirents. */ 889 return svn_fs_base__dag_dir_entries(args->dirents, node, 890 trail, trail->pool); 891} 892 893 894struct remove_node_args 895{ 896 const svn_fs_id_t *id; 897 const char *txn_id; 898}; 899 900 901static svn_error_t * 902txn_body_remove_node(void *baton, trail_t *trail) 903{ 904 struct remove_node_args *args = baton; 905 return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id, 906 trail, trail->pool); 907} 908 909 910static svn_error_t * 911delete_txn_tree(svn_fs_t *fs, 912 const svn_fs_id_t *id, 913 const char *txn_id, 914 apr_pool_t *pool) 915{ 916 struct get_dirents_args dirent_args; 917 struct remove_node_args rm_args; 918 apr_hash_t *dirents = NULL; 919 apr_hash_index_t *hi; 920 svn_error_t *err; 921 922 /* If this sucker isn't mutable, there's nothing to do. */ 923 if (strcmp(svn_fs_base__id_txn_id(id), txn_id) != 0) 924 return SVN_NO_ERROR; 925 926 /* See if the thing has dirents that need to be recursed upon. If 927 you can't find the thing itself, don't sweat it. We probably 928 already cleaned it up. */ 929 dirent_args.dirents = &dirents; 930 dirent_args.id = id; 931 dirent_args.txn_id = txn_id; 932 err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args, 933 FALSE, pool); 934 if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND)) 935 { 936 svn_error_clear(err); 937 return SVN_NO_ERROR; 938 } 939 SVN_ERR(err); 940 941 /* If there are dirents upon which to recurse ... recurse. */ 942 if (dirents) 943 { 944 apr_pool_t *subpool = svn_pool_create(pool); 945 946 /* Loop over hash entries */ 947 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 948 { 949 void *val; 950 svn_fs_dirent_t *dirent; 951 952 svn_pool_clear(subpool); 953 apr_hash_this(hi, NULL, NULL, &val); 954 dirent = val; 955 SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool)); 956 } 957 svn_pool_destroy(subpool); 958 } 959 960 /* Remove the node. */ 961 rm_args.id = id; 962 rm_args.txn_id = txn_id; 963 return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args, 964 TRUE, pool); 965} 966 967 968static svn_error_t * 969txn_body_delete_txn(void *baton, trail_t *trail) 970{ 971 const char *txn_id = *(const char **)baton; 972 973 return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool); 974} 975 976 977svn_error_t * 978svn_fs_base__purge_txn(svn_fs_t *fs, 979 const char *txn_id, 980 apr_pool_t *pool) 981{ 982 struct cleanup_txn_args args; 983 transaction_t *txn; 984 985 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 986 987 /* Open the transaction, expecting it to be dead. */ 988 args.txn_p = &txn; 989 args.name = txn_id; 990 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args, 991 FALSE, pool)); 992 993 /* Delete the mutable portion of the tree hanging from the 994 transaction (which should gracefully recover if we've already 995 done this). */ 996 SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool)); 997 998 /* Kill the transaction's changes (which should gracefully recover 999 if...). */ 1000 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes, 1001 &txn_id, TRUE, pool)); 1002 1003 /* Kill the transaction's copies (which should gracefully...). */ 1004 if (txn->copies) 1005 { 1006 int i; 1007 1008 for (i = 0; i < txn->copies->nelts; i++) 1009 { 1010 SVN_ERR(svn_fs_base__retry_txn 1011 (fs, txn_body_cleanup_txn_copy, 1012 &APR_ARRAY_IDX(txn->copies, i, const char *), 1013 TRUE, pool)); 1014 } 1015 } 1016 1017 /* Kill the transaction itself (which ... just kidding -- this has 1018 no graceful failure mode). */ 1019 return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id, 1020 TRUE, pool); 1021} 1022 1023 1024static svn_error_t * 1025txn_body_abort_txn(void *baton, trail_t *trail) 1026{ 1027 svn_fs_txn_t *txn = baton; 1028 transaction_t *fstxn; 1029 1030 /* Get the transaction by its id, set it to "dead", and store the 1031 transaction. */ 1032 SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool)); 1033 if (fstxn->kind != transaction_kind_normal) 1034 return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id); 1035 1036 fstxn->kind = transaction_kind_dead; 1037 return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool); 1038} 1039 1040 1041svn_error_t * 1042svn_fs_base__abort_txn(svn_fs_txn_t *txn, 1043 apr_pool_t *pool) 1044{ 1045 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 1046 1047 /* Set the transaction to "dead". */ 1048 SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn, 1049 TRUE, pool)); 1050 1051 /* Now, purge it. */ 1052 SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool), 1053 _("Transaction aborted, but cleanup failed")); 1054 1055 return SVN_NO_ERROR; 1056} 1057 1058 1059struct list_transactions_args 1060{ 1061 apr_array_header_t **names_p; 1062 apr_pool_t *pool; 1063}; 1064 1065static svn_error_t * 1066txn_body_list_transactions(void* baton, trail_t *trail) 1067{ 1068 struct list_transactions_args *args = baton; 1069 return svn_fs_bdb__get_txn_list(args->names_p, trail->fs, 1070 trail, args->pool); 1071} 1072 1073svn_error_t * 1074svn_fs_base__list_transactions(apr_array_header_t **names_p, 1075 svn_fs_t *fs, 1076 apr_pool_t *pool) 1077{ 1078 apr_array_header_t *names; 1079 struct list_transactions_args args; 1080 1081 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1082 1083 args.names_p = &names; 1084 args.pool = pool; 1085 SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args, 1086 FALSE, pool)); 1087 1088 *names_p = names; 1089 return SVN_NO_ERROR; 1090} 1091