1/* transaction.c --- transaction-related functions of FSX 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 "transaction.h" 24 25#include <assert.h> 26#include <apr_sha1.h> 27 28#include "svn_hash.h" 29#include "svn_props.h" 30#include "svn_sorts.h" 31#include "svn_time.h" 32#include "svn_dirent_uri.h" 33 34#include "fs_x.h" 35#include "tree.h" 36#include "util.h" 37#include "id.h" 38#include "low_level.h" 39#include "temp_serializer.h" 40#include "cached_data.h" 41#include "lock.h" 42#include "rep-cache.h" 43#include "index.h" 44 45#include "private/svn_fs_util.h" 46#include "private/svn_fspath.h" 47#include "private/svn_sorts_private.h" 48#include "private/svn_string_private.h" 49#include "private/svn_subr_private.h" 50#include "private/svn_io_private.h" 51#include "../libsvn_fs/fs-loader.h" 52 53#include "svn_private_config.h" 54 55/* The vtable associated with an open transaction object. */ 56static txn_vtable_t txn_vtable = { 57 svn_fs_x__commit_txn, 58 svn_fs_x__abort_txn, 59 svn_fs_x__txn_prop, 60 svn_fs_x__txn_proplist, 61 svn_fs_x__change_txn_prop, 62 svn_fs_x__txn_root, 63 svn_fs_x__change_txn_props 64}; 65 66/* FSX-specific data being attached to svn_fs_txn_t. 67 */ 68typedef struct fs_txn_data_t 69{ 70 /* Strongly typed representation of the TXN's ID member. */ 71 svn_fs_x__txn_id_t txn_id; 72} fs_txn_data_t; 73 74svn_fs_x__txn_id_t 75svn_fs_x__txn_get_id(svn_fs_txn_t *txn) 76{ 77 fs_txn_data_t *ftd = txn->fsap_data; 78 return ftd->txn_id; 79} 80 81/* Functions for working with shared transaction data. */ 82 83/* Return the transaction object for transaction TXN_ID from the 84 transaction list of filesystem FS (which must already be locked via the 85 txn_list_lock mutex). If the transaction does not exist in the list, 86 then create a new transaction object and return it (if CREATE_NEW is 87 true) or return NULL (otherwise). */ 88static svn_fs_x__shared_txn_data_t * 89get_shared_txn(svn_fs_t *fs, 90 svn_fs_x__txn_id_t txn_id, 91 svn_boolean_t create_new) 92{ 93 svn_fs_x__data_t *ffd = fs->fsap_data; 94 svn_fs_x__shared_data_t *ffsd = ffd->shared; 95 svn_fs_x__shared_txn_data_t *txn; 96 97 for (txn = ffsd->txns; txn; txn = txn->next) 98 if (txn->txn_id == txn_id) 99 break; 100 101 if (txn || !create_new) 102 return txn; 103 104 /* Use the transaction object from the (single-object) freelist, 105 if one is available, or otherwise create a new object. */ 106 if (ffsd->free_txn) 107 { 108 txn = ffsd->free_txn; 109 ffsd->free_txn = NULL; 110 } 111 else 112 { 113 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool); 114 txn = apr_palloc(subpool, sizeof(*txn)); 115 txn->pool = subpool; 116 } 117 118 txn->txn_id = txn_id; 119 txn->being_written = FALSE; 120 121 /* Link this transaction into the head of the list. We will typically 122 be dealing with only one active transaction at a time, so it makes 123 sense for searches through the transaction list to look at the 124 newest transactions first. */ 125 txn->next = ffsd->txns; 126 ffsd->txns = txn; 127 128 return txn; 129} 130 131/* Free the transaction object for transaction TXN_ID, and remove it 132 from the transaction list of filesystem FS (which must already be 133 locked via the txn_list_lock mutex). Do nothing if the transaction 134 does not exist. */ 135static void 136free_shared_txn(svn_fs_t *fs, svn_fs_x__txn_id_t txn_id) 137{ 138 svn_fs_x__data_t *ffd = fs->fsap_data; 139 svn_fs_x__shared_data_t *ffsd = ffd->shared; 140 svn_fs_x__shared_txn_data_t *txn, *prev = NULL; 141 142 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next) 143 if (txn->txn_id == txn_id) 144 break; 145 146 if (!txn) 147 return; 148 149 if (prev) 150 prev->next = txn->next; 151 else 152 ffsd->txns = txn->next; 153 154 /* As we typically will be dealing with one transaction after another, 155 we will maintain a single-object free list so that we can hopefully 156 keep reusing the same transaction object. */ 157 if (!ffsd->free_txn) 158 ffsd->free_txn = txn; 159 else 160 svn_pool_destroy(txn->pool); 161} 162 163 164/* Obtain a lock on the transaction list of filesystem FS, call BODY 165 with FS, BATON, and POOL, and then unlock the transaction list. 166 Return what BODY returned. */ 167static svn_error_t * 168with_txnlist_lock(svn_fs_t *fs, 169 svn_error_t *(*body)(svn_fs_t *fs, 170 const void *baton, 171 apr_pool_t *pool), 172 const void *baton, 173 apr_pool_t *pool) 174{ 175 svn_fs_x__data_t *ffd = fs->fsap_data; 176 svn_fs_x__shared_data_t *ffsd = ffd->shared; 177 178 SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock, 179 body(fs, baton, pool)); 180 181 return SVN_NO_ERROR; 182} 183 184 185/* Get a lock on empty file LOCK_FILENAME, creating it in RESULT_POOL. */ 186static svn_error_t * 187get_lock_on_filesystem(const char *lock_filename, 188 apr_pool_t *result_pool) 189{ 190 return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, 191 result_pool)); 192} 193 194/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID. 195 When registered with the pool holding the lock on the lock file, 196 this makes sure the flag gets reset just before we release the lock. */ 197static apr_status_t 198reset_lock_flag(void *baton_void) 199{ 200 svn_fs_x__data_t *ffd = baton_void; 201 ffd->has_write_lock = FALSE; 202 return APR_SUCCESS; 203} 204 205/* Structure defining a file system lock to be acquired and the function 206 to be executed while the lock is held. 207 208 Instances of this structure may be nested to allow for multiple locks to 209 be taken out before executing the user-provided body. In that case, BODY 210 and BATON of the outer instances will be with_lock and a with_lock_baton_t 211 instance (transparently, no special treatment is required.). It is 212 illegal to attempt to acquire the same lock twice within the same lock 213 chain or via nesting calls using separate lock chains. 214 215 All instances along the chain share the same LOCK_POOL such that only one 216 pool needs to be created and cleared for all locks. We also allocate as 217 much data from that lock pool as possible to minimize memory usage in 218 caller pools. */ 219typedef struct with_lock_baton_t 220{ 221 /* The filesystem we operate on. Same for all instances along the chain. */ 222 svn_fs_t *fs; 223 224 /* Mutex to complement the lock file in an APR threaded process. 225 No-op object for non-threaded processes but never NULL. */ 226 svn_mutex__t *mutex; 227 228 /* Path to the file to lock. */ 229 const char *lock_path; 230 231 /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */ 232 svn_boolean_t is_global_lock; 233 234 /* Function body to execute after we acquired the lock. 235 This may be user-provided or a nested call to with_lock(). */ 236 svn_error_t *(*body)(void *baton, 237 apr_pool_t *scratch_pool); 238 239 /* Baton to pass to BODY; possibly NULL. 240 This may be user-provided or a nested lock baton instance. */ 241 void *baton; 242 243 /* Pool for all allocations along the lock chain and BODY. Will hold the 244 file locks and gets destroyed after the outermost BODY returned, 245 releasing all file locks. 246 Same for all instances along the chain. */ 247 apr_pool_t *lock_pool; 248 249 /* TRUE, iff BODY is the user-provided body. */ 250 svn_boolean_t is_inner_most_lock; 251 252 /* TRUE, iff this is not a nested lock. 253 Then responsible for destroying LOCK_POOL. */ 254 svn_boolean_t is_outer_most_lock; 255} with_lock_baton_t; 256 257/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY 258 with BATON->BATON. If this is the outermost lock call, release all file 259 locks after the body returned. If BATON->IS_GLOBAL_LOCK is set, set the 260 HAS_WRITE_LOCK flag while we keep the write lock. */ 261static svn_error_t * 262with_some_lock_file(with_lock_baton_t *baton) 263{ 264 apr_pool_t *pool = baton->lock_pool; 265 svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool); 266 267 if (!err) 268 { 269 svn_fs_t *fs = baton->fs; 270 svn_fs_x__data_t *ffd = fs->fsap_data; 271 272 if (baton->is_global_lock) 273 { 274 /* set the "got the lock" flag and register reset function */ 275 apr_pool_cleanup_register(pool, 276 ffd, 277 reset_lock_flag, 278 apr_pool_cleanup_null); 279 ffd->has_write_lock = TRUE; 280 } 281 282 /* nobody else will modify the repo state 283 => read HEAD & pack info once */ 284 if (baton->is_inner_most_lock) 285 { 286 err = svn_fs_x__update_min_unpacked_rev(fs, pool); 287 if (!err) 288 err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs, pool); 289 } 290 291 if (!err) 292 err = baton->body(baton->baton, pool); 293 } 294 295 if (baton->is_outer_most_lock) 296 svn_pool_destroy(pool); 297 298 return svn_error_trace(err); 299} 300 301/* Wraps with_some_lock_file, protecting it with BATON->MUTEX. 302 303 SCRATCH_POOL is unused here and only provided for signature compatibility 304 with WITH_LOCK_BATON_T.BODY. */ 305static svn_error_t * 306with_lock(void *baton, 307 apr_pool_t *scratch_pool) 308{ 309 with_lock_baton_t *lock_baton = baton; 310 SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton)); 311 312 return SVN_NO_ERROR; 313} 314 315/* Enum identifying a filesystem lock. */ 316typedef enum lock_id_t 317{ 318 write_lock, 319 txn_lock, 320 pack_lock 321} lock_id_t; 322 323/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK 324 according to the LOCK_ID. All other members of BATON must already be 325 valid. */ 326static void 327init_lock_baton(with_lock_baton_t *baton, 328 lock_id_t lock_id) 329{ 330 svn_fs_x__data_t *ffd = baton->fs->fsap_data; 331 svn_fs_x__shared_data_t *ffsd = ffd->shared; 332 333 switch (lock_id) 334 { 335 case write_lock: 336 baton->mutex = ffsd->fs_write_lock; 337 baton->lock_path = svn_fs_x__path_lock(baton->fs, baton->lock_pool); 338 baton->is_global_lock = TRUE; 339 break; 340 341 case txn_lock: 342 baton->mutex = ffsd->txn_current_lock; 343 baton->lock_path = svn_fs_x__path_txn_current_lock(baton->fs, 344 baton->lock_pool); 345 baton->is_global_lock = FALSE; 346 break; 347 348 case pack_lock: 349 baton->mutex = ffsd->fs_pack_lock; 350 baton->lock_path = svn_fs_x__path_pack_lock(baton->fs, 351 baton->lock_pool); 352 baton->is_global_lock = FALSE; 353 break; 354 } 355} 356 357/* Return the baton for the innermost lock of a (potential) lock chain. 358 The baton shall take out LOCK_ID from FS and execute BODY with BATON 359 while the lock is being held. Allocate the result in a sub-pool of 360 RESULT_POOL. 361 */ 362static with_lock_baton_t * 363create_lock_baton(svn_fs_t *fs, 364 lock_id_t lock_id, 365 svn_error_t *(*body)(void *baton, 366 apr_pool_t *scratch_pool), 367 void *baton, 368 apr_pool_t *result_pool) 369{ 370 /* Allocate everything along the lock chain into a single sub-pool. 371 This minimizes memory usage and cleanup overhead. */ 372 apr_pool_t *lock_pool = svn_pool_create(result_pool); 373 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result)); 374 375 /* Store parameters. */ 376 result->fs = fs; 377 result->body = body; 378 result->baton = baton; 379 380 /* File locks etc. will use this pool as well for easy cleanup. */ 381 result->lock_pool = lock_pool; 382 383 /* Right now, we are the first, (only, ) and last struct in the chain. */ 384 result->is_inner_most_lock = TRUE; 385 result->is_outer_most_lock = TRUE; 386 387 /* Select mutex and lock file path depending on LOCK_ID. 388 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */ 389 init_lock_baton(result, lock_id); 390 391 return result; 392} 393 394/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock. 395 * 396 * That means, when you create a lock chain, start with the last / innermost 397 * lock to take out and add the first / outermost lock last. 398 */ 399static with_lock_baton_t * 400chain_lock_baton(lock_id_t lock_id, 401 with_lock_baton_t *nested) 402{ 403 /* Use the same pool for batons along the lock chain. */ 404 apr_pool_t *lock_pool = nested->lock_pool; 405 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result)); 406 407 /* All locks along the chain operate on the same FS. */ 408 result->fs = nested->fs; 409 410 /* Execution of this baton means acquiring the nested lock and its 411 execution. */ 412 result->body = with_lock; 413 result->baton = nested; 414 415 /* Shared among all locks along the chain. */ 416 result->lock_pool = lock_pool; 417 418 /* We are the new outermost lock but surely not the innermost lock. */ 419 result->is_inner_most_lock = FALSE; 420 result->is_outer_most_lock = TRUE; 421 nested->is_outer_most_lock = FALSE; 422 423 /* Select mutex and lock file path depending on LOCK_ID. 424 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */ 425 init_lock_baton(result, lock_id); 426 427 return result; 428} 429 430svn_error_t * 431svn_fs_x__with_write_lock(svn_fs_t *fs, 432 svn_error_t *(*body)(void *baton, 433 apr_pool_t *scratch_pool), 434 void *baton, 435 apr_pool_t *scratch_pool) 436{ 437 return svn_error_trace( 438 with_lock(create_lock_baton(fs, write_lock, body, baton, 439 scratch_pool), 440 scratch_pool)); 441} 442 443svn_error_t * 444svn_fs_x__with_pack_lock(svn_fs_t *fs, 445 svn_error_t *(*body)(void *baton, 446 apr_pool_t *scratch_pool), 447 void *baton, 448 apr_pool_t *scratch_pool) 449{ 450 return svn_error_trace( 451 with_lock(create_lock_baton(fs, pack_lock, body, baton, 452 scratch_pool), 453 scratch_pool)); 454} 455 456svn_error_t * 457svn_fs_x__with_txn_current_lock(svn_fs_t *fs, 458 svn_error_t *(*body)(void *baton, 459 apr_pool_t *scratch_pool), 460 void *baton, 461 apr_pool_t *scratch_pool) 462{ 463 return svn_error_trace( 464 with_lock(create_lock_baton(fs, txn_lock, body, baton, 465 scratch_pool), 466 scratch_pool)); 467} 468 469svn_error_t * 470svn_fs_x__with_all_locks(svn_fs_t *fs, 471 svn_error_t *(*body)(void *baton, 472 apr_pool_t *scratch_pool), 473 void *baton, 474 apr_pool_t *scratch_pool) 475{ 476 /* Be sure to use the correct lock ordering as documented in 477 fs_fs_shared_data_t. The lock chain is being created in 478 innermost (last to acquire) -> outermost (first to acquire) order. */ 479 with_lock_baton_t *lock_baton 480 = create_lock_baton(fs, write_lock, body, baton, scratch_pool); 481 482 lock_baton = chain_lock_baton(pack_lock, lock_baton); 483 lock_baton = chain_lock_baton(txn_lock, lock_baton); 484 485 return svn_error_trace(with_lock(lock_baton, scratch_pool)); 486} 487 488 489/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(), 490 which see. */ 491typedef struct unlock_proto_rev_baton_t 492{ 493 svn_fs_x__txn_id_t txn_id; 494 void *lockcookie; 495} unlock_proto_rev_baton_t; 496 497/* Callback used in the implementation of unlock_proto_rev(). */ 498static svn_error_t * 499unlock_proto_rev_body(svn_fs_t *fs, 500 const void *baton, 501 apr_pool_t *scratch_pool) 502{ 503 const unlock_proto_rev_baton_t *b = baton; 504 apr_file_t *lockfile = b->lockcookie; 505 svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, FALSE); 506 apr_status_t apr_err; 507 508 if (!txn) 509 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 510 _("Can't unlock unknown transaction '%s'"), 511 svn_fs_x__txn_name(b->txn_id, scratch_pool)); 512 if (!txn->being_written) 513 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 514 _("Can't unlock nonlocked transaction '%s'"), 515 svn_fs_x__txn_name(b->txn_id, scratch_pool)); 516 517 apr_err = apr_file_unlock(lockfile); 518 if (apr_err) 519 return svn_error_wrap_apr 520 (apr_err, 521 _("Can't unlock prototype revision lockfile for transaction '%s'"), 522 svn_fs_x__txn_name(b->txn_id, scratch_pool)); 523 apr_err = apr_file_close(lockfile); 524 if (apr_err) 525 return svn_error_wrap_apr 526 (apr_err, 527 _("Can't close prototype revision lockfile for transaction '%s'"), 528 svn_fs_x__txn_name(b->txn_id, scratch_pool)); 529 530 txn->being_written = FALSE; 531 532 return SVN_NO_ERROR; 533} 534 535/* Unlock the prototype revision file for transaction TXN_ID in filesystem 536 FS using cookie LOCKCOOKIE. The original prototype revision file must 537 have been closed _before_ calling this function. 538 539 Perform temporary allocations in SCRATCH_POOL. */ 540static svn_error_t * 541unlock_proto_rev(svn_fs_t *fs, 542 svn_fs_x__txn_id_t txn_id, 543 void *lockcookie, 544 apr_pool_t *scratch_pool) 545{ 546 unlock_proto_rev_baton_t b; 547 548 b.txn_id = txn_id; 549 b.lockcookie = lockcookie; 550 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, scratch_pool); 551} 552 553/* A structure used by get_writable_proto_rev() and 554 get_writable_proto_rev_body(), which see. */ 555typedef struct get_writable_proto_rev_baton_t 556{ 557 void **lockcookie; 558 svn_fs_x__txn_id_t txn_id; 559} get_writable_proto_rev_baton_t; 560 561/* Callback used in the implementation of get_writable_proto_rev(). */ 562static svn_error_t * 563get_writable_proto_rev_body(svn_fs_t *fs, 564 const void *baton, 565 apr_pool_t *scratch_pool) 566{ 567 const get_writable_proto_rev_baton_t *b = baton; 568 void **lockcookie = b->lockcookie; 569 svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, TRUE); 570 571 /* First, ensure that no thread in this process (including this one) 572 is currently writing to this transaction's proto-rev file. */ 573 if (txn->being_written) 574 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 575 _("Cannot write to the prototype revision file " 576 "of transaction '%s' because a previous " 577 "representation is currently being written by " 578 "this process"), 579 svn_fs_x__txn_name(b->txn_id, scratch_pool)); 580 581 582 /* We know that no thread in this process is writing to the proto-rev 583 file, and by extension, that no thread in this process is holding a 584 lock on the prototype revision lock file. It is therefore safe 585 for us to attempt to lock this file, to see if any other process 586 is holding a lock. */ 587 588 { 589 apr_file_t *lockfile; 590 apr_status_t apr_err; 591 const char *lockfile_path 592 = svn_fs_x__path_txn_proto_rev_lock(fs, b->txn_id, scratch_pool); 593 594 /* Open the proto-rev lockfile, creating it if necessary, as it may 595 not exist if the transaction dates from before the lockfiles were 596 introduced. 597 598 ### We'd also like to use something like svn_io_file_lock2(), but 599 that forces us to create a subpool just to be able to unlock 600 the file, which seems a waste. */ 601 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path, 602 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, 603 scratch_pool)); 604 605 apr_err = apr_file_lock(lockfile, 606 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK); 607 if (apr_err) 608 { 609 svn_error_clear(svn_io_file_close(lockfile, scratch_pool)); 610 611 if (APR_STATUS_IS_EAGAIN(apr_err)) 612 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 613 _("Cannot write to the prototype revision " 614 "file of transaction '%s' because a " 615 "previous representation is currently " 616 "being written by another process"), 617 svn_fs_x__txn_name(b->txn_id, 618 scratch_pool)); 619 620 return svn_error_wrap_apr(apr_err, 621 _("Can't get exclusive lock on file '%s'"), 622 svn_dirent_local_style(lockfile_path, 623 scratch_pool)); 624 } 625 626 *lockcookie = lockfile; 627 } 628 629 /* We've successfully locked the transaction; mark it as such. */ 630 txn->being_written = TRUE; 631 632 return SVN_NO_ERROR; 633} 634 635/* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV 636 of transaction TXN_ID in filesystem FS matches the proto-index file. 637 Trim any crash / failure related extra data from the proto-rev file. 638 639 If the prototype revision file is too short, we can't do much but bail out. 640 641 Perform all allocations in SCRATCH_POOL. */ 642static svn_error_t * 643auto_truncate_proto_rev(svn_fs_t *fs, 644 apr_file_t *proto_rev, 645 apr_off_t actual_length, 646 svn_fs_x__txn_id_t txn_id, 647 apr_pool_t *scratch_pool) 648{ 649 /* Determine file range covered by the proto-index so far. Note that 650 we always append to both file, i.e. the last index entry also 651 corresponds to the last addition in the rev file. */ 652 const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool); 653 apr_file_t *file; 654 apr_off_t indexed_length; 655 656 SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool)); 657 SVN_ERR(svn_fs_x__p2l_proto_index_next_offset(&indexed_length, file, 658 scratch_pool)); 659 SVN_ERR(svn_io_file_close(file, scratch_pool)); 660 661 /* Handle mismatches. */ 662 if (indexed_length < actual_length) 663 SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, scratch_pool)); 664 else if (indexed_length > actual_length) 665 return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT, 666 NULL, 667 _("p2l proto index offset %s beyond proto" 668 "rev file size %s for TXN %s"), 669 apr_off_t_toa(scratch_pool, indexed_length), 670 apr_off_t_toa(scratch_pool, actual_length), 671 svn_fs_x__txn_name(txn_id, scratch_pool)); 672 673 return SVN_NO_ERROR; 674} 675 676/* Get a handle to the prototype revision file for transaction TXN_ID in 677 filesystem FS, and lock it for writing. Return FILE, a file handle 678 positioned at the end of the file, and LOCKCOOKIE, a cookie that 679 should be passed to unlock_proto_rev() to unlock the file once FILE 680 has been closed. 681 682 If the prototype revision file is already locked, return error 683 SVN_ERR_FS_REP_BEING_WRITTEN. 684 685 Perform all allocations in POOL. */ 686static svn_error_t * 687get_writable_proto_rev(apr_file_t **file, 688 void **lockcookie, 689 svn_fs_t *fs, 690 svn_fs_x__txn_id_t txn_id, 691 apr_pool_t *pool) 692{ 693 get_writable_proto_rev_baton_t b; 694 svn_error_t *err; 695 apr_off_t end_offset = 0; 696 697 b.lockcookie = lockcookie; 698 b.txn_id = txn_id; 699 700 SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool)); 701 702 /* Now open the prototype revision file and seek to the end. */ 703 err = svn_io_file_open(file, 704 svn_fs_x__path_txn_proto_rev(fs, txn_id, pool), 705 APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool); 706 707 /* You might expect that we could dispense with the following seek 708 and achieve the same thing by opening the file using APR_APPEND. 709 Unfortunately, APR's buffered file implementation unconditionally 710 places its initial file pointer at the start of the file (even for 711 files opened with APR_APPEND), so we need this seek to reconcile 712 the APR file pointer to the OS file pointer (since we need to be 713 able to read the current file position later). */ 714 if (!err) 715 err = svn_io_file_seek(*file, APR_END, &end_offset, pool); 716 717 /* We don't want unused sections (such as leftovers from failed delta 718 stream) in our file. If we use log addressing, we would need an 719 index entry for the unused section and that section would need to 720 be all NUL by convention. So, detect and fix those cases by truncating 721 the protorev file. */ 722 if (!err) 723 err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool); 724 725 if (err) 726 { 727 err = svn_error_compose_create( 728 err, 729 unlock_proto_rev(fs, txn_id, *lockcookie, pool)); 730 731 *lockcookie = NULL; 732 } 733 734 return svn_error_trace(err); 735} 736 737/* Callback used in the implementation of purge_shared_txn(). */ 738static svn_error_t * 739purge_shared_txn_body(svn_fs_t *fs, 740 const void *baton, 741 apr_pool_t *scratch_pool) 742{ 743 svn_fs_x__txn_id_t txn_id = *(const svn_fs_x__txn_id_t *)baton; 744 745 free_shared_txn(fs, txn_id); 746 747 return SVN_NO_ERROR; 748} 749 750/* Purge the shared data for transaction TXN_ID in filesystem FS. 751 Perform all temporary allocations in SCRATCH_POOL. */ 752static svn_error_t * 753purge_shared_txn(svn_fs_t *fs, 754 svn_fs_x__txn_id_t txn_id, 755 apr_pool_t *scratch_pool) 756{ 757 return with_txnlist_lock(fs, purge_shared_txn_body, &txn_id, scratch_pool); 758} 759 760 761svn_boolean_t 762svn_fs_x__is_fresh_txn_root(svn_fs_x__noderev_t *noderev) 763{ 764 /* Is it a root node? */ 765 if (noderev->noderev_id.number != SVN_FS_X__ITEM_INDEX_ROOT_NODE) 766 return FALSE; 767 768 /* ... in a transaction? */ 769 if (!svn_fs_x__is_txn(noderev->noderev_id.change_set)) 770 return FALSE; 771 772 /* ... with no prop change in that txn? 773 (Once we set a property, the prop rep will never become NULL again.) */ 774 if (noderev->prop_rep && svn_fs_x__is_txn(noderev->prop_rep->id.change_set)) 775 return FALSE; 776 777 /* ... and no sub-tree change? 778 (Once we set a text, the data rep will never become NULL again.) */ 779 if (noderev->data_rep && svn_fs_x__is_txn(noderev->data_rep->id.change_set)) 780 return FALSE; 781 782 /* Root node of a txn with no changes. */ 783 return TRUE; 784} 785 786svn_error_t * 787svn_fs_x__put_node_revision(svn_fs_t *fs, 788 svn_fs_x__noderev_t *noderev, 789 apr_pool_t *scratch_pool) 790{ 791 apr_file_t *noderev_file; 792 const svn_fs_x__id_t *id = &noderev->noderev_id; 793 794 if (! svn_fs_x__is_txn(id->change_set)) 795 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 796 _("Attempted to write to non-transaction '%s'"), 797 svn_fs_x__id_unparse(id, scratch_pool)->data); 798 799 SVN_ERR(svn_io_file_open(&noderev_file, 800 svn_fs_x__path_txn_node_rev(fs, id, scratch_pool, 801 scratch_pool), 802 APR_WRITE | APR_CREATE | APR_TRUNCATE 803 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool)); 804 805 SVN_ERR(svn_fs_x__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE, 806 scratch_pool), 807 noderev, scratch_pool)); 808 809 SVN_ERR(svn_io_file_close(noderev_file, scratch_pool)); 810 811 return SVN_NO_ERROR; 812} 813 814/* For the in-transaction NODEREV within FS, write the sha1->rep mapping 815 * file in the respective transaction, if rep sharing has been enabled etc. 816 * Use SCATCH_POOL for temporary allocations. 817 */ 818static svn_error_t * 819store_sha1_rep_mapping(svn_fs_t *fs, 820 svn_fs_x__noderev_t *noderev, 821 apr_pool_t *scratch_pool) 822{ 823 svn_fs_x__data_t *ffd = fs->fsap_data; 824 825 /* if rep sharing has been enabled and the noderev has a data rep and 826 * its SHA-1 is known, store the rep struct under its SHA1. */ 827 if ( ffd->rep_sharing_allowed 828 && noderev->data_rep 829 && noderev->data_rep->has_sha1) 830 { 831 apr_file_t *rep_file; 832 apr_int64_t txn_id 833 = svn_fs_x__get_txn_id(noderev->data_rep->id.change_set); 834 const char *file_name 835 = svn_fs_x__path_txn_sha1(fs, txn_id, 836 noderev->data_rep->sha1_digest, 837 scratch_pool); 838 svn_stringbuf_t *rep_string 839 = svn_fs_x__unparse_representation(noderev->data_rep, 840 (noderev->kind == svn_node_dir), 841 scratch_pool, scratch_pool); 842 843 SVN_ERR(svn_io_file_open(&rep_file, file_name, 844 APR_WRITE | APR_CREATE | APR_TRUNCATE 845 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool)); 846 847 SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data, 848 rep_string->len, NULL, scratch_pool)); 849 850 SVN_ERR(svn_io_file_close(rep_file, scratch_pool)); 851 } 852 853 return SVN_NO_ERROR; 854} 855 856static svn_error_t * 857unparse_dir_entry(svn_fs_x__dirent_t *dirent, 858 svn_stream_t *stream, 859 apr_pool_t *scratch_pool) 860{ 861 const char *val 862 = apr_psprintf(scratch_pool, "%s %s", 863 (dirent->kind == svn_node_file) ? SVN_FS_X__KIND_FILE 864 : SVN_FS_X__KIND_DIR, 865 svn_fs_x__id_unparse(&dirent->id, scratch_pool)->data); 866 867 SVN_ERR(svn_stream_printf(stream, scratch_pool, "K %" APR_SIZE_T_FMT 868 "\n%s\nV %" APR_SIZE_T_FMT "\n%s\n", 869 strlen(dirent->name), dirent->name, 870 strlen(val), val)); 871 return SVN_NO_ERROR; 872} 873 874/* Write the directory given as array of dirent structs in ENTRIES to STREAM. 875 Perform temporary allocations in SCRATCH_POOL. */ 876static svn_error_t * 877unparse_dir_entries(apr_array_header_t *entries, 878 svn_stream_t *stream, 879 apr_pool_t *scratch_pool) 880{ 881 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 882 int i; 883 for (i = 0; i < entries->nelts; ++i) 884 { 885 svn_fs_x__dirent_t *dirent; 886 887 svn_pool_clear(iterpool); 888 dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *); 889 SVN_ERR(unparse_dir_entry(dirent, stream, iterpool)); 890 } 891 892 SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s\n", 893 SVN_HASH_TERMINATOR)); 894 895 svn_pool_destroy(iterpool); 896 return SVN_NO_ERROR; 897} 898 899/* Return a deep copy of SOURCE and allocate it in RESULT_POOL. 900 */ 901static svn_fs_x__change_t * 902path_change_dup(const svn_fs_x__change_t *source, 903 apr_pool_t *result_pool) 904{ 905 svn_fs_x__change_t *result 906 = apr_pmemdup(result_pool, source, sizeof(*source)); 907 result->path.data 908 = apr_pstrmemdup(result_pool, source->path.data, source->path.len); 909 910 if (source->copyfrom_path) 911 result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path); 912 913 return result; 914} 915 916/* Merge the internal-use-only CHANGE into a hash of public-FS 917 svn_fs_x__change_t CHANGED_PATHS, collapsing multiple changes into a 918 single summarical (is that real word?) change per path. DELETIONS is 919 also a path->svn_fs_x__change_t hash and contains all the deletions 920 that got turned into a replacement. */ 921static svn_error_t * 922fold_change(apr_hash_t *changed_paths, 923 apr_hash_t *deletions, 924 const svn_fs_x__change_t *change) 925{ 926 apr_pool_t *pool = apr_hash_pool_get(changed_paths); 927 svn_fs_x__change_t *old_change, *new_change; 928 const svn_string_t *path = &change->path; 929 930 if ((old_change = apr_hash_get(changed_paths, path->data, path->len))) 931 { 932 /* This path already exists in the hash, so we have to merge 933 this change into the already existing one. */ 934 935 /* Sanity check: only allow unused node revision IDs in the 936 `reset' case. */ 937 if ((! svn_fs_x__id_used(&change->noderev_id)) 938 && (change->change_kind != svn_fs_path_change_reset)) 939 return svn_error_create 940 (SVN_ERR_FS_CORRUPT, NULL, 941 _("Missing required node revision ID")); 942 943 /* Sanity check: we should be talking about the same node 944 revision ID as our last change except where the last change 945 was a deletion. */ 946 if (svn_fs_x__id_used(&change->noderev_id) 947 && (!svn_fs_x__id_eq(&old_change->noderev_id, &change->noderev_id)) 948 && (old_change->change_kind != svn_fs_path_change_delete)) 949 return svn_error_create 950 (SVN_ERR_FS_CORRUPT, NULL, 951 _("Invalid change ordering: new node revision ID " 952 "without delete")); 953 954 /* Sanity check: an add, replacement, or reset must be the first 955 thing to follow a deletion. */ 956 if ((old_change->change_kind == svn_fs_path_change_delete) 957 && (! ((change->change_kind == svn_fs_path_change_replace) 958 || (change->change_kind == svn_fs_path_change_reset) 959 || (change->change_kind == svn_fs_path_change_add)))) 960 return svn_error_create 961 (SVN_ERR_FS_CORRUPT, NULL, 962 _("Invalid change ordering: non-add change on deleted path")); 963 964 /* Sanity check: an add can't follow anything except 965 a delete or reset. */ 966 if ((change->change_kind == svn_fs_path_change_add) 967 && (old_change->change_kind != svn_fs_path_change_delete) 968 && (old_change->change_kind != svn_fs_path_change_reset)) 969 return svn_error_create 970 (SVN_ERR_FS_CORRUPT, NULL, 971 _("Invalid change ordering: add change on preexisting path")); 972 973 /* Now, merge that change in. */ 974 switch (change->change_kind) 975 { 976 case svn_fs_path_change_reset: 977 /* A reset here will simply remove the path change from the 978 hash. */ 979 apr_hash_set(changed_paths, path->data, path->len, NULL); 980 break; 981 982 case svn_fs_path_change_delete: 983 if (old_change->change_kind == svn_fs_path_change_add) 984 { 985 /* If the path was introduced in this transaction via an 986 add, and we are deleting it, just remove the path 987 altogether. (The caller will delete any child paths.) */ 988 apr_hash_set(changed_paths, path->data, path->len, NULL); 989 } 990 else if (old_change->change_kind == svn_fs_path_change_replace) 991 { 992 /* A deleting a 'replace' restore the original deletion. */ 993 new_change = apr_hash_get(deletions, path->data, path->len); 994 SVN_ERR_ASSERT(new_change); 995 apr_hash_set(changed_paths, path->data, path->len, new_change); 996 } 997 else 998 { 999 /* A deletion overrules a previous change (modify). */ 1000 new_change = path_change_dup(change, pool); 1001 apr_hash_set(changed_paths, path->data, path->len, new_change); 1002 } 1003 break; 1004 1005 case svn_fs_path_change_add: 1006 case svn_fs_path_change_replace: 1007 /* An add at this point must be following a previous delete, 1008 so treat it just like a replace. Remember the original 1009 deletion such that we are able to delete this path again 1010 (the replacement may have changed node kind and id). */ 1011 new_change = path_change_dup(change, pool); 1012 new_change->change_kind = svn_fs_path_change_replace; 1013 1014 apr_hash_set(changed_paths, path->data, path->len, new_change); 1015 1016 /* Remember the original change. 1017 * Make sure to allocate the hash key in a durable pool. */ 1018 apr_hash_set(deletions, 1019 apr_pstrmemdup(apr_hash_pool_get(deletions), 1020 path->data, path->len), 1021 path->len, old_change); 1022 break; 1023 1024 case svn_fs_path_change_modify: 1025 default: 1026 /* If the new change modifies some attribute of the node, set 1027 the corresponding flag, whether it already was set or not. 1028 Note: We do not reset a flag to FALSE if a change is undone. */ 1029 if (change->text_mod) 1030 old_change->text_mod = TRUE; 1031 if (change->prop_mod) 1032 old_change->prop_mod = TRUE; 1033 if (change->mergeinfo_mod == svn_tristate_true) 1034 old_change->mergeinfo_mod = svn_tristate_true; 1035 break; 1036 } 1037 } 1038 else 1039 { 1040 /* Add this path. The API makes no guarantees that this (new) key 1041 will not be retained. Thus, we copy the key into the target pool 1042 to ensure a proper lifetime. */ 1043 new_change = path_change_dup(change, pool); 1044 apr_hash_set(changed_paths, new_change->path.data, 1045 new_change->path.len, new_change); 1046 } 1047 1048 return SVN_NO_ERROR; 1049} 1050 1051/* Baton type to be used with process_changes(). */ 1052typedef struct process_changes_baton_t 1053{ 1054 /* Folded list of path changes. */ 1055 apr_hash_t *changed_paths; 1056 1057 /* Path changes that are deletions and have been turned into 1058 replacements. If those replacements get deleted again, this 1059 container contains the record that we have to revert to. */ 1060 apr_hash_t *deletions; 1061} process_changes_baton_t; 1062 1063/* An implementation of svn_fs_x__change_receiver_t. 1064 Examine all the changed path entries in CHANGES and store them in 1065 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary 1066 data. Use SCRATCH_POOL for temporary allocations. */ 1067static svn_error_t * 1068process_changes(void *baton_p, 1069 svn_fs_x__change_t *change, 1070 apr_pool_t *scratch_pool) 1071{ 1072 process_changes_baton_t *baton = baton_p; 1073 1074 SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change)); 1075 1076 /* Now, if our change was a deletion or replacement, we have to 1077 blow away any changes thus far on paths that are (or, were) 1078 children of this path. 1079 ### i won't bother with another iteration pool here -- at 1080 most we talking about a few extra dups of paths into what 1081 is already a temporary subpool. 1082 */ 1083 1084 if ((change->change_kind == svn_fs_path_change_delete) 1085 || (change->change_kind == svn_fs_path_change_replace)) 1086 { 1087 apr_hash_index_t *hi; 1088 1089 /* a potential child path must contain at least 2 more chars 1090 (the path separator plus at least one char for the name). 1091 Also, we should not assume that all paths have been normalized 1092 i.e. some might have trailing path separators. 1093 */ 1094 apr_ssize_t path_len = change->path.len; 1095 apr_ssize_t min_child_len = path_len == 0 1096 ? 1 1097 : change->path.data[path_len-1] == '/' 1098 ? path_len + 1 1099 : path_len + 2; 1100 1101 /* CAUTION: This is the inner loop of an O(n^2) algorithm. 1102 The number of changes to process may be >> 1000. 1103 Therefore, keep the inner loop as tight as possible. 1104 */ 1105 for (hi = apr_hash_first(scratch_pool, baton->changed_paths); 1106 hi; 1107 hi = apr_hash_next(hi)) 1108 { 1109 /* KEY is the path. */ 1110 const void *path; 1111 apr_ssize_t klen; 1112 apr_hash_this(hi, &path, &klen, NULL); 1113 1114 /* If we come across a child of our path, remove it. 1115 Call svn_fspath__skip_ancestor only if there is a chance that 1116 this is actually a sub-path. 1117 */ 1118 if (klen >= min_child_len) 1119 { 1120 const char *child; 1121 1122 child = svn_fspath__skip_ancestor(change->path.data, path); 1123 if (child && child[0] != '\0') 1124 apr_hash_set(baton->changed_paths, path, klen, NULL); 1125 } 1126 } 1127 } 1128 1129 return SVN_NO_ERROR; 1130} 1131 1132svn_error_t * 1133svn_fs_x__txn_changes_fetch(apr_hash_t **changed_paths_p, 1134 svn_fs_t *fs, 1135 svn_fs_x__txn_id_t txn_id, 1136 apr_pool_t *pool) 1137{ 1138 apr_file_t *file; 1139 apr_hash_t *changed_paths = apr_hash_make(pool); 1140 apr_pool_t *scratch_pool = svn_pool_create(pool); 1141 process_changes_baton_t baton; 1142 1143 baton.changed_paths = changed_paths; 1144 baton.deletions = apr_hash_make(scratch_pool); 1145 1146 SVN_ERR(svn_io_file_open(&file, 1147 svn_fs_x__path_txn_changes(fs, txn_id, scratch_pool), 1148 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, 1149 scratch_pool)); 1150 1151 SVN_ERR(svn_fs_x__read_changes_incrementally( 1152 svn_stream_from_aprfile2(file, TRUE, 1153 scratch_pool), 1154 process_changes, &baton, 1155 scratch_pool)); 1156 svn_pool_destroy(scratch_pool); 1157 1158 *changed_paths_p = changed_paths; 1159 1160 return SVN_NO_ERROR; 1161} 1162 1163/* Copy a revision node-rev SRC into the current transaction TXN_ID in 1164 the filesystem FS. This is only used to create the root of a transaction. 1165 Temporary allocations are from SCRATCH_POOL. */ 1166static svn_error_t * 1167create_new_txn_noderev_from_rev(svn_fs_t *fs, 1168 svn_fs_x__txn_id_t txn_id, 1169 svn_fs_x__id_t *src, 1170 apr_pool_t *scratch_pool) 1171{ 1172 svn_fs_x__noderev_t *noderev; 1173 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, src, scratch_pool, 1174 scratch_pool)); 1175 1176 /* This must be a root node. */ 1177 SVN_ERR_ASSERT( noderev->node_id.number == 0 1178 && noderev->copy_id.number == 0); 1179 1180 if (svn_fs_x__is_txn(noderev->noderev_id.change_set)) 1181 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1182 _("Copying from transactions not allowed")); 1183 1184 noderev->predecessor_id = noderev->noderev_id; 1185 noderev->predecessor_count++; 1186 noderev->copyfrom_path = NULL; 1187 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 1188 1189 /* For the transaction root, the copyroot never changes. */ 1190 svn_fs_x__init_txn_root(&noderev->noderev_id, txn_id); 1191 1192 return svn_fs_x__put_node_revision(fs, noderev, scratch_pool); 1193} 1194 1195/* A structure used by get_and_increment_txn_key_body(). */ 1196typedef struct get_and_increment_txn_key_baton_t 1197{ 1198 svn_fs_t *fs; 1199 apr_uint64_t txn_number; 1200} get_and_increment_txn_key_baton_t; 1201 1202/* Callback used in the implementation of create_txn_dir(). This gets 1203 the current base 36 value in PATH_TXN_CURRENT and increments it. 1204 It returns the original value by the baton. */ 1205static svn_error_t * 1206get_and_increment_txn_key_body(void *baton, 1207 apr_pool_t *scratch_pool) 1208{ 1209 get_and_increment_txn_key_baton_t *cb = baton; 1210 const char *txn_current_filename = svn_fs_x__path_txn_current(cb->fs, 1211 scratch_pool); 1212 const char *tmp_filename; 1213 char new_id_str[SVN_INT64_BUFFER_SIZE]; 1214 1215 svn_stringbuf_t *buf; 1216 SVN_ERR(svn_fs_x__read_content(&buf, txn_current_filename, scratch_pool)); 1217 1218 /* remove trailing newlines */ 1219 cb->txn_number = svn__base36toui64(NULL, buf->data); 1220 1221 /* Increment the key and add a trailing \n to the string so the 1222 txn-current file has a newline in it. */ 1223 SVN_ERR(svn_io_write_unique(&tmp_filename, 1224 svn_dirent_dirname(txn_current_filename, 1225 scratch_pool), 1226 new_id_str, 1227 svn__ui64tobase36(new_id_str, cb->txn_number+1), 1228 svn_io_file_del_none, scratch_pool)); 1229 SVN_ERR(svn_fs_x__move_into_place(tmp_filename, txn_current_filename, 1230 txn_current_filename, scratch_pool)); 1231 1232 return SVN_NO_ERROR; 1233} 1234 1235/* Create a unique directory for a transaction in FS based on revision REV. 1236 Return the ID for this transaction in *ID_P and *TXN_ID. Use a sequence 1237 value in the transaction ID to prevent reuse of transaction IDs. */ 1238static svn_error_t * 1239create_txn_dir(const char **id_p, 1240 svn_fs_x__txn_id_t *txn_id, 1241 svn_fs_t *fs, 1242 apr_pool_t *result_pool, 1243 apr_pool_t *scratch_pool) 1244{ 1245 get_and_increment_txn_key_baton_t cb; 1246 const char *txn_dir; 1247 1248 /* Get the current transaction sequence value, which is a base-36 1249 number, from the txn-current file, and write an 1250 incremented value back out to the file. Place the revision 1251 number the transaction is based off into the transaction id. */ 1252 cb.fs = fs; 1253 SVN_ERR(svn_fs_x__with_txn_current_lock(fs, 1254 get_and_increment_txn_key_body, 1255 &cb, 1256 scratch_pool)); 1257 *txn_id = cb.txn_number; 1258 1259 *id_p = svn_fs_x__txn_name(*txn_id, result_pool); 1260 txn_dir = svn_fs_x__path_txn_dir(fs, *txn_id, scratch_pool); 1261 1262 return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, scratch_pool); 1263} 1264 1265/* Create a new transaction in filesystem FS, based on revision REV, 1266 and store it in *TXN_P, allocated in RESULT_POOL. Allocate necessary 1267 temporaries from SCRATCH_POOL. */ 1268static svn_error_t * 1269create_txn(svn_fs_txn_t **txn_p, 1270 svn_fs_t *fs, 1271 svn_revnum_t rev, 1272 apr_pool_t *result_pool, 1273 apr_pool_t *scratch_pool) 1274{ 1275 svn_fs_txn_t *txn; 1276 fs_txn_data_t *ftd; 1277 svn_fs_x__id_t root_id; 1278 1279 txn = apr_pcalloc(result_pool, sizeof(*txn)); 1280 ftd = apr_pcalloc(result_pool, sizeof(*ftd)); 1281 1282 /* Valid revision number? */ 1283 SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool)); 1284 1285 /* Get the txn_id. */ 1286 SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, result_pool, 1287 scratch_pool)); 1288 1289 txn->fs = fs; 1290 txn->base_rev = rev; 1291 1292 txn->vtable = &txn_vtable; 1293 txn->fsap_data = ftd; 1294 *txn_p = txn; 1295 1296 /* Create a new root node for this transaction. */ 1297 svn_fs_x__init_rev_root(&root_id, rev); 1298 SVN_ERR(create_new_txn_noderev_from_rev(fs, ftd->txn_id, &root_id, 1299 scratch_pool)); 1300 1301 /* Create an empty rev file. */ 1302 SVN_ERR(svn_io_file_create_empty( 1303 svn_fs_x__path_txn_proto_rev(fs, ftd->txn_id, scratch_pool), 1304 scratch_pool)); 1305 1306 /* Create an empty rev-lock file. */ 1307 SVN_ERR(svn_io_file_create_empty( 1308 svn_fs_x__path_txn_proto_rev_lock(fs, ftd->txn_id, scratch_pool), 1309 scratch_pool)); 1310 1311 /* Create an empty changes file. */ 1312 SVN_ERR(svn_io_file_create_empty( 1313 svn_fs_x__path_txn_changes(fs, ftd->txn_id, scratch_pool), 1314 scratch_pool)); 1315 1316 /* Create the next-ids file. */ 1317 SVN_ERR(svn_io_file_create( 1318 svn_fs_x__path_txn_next_ids(fs, ftd->txn_id, scratch_pool), 1319 "0 0\n", scratch_pool)); 1320 1321 return SVN_NO_ERROR; 1322} 1323 1324/* Store the property list for transaction TXN_ID in PROPLIST. 1325 Perform temporary allocations in POOL. */ 1326static svn_error_t * 1327get_txn_proplist(apr_hash_t *proplist, 1328 svn_fs_t *fs, 1329 svn_fs_x__txn_id_t txn_id, 1330 apr_pool_t *pool) 1331{ 1332 svn_stream_t *stream; 1333 1334 /* Check for issue #3696. (When we find and fix the cause, we can change 1335 * this to an assertion.) */ 1336 if (txn_id == SVN_FS_X__INVALID_TXN_ID) 1337 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 1338 _("Internal error: a null transaction id was " 1339 "passed to get_txn_proplist()")); 1340 1341 /* Open the transaction properties file. */ 1342 SVN_ERR(svn_stream_open_readonly(&stream, 1343 svn_fs_x__path_txn_props(fs, txn_id, pool), 1344 pool, pool)); 1345 1346 /* Read in the property list. */ 1347 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 1348 1349 return svn_stream_close(stream); 1350} 1351 1352/* Save the property list PROPS as the revprops for transaction TXN_ID 1353 in FS. Perform temporary allocations in SCRATCH_POOL. */ 1354static svn_error_t * 1355set_txn_proplist(svn_fs_t *fs, 1356 svn_fs_x__txn_id_t txn_id, 1357 apr_hash_t *props, 1358 svn_boolean_t final, 1359 apr_pool_t *scratch_pool) 1360{ 1361 svn_stringbuf_t *buf; 1362 svn_stream_t *stream; 1363 1364 /* Write out the new file contents to BUF. */ 1365 buf = svn_stringbuf_create_ensure(1024, scratch_pool); 1366 stream = svn_stream_from_stringbuf(buf, scratch_pool); 1367 SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, scratch_pool)); 1368 SVN_ERR(svn_stream_close(stream)); 1369 1370 /* Open the transaction properties file and write new contents to it. */ 1371 SVN_ERR(svn_io_write_atomic((final 1372 ? svn_fs_x__path_txn_props_final(fs, txn_id, 1373 scratch_pool) 1374 : svn_fs_x__path_txn_props(fs, txn_id, 1375 scratch_pool)), 1376 buf->data, buf->len, 1377 NULL /* copy_perms_path */, scratch_pool)); 1378 return SVN_NO_ERROR; 1379} 1380 1381 1382svn_error_t * 1383svn_fs_x__change_txn_prop(svn_fs_txn_t *txn, 1384 const char *name, 1385 const svn_string_t *value, 1386 apr_pool_t *scratch_pool) 1387{ 1388 apr_array_header_t *props = apr_array_make(scratch_pool, 1, 1389 sizeof(svn_prop_t)); 1390 svn_prop_t prop; 1391 1392 prop.name = name; 1393 prop.value = value; 1394 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 1395 1396 return svn_fs_x__change_txn_props(txn, props, scratch_pool); 1397} 1398 1399svn_error_t * 1400svn_fs_x__change_txn_props(svn_fs_txn_t *txn, 1401 const apr_array_header_t *props, 1402 apr_pool_t *scratch_pool) 1403{ 1404 fs_txn_data_t *ftd = txn->fsap_data; 1405 apr_hash_t *txn_prop = apr_hash_make(scratch_pool); 1406 int i; 1407 svn_error_t *err; 1408 1409 err = get_txn_proplist(txn_prop, txn->fs, ftd->txn_id, scratch_pool); 1410 /* Here - and here only - we need to deal with the possibility that the 1411 transaction property file doesn't yet exist. The rest of the 1412 implementation assumes that the file exists, but we're called to set the 1413 initial transaction properties as the transaction is being created. */ 1414 if (err && (APR_STATUS_IS_ENOENT(err->apr_err))) 1415 svn_error_clear(err); 1416 else if (err) 1417 return svn_error_trace(err); 1418 1419 for (i = 0; i < props->nelts; i++) 1420 { 1421 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 1422 1423 if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE) 1424 && !strcmp(prop->name, SVN_PROP_REVISION_DATE)) 1425 svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE, 1426 svn_string_create("1", scratch_pool)); 1427 1428 svn_hash_sets(txn_prop, prop->name, prop->value); 1429 } 1430 1431 /* Create a new version of the file and write out the new props. */ 1432 /* Open the transaction properties file. */ 1433 SVN_ERR(set_txn_proplist(txn->fs, ftd->txn_id, txn_prop, FALSE, 1434 scratch_pool)); 1435 1436 return SVN_NO_ERROR; 1437} 1438 1439svn_error_t * 1440svn_fs_x__get_txn(svn_fs_x__transaction_t **txn_p, 1441 svn_fs_t *fs, 1442 svn_fs_x__txn_id_t txn_id, 1443 apr_pool_t *pool) 1444{ 1445 svn_fs_x__transaction_t *txn; 1446 svn_fs_x__noderev_t *noderev; 1447 svn_fs_x__id_t root_id; 1448 1449 txn = apr_pcalloc(pool, sizeof(*txn)); 1450 txn->proplist = apr_hash_make(pool); 1451 1452 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool)); 1453 svn_fs_x__init_txn_root(&root_id, txn_id); 1454 1455 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &root_id, pool, pool)); 1456 1457 txn->base_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set); 1458 txn->copies = NULL; 1459 1460 *txn_p = txn; 1461 1462 return SVN_NO_ERROR; 1463} 1464 1465/* If it is supported by the format of file system FS, store the (ITEM_INDEX, 1466 * OFFSET) pair in the log-to-phys proto index file of transaction TXN_ID. 1467 * Use SCRATCH_POOL for temporary allocations. 1468 */ 1469static svn_error_t * 1470store_l2p_index_entry(svn_fs_t *fs, 1471 svn_fs_x__txn_id_t txn_id, 1472 apr_off_t offset, 1473 apr_uint64_t item_index, 1474 apr_pool_t *scratch_pool) 1475{ 1476 const char *path = svn_fs_x__path_l2p_proto_index(fs, txn_id, scratch_pool); 1477 apr_file_t *file; 1478 SVN_ERR(svn_fs_x__l2p_proto_index_open(&file, path, scratch_pool)); 1479 SVN_ERR(svn_fs_x__l2p_proto_index_add_entry(file, offset, 0, 1480 item_index, scratch_pool)); 1481 SVN_ERR(svn_io_file_close(file, scratch_pool)); 1482 1483 return SVN_NO_ERROR; 1484} 1485 1486/* If it is supported by the format of file system FS, store ENTRY in the 1487 * phys-to-log proto index file of transaction TXN_ID. 1488 * Use SCRATCH_POOL for temporary allocations. 1489 */ 1490static svn_error_t * 1491store_p2l_index_entry(svn_fs_t *fs, 1492 svn_fs_x__txn_id_t txn_id, 1493 svn_fs_x__p2l_entry_t *entry, 1494 apr_pool_t *scratch_pool) 1495{ 1496 const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool); 1497 apr_file_t *file; 1498 SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool)); 1499 SVN_ERR(svn_fs_x__p2l_proto_index_add_entry(file, entry, scratch_pool)); 1500 SVN_ERR(svn_io_file_close(file, scratch_pool)); 1501 1502 return SVN_NO_ERROR; 1503} 1504 1505/* Allocate an item index in the transaction TXN_ID of file system FS and 1506 * return it in *ITEM_INDEX. Use SCRATCH_POOL for temporary allocations. 1507 */ 1508static svn_error_t * 1509allocate_item_index(apr_uint64_t *item_index, 1510 svn_fs_t *fs, 1511 svn_fs_x__txn_id_t txn_id, 1512 apr_pool_t *scratch_pool) 1513{ 1514 apr_file_t *file; 1515 char buffer[SVN_INT64_BUFFER_SIZE] = { 0 }; 1516 svn_boolean_t eof = FALSE; 1517 apr_size_t to_write; 1518 apr_size_t read; 1519 apr_off_t offset = 0; 1520 1521 /* read number */ 1522 SVN_ERR(svn_io_file_open(&file, 1523 svn_fs_x__path_txn_item_index(fs, txn_id, 1524 scratch_pool), 1525 APR_READ | APR_WRITE 1526 | APR_CREATE | APR_BUFFERED, 1527 APR_OS_DEFAULT, scratch_pool)); 1528 SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1, 1529 &read, &eof, scratch_pool)); 1530 if (read) 1531 SVN_ERR(svn_cstring_atoui64(item_index, buffer)); 1532 else 1533 *item_index = SVN_FS_X__ITEM_INDEX_FIRST_USER; 1534 1535 /* increment it */ 1536 to_write = svn__ui64toa(buffer, *item_index + 1); 1537 1538 /* write it back to disk */ 1539 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 1540 SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, scratch_pool)); 1541 SVN_ERR(svn_io_file_close(file, scratch_pool)); 1542 1543 return SVN_NO_ERROR; 1544} 1545 1546/* Write out the currently available next node_id NODE_ID and copy_id 1547 COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is 1548 used both for creating new unique nodes for the given transaction, as 1549 well as uniquifying representations. Perform temporary allocations in 1550 SCRATCH_POOL. */ 1551static svn_error_t * 1552write_next_ids(svn_fs_t *fs, 1553 svn_fs_x__txn_id_t txn_id, 1554 apr_uint64_t node_id, 1555 apr_uint64_t copy_id, 1556 apr_pool_t *scratch_pool) 1557{ 1558 apr_file_t *file; 1559 char buffer[2 * SVN_INT64_BUFFER_SIZE + 2]; 1560 char *p = buffer; 1561 1562 p += svn__ui64tobase36(p, node_id); 1563 *(p++) = ' '; 1564 p += svn__ui64tobase36(p, copy_id); 1565 *(p++) = '\n'; 1566 *(p++) = '\0'; 1567 1568 SVN_ERR(svn_io_file_open(&file, 1569 svn_fs_x__path_txn_next_ids(fs, txn_id, 1570 scratch_pool), 1571 APR_WRITE | APR_TRUNCATE, 1572 APR_OS_DEFAULT, scratch_pool)); 1573 SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, 1574 scratch_pool)); 1575 return svn_io_file_close(file, scratch_pool); 1576} 1577 1578/* Find out what the next unique node-id and copy-id are for 1579 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID 1580 and *COPY_ID. The next node-id is used both for creating new unique 1581 nodes for the given transaction, as well as uniquifying representations. 1582 Perform temporary allocations in SCRATCH_POOL. */ 1583static svn_error_t * 1584read_next_ids(apr_uint64_t *node_id, 1585 apr_uint64_t *copy_id, 1586 svn_fs_t *fs, 1587 svn_fs_x__txn_id_t txn_id, 1588 apr_pool_t *scratch_pool) 1589{ 1590 svn_stringbuf_t *buf; 1591 const char *str; 1592 SVN_ERR(svn_fs_x__read_content(&buf, 1593 svn_fs_x__path_txn_next_ids(fs, txn_id, 1594 scratch_pool), 1595 scratch_pool)); 1596 1597 /* Parse this into two separate strings. */ 1598 1599 str = buf->data; 1600 *node_id = svn__base36toui64(&str, str); 1601 if (*str != ' ') 1602 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1603 _("next-id file corrupt")); 1604 1605 ++str; 1606 *copy_id = svn__base36toui64(&str, str); 1607 if (*str != '\n') 1608 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1609 _("next-id file corrupt")); 1610 1611 return SVN_NO_ERROR; 1612} 1613 1614/* Get a new and unique to this transaction node-id for transaction 1615 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P. 1616 Node-ids are guaranteed to be unique to this transction, but may 1617 not necessarily be sequential. 1618 Perform temporary allocations in SCRATCH_POOL. */ 1619static svn_error_t * 1620get_new_txn_node_id(svn_fs_x__id_t *node_id_p, 1621 svn_fs_t *fs, 1622 svn_fs_x__txn_id_t txn_id, 1623 apr_pool_t *scratch_pool) 1624{ 1625 apr_uint64_t node_id, copy_id; 1626 1627 /* First read in the current next-ids file. */ 1628 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool)); 1629 1630 node_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id); 1631 node_id_p->number = node_id; 1632 1633 SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, scratch_pool)); 1634 1635 return SVN_NO_ERROR; 1636} 1637 1638svn_error_t * 1639svn_fs_x__reserve_copy_id(svn_fs_x__id_t *copy_id_p, 1640 svn_fs_t *fs, 1641 svn_fs_x__txn_id_t txn_id, 1642 apr_pool_t *scratch_pool) 1643{ 1644 apr_uint64_t node_id, copy_id; 1645 1646 /* First read in the current next-ids file. */ 1647 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool)); 1648 1649 copy_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id); 1650 copy_id_p->number = copy_id; 1651 1652 SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, scratch_pool)); 1653 1654 return SVN_NO_ERROR; 1655} 1656 1657svn_error_t * 1658svn_fs_x__create_node(svn_fs_t *fs, 1659 svn_fs_x__noderev_t *noderev, 1660 const svn_fs_x__id_t *copy_id, 1661 svn_fs_x__txn_id_t txn_id, 1662 apr_pool_t *scratch_pool) 1663{ 1664 /* Get a new node-id for this node. */ 1665 SVN_ERR(get_new_txn_node_id(&noderev->node_id, fs, txn_id, scratch_pool)); 1666 1667 /* Assign copy-id. */ 1668 noderev->copy_id = *copy_id; 1669 1670 /* Noderev-id = Change set and item number within this change set. */ 1671 noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id); 1672 SVN_ERR(allocate_item_index(&noderev->noderev_id.number, fs, txn_id, 1673 scratch_pool)); 1674 1675 SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool)); 1676 1677 return SVN_NO_ERROR; 1678} 1679 1680svn_error_t * 1681svn_fs_x__purge_txn(svn_fs_t *fs, 1682 const char *txn_id_str, 1683 apr_pool_t *scratch_pool) 1684{ 1685 svn_fs_x__txn_id_t txn_id; 1686 SVN_ERR(svn_fs_x__txn_by_name(&txn_id, txn_id_str)); 1687 1688 /* Remove the shared transaction object associated with this transaction. */ 1689 SVN_ERR(purge_shared_txn(fs, txn_id, scratch_pool)); 1690 /* Remove the directory associated with this transaction. */ 1691 SVN_ERR(svn_io_remove_dir2(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool), 1692 FALSE, NULL, NULL, scratch_pool)); 1693 1694 /* Delete protorev and its lock, which aren't in the txn 1695 directory. It's OK if they don't exist (for example, if this 1696 is post-commit and the proto-rev has been moved into 1697 place). */ 1698 SVN_ERR(svn_io_remove_file2( 1699 svn_fs_x__path_txn_proto_rev(fs, txn_id, scratch_pool), 1700 TRUE, scratch_pool)); 1701 SVN_ERR(svn_io_remove_file2( 1702 svn_fs_x__path_txn_proto_rev_lock(fs, txn_id, scratch_pool), 1703 TRUE, scratch_pool)); 1704 1705 return SVN_NO_ERROR; 1706} 1707 1708 1709svn_error_t * 1710svn_fs_x__abort_txn(svn_fs_txn_t *txn, 1711 apr_pool_t *scratch_pool) 1712{ 1713 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 1714 1715 /* Now, purge the transaction. */ 1716 SVN_ERR_W(svn_fs_x__purge_txn(txn->fs, txn->id, scratch_pool), 1717 apr_psprintf(scratch_pool, _("Transaction '%s' cleanup failed"), 1718 txn->id)); 1719 1720 return SVN_NO_ERROR; 1721} 1722 1723svn_error_t * 1724svn_fs_x__set_entry(svn_fs_t *fs, 1725 svn_fs_x__txn_id_t txn_id, 1726 svn_fs_x__noderev_t *parent_noderev, 1727 const char *name, 1728 const svn_fs_x__id_t *id, 1729 svn_node_kind_t kind, 1730 apr_pool_t *result_pool, 1731 apr_pool_t *scratch_pool) 1732{ 1733 svn_fs_x__representation_t *rep = parent_noderev->data_rep; 1734 const char *filename 1735 = svn_fs_x__path_txn_node_children(fs, &parent_noderev->noderev_id, 1736 scratch_pool, scratch_pool); 1737 apr_file_t *file; 1738 svn_stream_t *out; 1739 svn_fs_x__data_t *ffd = fs->fsap_data; 1740 apr_pool_t *subpool = svn_pool_create(scratch_pool); 1741 1742 if (!rep || !svn_fs_x__is_txn(rep->id.change_set)) 1743 { 1744 apr_array_header_t *entries; 1745 1746 /* Before we can modify the directory, we need to dump its old 1747 contents into a mutable representation file. */ 1748 SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, parent_noderev, 1749 subpool, subpool)); 1750 SVN_ERR(svn_io_file_open(&file, filename, 1751 APR_WRITE | APR_CREATE | APR_BUFFERED, 1752 APR_OS_DEFAULT, scratch_pool)); 1753 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool); 1754 SVN_ERR(unparse_dir_entries(entries, out, subpool)); 1755 1756 svn_pool_clear(subpool); 1757 1758 /* Provide the parent with a data rep if it had none before 1759 (directories so far empty). */ 1760 if (!rep) 1761 { 1762 rep = apr_pcalloc(result_pool, sizeof(*rep)); 1763 parent_noderev->data_rep = rep; 1764 } 1765 1766 /* Mark the node-rev's data rep as mutable. */ 1767 rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id); 1768 rep->id.number = SVN_FS_X__ITEM_INDEX_UNUSED; 1769 1770 /* Save noderev to disk. */ 1771 SVN_ERR(svn_fs_x__put_node_revision(fs, parent_noderev, subpool)); 1772 } 1773 else 1774 { 1775 /* The directory rep is already mutable, so just open it for append. */ 1776 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND, 1777 APR_OS_DEFAULT, scratch_pool)); 1778 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool); 1779 } 1780 1781 /* update directory cache */ 1782 { 1783 /* build parameters: (name, new entry) pair */ 1784 const svn_fs_x__id_t *key = &(parent_noderev->noderev_id); 1785 replace_baton_t baton; 1786 1787 baton.name = name; 1788 baton.new_entry = NULL; 1789 1790 if (id) 1791 { 1792 baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry)); 1793 baton.new_entry->name = name; 1794 baton.new_entry->kind = kind; 1795 baton.new_entry->id = *id; 1796 } 1797 1798 /* actually update the cached directory (if cached) */ 1799 SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key, 1800 svn_fs_x__replace_dir_entry, &baton, 1801 subpool)); 1802 } 1803 svn_pool_clear(subpool); 1804 1805 /* Append an incremental hash entry for the entry change. */ 1806 if (id) 1807 { 1808 svn_fs_x__dirent_t entry; 1809 entry.name = name; 1810 entry.id = *id; 1811 entry.kind = kind; 1812 1813 SVN_ERR(unparse_dir_entry(&entry, out, subpool)); 1814 } 1815 else 1816 { 1817 SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n", 1818 strlen(name), name)); 1819 } 1820 1821 SVN_ERR(svn_io_file_close(file, subpool)); 1822 svn_pool_destroy(subpool); 1823 return SVN_NO_ERROR; 1824} 1825 1826svn_error_t * 1827svn_fs_x__add_change(svn_fs_t *fs, 1828 svn_fs_x__txn_id_t txn_id, 1829 const char *path, 1830 const svn_fs_x__id_t *id, 1831 svn_fs_path_change_kind_t change_kind, 1832 svn_boolean_t text_mod, 1833 svn_boolean_t prop_mod, 1834 svn_boolean_t mergeinfo_mod, 1835 svn_node_kind_t node_kind, 1836 svn_revnum_t copyfrom_rev, 1837 const char *copyfrom_path, 1838 apr_pool_t *scratch_pool) 1839{ 1840 apr_file_t *file; 1841 svn_fs_x__change_t change; 1842 apr_hash_t *changes = apr_hash_make(scratch_pool); 1843 1844 /* Not using APR_BUFFERED to append change in one atomic write operation. */ 1845 SVN_ERR(svn_io_file_open(&file, 1846 svn_fs_x__path_txn_changes(fs, txn_id, 1847 scratch_pool), 1848 APR_APPEND | APR_WRITE | APR_CREATE, 1849 APR_OS_DEFAULT, scratch_pool)); 1850 1851 change.path.data = path; 1852 change.path.len = strlen(path); 1853 change.noderev_id = *id; 1854 change.change_kind = change_kind; 1855 change.text_mod = text_mod; 1856 change.prop_mod = prop_mod; 1857 change.mergeinfo_mod = mergeinfo_mod ? svn_tristate_true 1858 : svn_tristate_false; 1859 change.node_kind = node_kind; 1860 change.copyfrom_known = TRUE; 1861 change.copyfrom_rev = copyfrom_rev; 1862 if (copyfrom_path) 1863 change.copyfrom_path = apr_pstrdup(scratch_pool, copyfrom_path); 1864 1865 svn_hash_sets(changes, path, &change); 1866 SVN_ERR(svn_fs_x__write_changes(svn_stream_from_aprfile2(file, TRUE, 1867 scratch_pool), 1868 fs, changes, FALSE, scratch_pool)); 1869 1870 return svn_io_file_close(file, scratch_pool); 1871} 1872 1873/* This baton is used by the representation writing streams. It keeps 1874 track of the checksum information as well as the total size of the 1875 representation so far. */ 1876typedef struct rep_write_baton_t 1877{ 1878 /* The FS we are writing to. */ 1879 svn_fs_t *fs; 1880 1881 /* Actual file to which we are writing. */ 1882 svn_stream_t *rep_stream; 1883 1884 /* A stream from the delta combiner. Data written here gets 1885 deltified, then eventually written to rep_stream. */ 1886 svn_stream_t *delta_stream; 1887 1888 /* Where is this representation header stored. */ 1889 apr_off_t rep_offset; 1890 1891 /* Start of the actual data. */ 1892 apr_off_t delta_start; 1893 1894 /* How many bytes have been written to this rep already. */ 1895 svn_filesize_t rep_size; 1896 1897 /* The node revision for which we're writing out info. */ 1898 svn_fs_x__noderev_t *noderev; 1899 1900 /* Actual output file. */ 1901 apr_file_t *file; 1902 /* Lock 'cookie' used to unlock the output file once we've finished 1903 writing to it. */ 1904 void *lockcookie; 1905 1906 svn_checksum_ctx_t *md5_checksum_ctx; 1907 svn_checksum_ctx_t *sha1_checksum_ctx; 1908 1909 /* Receives the low-level checksum when closing REP_STREAM. */ 1910 apr_uint32_t fnv1a_checksum; 1911 1912 /* Local pool, available for allocations that must remain valid as long 1913 as this baton is used but may be cleaned up immediately afterwards. */ 1914 apr_pool_t *local_pool; 1915 1916 /* Outer / result pool. */ 1917 apr_pool_t *result_pool; 1918} rep_write_baton_t; 1919 1920/* Handler for the write method of the representation writable stream. 1921 BATON is a rep_write_baton_t, DATA is the data to write, and *LEN is 1922 the length of this data. */ 1923static svn_error_t * 1924rep_write_contents(void *baton, 1925 const char *data, 1926 apr_size_t *len) 1927{ 1928 rep_write_baton_t *b = baton; 1929 1930 SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len)); 1931 SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len)); 1932 b->rep_size += *len; 1933 1934 return svn_stream_write(b->delta_stream, data, len); 1935} 1936 1937/* Set *SPANNED to the number of shards touched when walking WALK steps on 1938 * NODEREV's predecessor chain in FS. 1939 * Use SCRATCH_POOL for temporary allocations. 1940 */ 1941static svn_error_t * 1942shards_spanned(int *spanned, 1943 svn_fs_t *fs, 1944 svn_fs_x__noderev_t *noderev, 1945 int walk, 1946 apr_pool_t *scratch_pool) 1947{ 1948 svn_fs_x__data_t *ffd = fs->fsap_data; 1949 int shard_size = ffd->max_files_per_dir; 1950 apr_pool_t *iterpool; 1951 1952 int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */ 1953 svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size; 1954 iterpool = svn_pool_create(scratch_pool); 1955 while (walk-- && noderev->predecessor_count) 1956 { 1957 svn_fs_x__id_t id = noderev->predecessor_id; 1958 1959 svn_pool_clear(iterpool); 1960 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &id, scratch_pool, 1961 iterpool)); 1962 shard = svn_fs_x__get_revnum(id.change_set) / shard_size; 1963 if (shard != last_shard) 1964 { 1965 ++count; 1966 last_shard = shard; 1967 } 1968 } 1969 svn_pool_destroy(iterpool); 1970 1971 *spanned = count; 1972 return SVN_NO_ERROR; 1973} 1974 1975/* Given a node-revision NODEREV in filesystem FS, return the 1976 representation in *REP to use as the base for a text representation 1977 delta if PROPS is FALSE. If PROPS has been set, a suitable props 1978 base representation will be returned. Perform temporary allocations 1979 in *POOL. */ 1980static svn_error_t * 1981choose_delta_base(svn_fs_x__representation_t **rep, 1982 svn_fs_t *fs, 1983 svn_fs_x__noderev_t *noderev, 1984 svn_boolean_t props, 1985 apr_pool_t *pool) 1986{ 1987 /* The zero-based index (counting from the "oldest" end), along NODEREVs line 1988 * predecessors, of the node-rev we will use as delta base. */ 1989 int count; 1990 /* The length of the linear part of a delta chain. (Delta chains use 1991 * skip-delta bits for the high-order bits and are linear in the low-order 1992 * bits.) */ 1993 int walk; 1994 svn_fs_x__noderev_t *base; 1995 svn_fs_x__data_t *ffd = fs->fsap_data; 1996 apr_pool_t *iterpool; 1997 1998 /* If we have no predecessors, or that one is empty, then use the empty 1999 * stream as a base. */ 2000 if (! noderev->predecessor_count) 2001 { 2002 *rep = NULL; 2003 return SVN_NO_ERROR; 2004 } 2005 2006 /* Flip the rightmost '1' bit of the predecessor count to determine 2007 which file rev (counting from 0) we want to use. (To see why 2008 count & (count - 1) unsets the rightmost set bit, think about how 2009 you decrement a binary number.) */ 2010 count = noderev->predecessor_count; 2011 count = count & (count - 1); 2012 2013 /* Finding the delta base over a very long distance can become extremely 2014 expensive for very deep histories, possibly causing client timeouts etc. 2015 OTOH, this is a rare operation and its gains are minimal. Lets simply 2016 start deltification anew close every other 1000 changes or so. */ 2017 walk = noderev->predecessor_count - count; 2018 if (walk > (int)ffd->max_deltification_walk) 2019 { 2020 *rep = NULL; 2021 return SVN_NO_ERROR; 2022 } 2023 2024 /* We use skip delta for limiting the number of delta operations 2025 along very long node histories. Close to HEAD however, we create 2026 a linear history to minimize delta size. */ 2027 if (walk < (int)ffd->max_linear_deltification) 2028 { 2029 int shards; 2030 SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool)); 2031 2032 /* We also don't want the linear deltification to span more shards 2033 than if deltas we used in a simple skip-delta scheme. */ 2034 if ((1 << (--shards)) <= walk) 2035 count = noderev->predecessor_count - 1; 2036 } 2037 2038 /* Walk back a number of predecessors equal to the difference 2039 between count and the original predecessor count. (For example, 2040 if noderev has ten predecessors and we want the eighth file rev, 2041 walk back two predecessors.) */ 2042 base = noderev; 2043 iterpool = svn_pool_create(pool); 2044 while ((count++) < noderev->predecessor_count) 2045 { 2046 svn_fs_x__id_t id = noderev->predecessor_id; 2047 svn_pool_clear(iterpool); 2048 SVN_ERR(svn_fs_x__get_node_revision(&base, fs, &id, pool, iterpool)); 2049 } 2050 svn_pool_destroy(iterpool); 2051 2052 /* return a suitable base representation */ 2053 *rep = props ? base->prop_rep : base->data_rep; 2054 2055 /* if we encountered a shared rep, its parent chain may be different 2056 * from the node-rev parent chain. */ 2057 if (*rep) 2058 { 2059 int chain_length = 0; 2060 int shard_count = 0; 2061 2062 /* Very short rep bases are simply not worth it as we are unlikely 2063 * to re-coup the deltification space overhead of 20+ bytes. */ 2064 svn_filesize_t rep_size = (*rep)->expanded_size 2065 ? (*rep)->expanded_size 2066 : (*rep)->size; 2067 if (rep_size < 64) 2068 { 2069 *rep = NULL; 2070 return SVN_NO_ERROR; 2071 } 2072 2073 /* Check whether the length of the deltification chain is acceptable. 2074 * Otherwise, shared reps may form a non-skipping delta chain in 2075 * extreme cases. */ 2076 SVN_ERR(svn_fs_x__rep_chain_length(&chain_length, &shard_count, 2077 *rep, fs, pool)); 2078 2079 /* Some reasonable limit, depending on how acceptable longer linear 2080 * chains are in this repo. Also, allow for some minimal chain. */ 2081 if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2) 2082 *rep = NULL; 2083 else 2084 /* To make it worth opening additional shards / pack files, we 2085 * require that the reps have a certain minimal size. To deltify 2086 * against a rep in different shard, the lower limit is 512 bytes 2087 * and doubles with every extra shard to visit along the delta 2088 * chain. */ 2089 if ( shard_count > 1 2090 && ((svn_filesize_t)128 << shard_count) >= rep_size) 2091 *rep = NULL; 2092 } 2093 2094 return SVN_NO_ERROR; 2095} 2096 2097/* Something went wrong and the pool for the rep write is being 2098 cleared before we've finished writing the rep. So we need 2099 to remove the rep from the protorevfile and we need to unlock 2100 the protorevfile. */ 2101static apr_status_t 2102rep_write_cleanup(void *data) 2103{ 2104 svn_error_t *err; 2105 rep_write_baton_t *b = data; 2106 svn_fs_x__txn_id_t txn_id 2107 = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set); 2108 2109 /* Truncate and close the protorevfile. */ 2110 err = svn_io_file_trunc(b->file, b->rep_offset, b->local_pool); 2111 err = svn_error_compose_create(err, svn_io_file_close(b->file, 2112 b->local_pool)); 2113 2114 /* Remove our lock regardless of any preceding errors so that the 2115 being_written flag is always removed and stays consistent with the 2116 file lock which will be removed no matter what since the pool is 2117 going away. */ 2118 err = svn_error_compose_create(err, 2119 unlock_proto_rev(b->fs, txn_id, 2120 b->lockcookie, 2121 b->local_pool)); 2122 if (err) 2123 { 2124 apr_status_t rc = err->apr_err; 2125 svn_error_clear(err); 2126 return rc; 2127 } 2128 2129 return APR_SUCCESS; 2130} 2131 2132/* Get a rep_write_baton_t, allocated from RESULT_POOL, and store it in 2133 WB_P for the representation indicated by NODEREV in filesystem FS. 2134 Only appropriate for file contents, not for props or directory contents. 2135 */ 2136static svn_error_t * 2137rep_write_get_baton(rep_write_baton_t **wb_p, 2138 svn_fs_t *fs, 2139 svn_fs_x__noderev_t *noderev, 2140 apr_pool_t *result_pool) 2141{ 2142 svn_fs_x__data_t *ffd = fs->fsap_data; 2143 rep_write_baton_t *b; 2144 apr_file_t *file; 2145 svn_fs_x__representation_t *base_rep; 2146 svn_stream_t *source; 2147 svn_txdelta_window_handler_t wh; 2148 void *whb; 2149 int diff_version = 1; 2150 svn_fs_x__rep_header_t header = { 0 }; 2151 svn_fs_x__txn_id_t txn_id 2152 = svn_fs_x__get_txn_id(noderev->noderev_id.change_set); 2153 2154 b = apr_pcalloc(result_pool, sizeof(*b)); 2155 2156 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, 2157 result_pool); 2158 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, 2159 result_pool); 2160 2161 b->fs = fs; 2162 b->result_pool = result_pool; 2163 b->local_pool = svn_pool_create(result_pool); 2164 b->rep_size = 0; 2165 b->noderev = noderev; 2166 2167 /* Open the prototype rev file and seek to its end. */ 2168 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, fs, txn_id, 2169 b->local_pool)); 2170 2171 b->file = file; 2172 b->rep_stream = svn_checksum__wrap_write_stream_fnv1a_32x4( 2173 &b->fnv1a_checksum, 2174 svn_stream_from_aprfile2(file, TRUE, 2175 b->local_pool), 2176 b->local_pool); 2177 2178 SVN_ERR(svn_fs_x__get_file_offset(&b->rep_offset, file, b->local_pool)); 2179 2180 /* Get the base for this delta. */ 2181 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->local_pool)); 2182 SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, TRUE, 2183 b->local_pool)); 2184 2185 /* Write out the rep header. */ 2186 if (base_rep) 2187 { 2188 header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set); 2189 header.base_item_index = base_rep->id.number; 2190 header.base_length = base_rep->size; 2191 header.type = svn_fs_x__rep_delta; 2192 } 2193 else 2194 { 2195 header.type = svn_fs_x__rep_self_delta; 2196 } 2197 SVN_ERR(svn_fs_x__write_rep_header(&header, b->rep_stream, 2198 b->local_pool)); 2199 2200 /* Now determine the offset of the actual svndiff data. */ 2201 SVN_ERR(svn_fs_x__get_file_offset(&b->delta_start, file, 2202 b->local_pool)); 2203 2204 /* Cleanup in case something goes wrong. */ 2205 apr_pool_cleanup_register(b->local_pool, b, rep_write_cleanup, 2206 apr_pool_cleanup_null); 2207 2208 /* Prepare to write the svndiff data. */ 2209 svn_txdelta_to_svndiff3(&wh, 2210 &whb, 2211 svn_stream_disown(b->rep_stream, b->result_pool), 2212 diff_version, 2213 ffd->delta_compression_level, 2214 result_pool); 2215 2216 b->delta_stream = svn_txdelta_target_push(wh, whb, source, 2217 b->result_pool); 2218 2219 *wb_p = b; 2220 2221 return SVN_NO_ERROR; 2222} 2223 2224/* For REP->SHA1_CHECKSUM, try to find an already existing representation 2225 in FS and return it in *OUT_REP. If no such representation exists or 2226 if rep sharing has been disabled for FS, NULL will be returned. Since 2227 there may be new duplicate representations within the same uncommitted 2228 revision, those can be passed in REPS_HASH (maps a sha1 digest onto 2229 svn_fs_x__representation_t*), otherwise pass in NULL for REPS_HASH. 2230 Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries. 2231 The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime. 2232 */ 2233static svn_error_t * 2234get_shared_rep(svn_fs_x__representation_t **old_rep, 2235 svn_fs_t *fs, 2236 svn_fs_x__representation_t *rep, 2237 apr_hash_t *reps_hash, 2238 apr_pool_t *result_pool, 2239 apr_pool_t *scratch_pool) 2240{ 2241 svn_error_t *err; 2242 svn_fs_x__data_t *ffd = fs->fsap_data; 2243 2244 /* Return NULL, if rep sharing has been disabled. */ 2245 *old_rep = NULL; 2246 if (!ffd->rep_sharing_allowed) 2247 return SVN_NO_ERROR; 2248 2249 /* Check and see if we already have a representation somewhere that's 2250 identical to the one we just wrote out. Start with the hash lookup 2251 because it is cheepest. */ 2252 if (reps_hash) 2253 *old_rep = apr_hash_get(reps_hash, 2254 rep->sha1_digest, 2255 APR_SHA1_DIGESTSIZE); 2256 2257 /* If we haven't found anything yet, try harder and consult our DB. */ 2258 if (*old_rep == NULL) 2259 { 2260 svn_checksum_t checksum; 2261 checksum.digest = rep->sha1_digest; 2262 checksum.kind = svn_checksum_sha1; 2263 err = svn_fs_x__get_rep_reference(old_rep, fs, &checksum, result_pool, 2264 scratch_pool); 2265 2266 /* ### Other error codes that we shouldn't mask out? */ 2267 if (err == SVN_NO_ERROR) 2268 { 2269 if (*old_rep) 2270 SVN_ERR(svn_fs_x__check_rep(*old_rep, fs, scratch_pool)); 2271 } 2272 else if (err->apr_err == SVN_ERR_FS_CORRUPT 2273 || SVN_ERROR_IN_CATEGORY(err->apr_err, 2274 SVN_ERR_MALFUNC_CATEGORY_START)) 2275 { 2276 /* Fatal error; don't mask it. 2277 2278 In particular, this block is triggered when the rep-cache refers 2279 to revisions in the future. We signal that as a corruption situation 2280 since, once those revisions are less than youngest (because of more 2281 commits), the rep-cache would be invalid. 2282 */ 2283 SVN_ERR(err); 2284 } 2285 else 2286 { 2287 /* Something's wrong with the rep-sharing index. We can continue 2288 without rep-sharing, but warn. 2289 */ 2290 (fs->warning)(fs->warning_baton, err); 2291 svn_error_clear(err); 2292 *old_rep = NULL; 2293 } 2294 } 2295 2296 /* look for intra-revision matches (usually data reps but not limited 2297 to them in case props happen to look like some data rep) 2298 */ 2299 if (*old_rep == NULL && svn_fs_x__is_txn(rep->id.change_set)) 2300 { 2301 svn_node_kind_t kind; 2302 const char *file_name 2303 = svn_fs_x__path_txn_sha1(fs, 2304 svn_fs_x__get_txn_id(rep->id.change_set), 2305 rep->sha1_digest, scratch_pool); 2306 2307 /* in our txn, is there a rep file named with the wanted SHA1? 2308 If so, read it and use that rep. 2309 */ 2310 SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool)); 2311 if (kind == svn_node_file) 2312 { 2313 svn_stringbuf_t *rep_string; 2314 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, 2315 scratch_pool)); 2316 SVN_ERR(svn_fs_x__parse_representation(old_rep, rep_string, 2317 result_pool, scratch_pool)); 2318 } 2319 } 2320 2321 /* Add information that is missing in the cached data. */ 2322 if (*old_rep) 2323 { 2324 /* Use the old rep for this content. */ 2325 memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest)); 2326 } 2327 2328 return SVN_NO_ERROR; 2329} 2330 2331/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP. 2332 * Use SCRATCH_POOL for temporary allocations. 2333 */ 2334static svn_error_t * 2335digests_final(svn_fs_x__representation_t *rep, 2336 const svn_checksum_ctx_t *md5_ctx, 2337 const svn_checksum_ctx_t *sha1_ctx, 2338 apr_pool_t *scratch_pool) 2339{ 2340 svn_checksum_t *checksum; 2341 2342 SVN_ERR(svn_checksum_final(&checksum, md5_ctx, scratch_pool)); 2343 memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum)); 2344 SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool)); 2345 rep->has_sha1 = checksum != NULL; 2346 if (rep->has_sha1) 2347 memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum)); 2348 2349 return SVN_NO_ERROR; 2350} 2351 2352/* Close handler for the representation write stream. BATON is a 2353 rep_write_baton_t. Writes out a new node-rev that correctly 2354 references the representation we just finished writing. */ 2355static svn_error_t * 2356rep_write_contents_close(void *baton) 2357{ 2358 rep_write_baton_t *b = baton; 2359 svn_fs_x__representation_t *rep; 2360 svn_fs_x__representation_t *old_rep; 2361 apr_off_t offset; 2362 apr_int64_t txn_id; 2363 2364 rep = apr_pcalloc(b->result_pool, sizeof(*rep)); 2365 2366 /* Close our delta stream so the last bits of svndiff are written 2367 out. */ 2368 SVN_ERR(svn_stream_close(b->delta_stream)); 2369 2370 /* Determine the length of the svndiff data. */ 2371 SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool)); 2372 rep->size = offset - b->delta_start; 2373 2374 /* Fill in the rest of the representation field. */ 2375 rep->expanded_size = b->rep_size; 2376 txn_id = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set); 2377 rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id); 2378 2379 /* Finalize the checksum. */ 2380 SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx, 2381 b->result_pool)); 2382 2383 /* Check and see if we already have a representation somewhere that's 2384 identical to the one we just wrote out. */ 2385 SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool, 2386 b->local_pool)); 2387 2388 if (old_rep) 2389 { 2390 /* We need to erase from the protorev the data we just wrote. */ 2391 SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->local_pool)); 2392 2393 /* Use the old rep for this content. */ 2394 b->noderev->data_rep = old_rep; 2395 } 2396 else 2397 { 2398 /* Write out our cosmetic end marker. */ 2399 SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n")); 2400 SVN_ERR(allocate_item_index(&rep->id.number, b->fs, txn_id, 2401 b->local_pool)); 2402 SVN_ERR(store_l2p_index_entry(b->fs, txn_id, b->rep_offset, 2403 rep->id.number, b->local_pool)); 2404 2405 b->noderev->data_rep = rep; 2406 } 2407 2408 SVN_ERR(svn_stream_close(b->rep_stream)); 2409 2410 /* Remove cleanup callback. */ 2411 apr_pool_cleanup_kill(b->local_pool, b, rep_write_cleanup); 2412 2413 /* Write out the new node-rev information. */ 2414 SVN_ERR(svn_fs_x__put_node_revision(b->fs, b->noderev, b->local_pool)); 2415 if (!old_rep) 2416 { 2417 svn_fs_x__p2l_entry_t entry; 2418 svn_fs_x__id_t noderev_id; 2419 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET; 2420 noderev_id.number = rep->id.number; 2421 2422 entry.offset = b->rep_offset; 2423 SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool)); 2424 entry.size = offset - b->rep_offset; 2425 entry.type = SVN_FS_X__ITEM_TYPE_FILE_REP; 2426 entry.item_count = 1; 2427 entry.items = &noderev_id; 2428 entry.fnv1_checksum = b->fnv1a_checksum; 2429 2430 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->local_pool)); 2431 SVN_ERR(store_p2l_index_entry(b->fs, txn_id, &entry, b->local_pool)); 2432 } 2433 2434 SVN_ERR(svn_io_file_close(b->file, b->local_pool)); 2435 SVN_ERR(unlock_proto_rev(b->fs, txn_id, b->lockcookie, b->local_pool)); 2436 svn_pool_destroy(b->local_pool); 2437 2438 return SVN_NO_ERROR; 2439} 2440 2441/* Store a writable stream in *CONTENTS_P, allocated in RESULT_POOL, that 2442 will receive all data written and store it as the file data representation 2443 referenced by NODEREV in filesystem FS. Only appropriate for file data, 2444 not props or directory contents. */ 2445static svn_error_t * 2446set_representation(svn_stream_t **contents_p, 2447 svn_fs_t *fs, 2448 svn_fs_x__noderev_t *noderev, 2449 apr_pool_t *result_pool) 2450{ 2451 rep_write_baton_t *wb; 2452 2453 if (! svn_fs_x__is_txn(noderev->noderev_id.change_set)) 2454 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2455 _("Attempted to write to non-transaction '%s'"), 2456 svn_fs_x__id_unparse(&noderev->noderev_id, 2457 result_pool)->data); 2458 2459 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, result_pool)); 2460 2461 *contents_p = svn_stream_create(wb, result_pool); 2462 svn_stream_set_write(*contents_p, rep_write_contents); 2463 svn_stream_set_close(*contents_p, rep_write_contents_close); 2464 2465 return SVN_NO_ERROR; 2466} 2467 2468svn_error_t * 2469svn_fs_x__set_contents(svn_stream_t **stream, 2470 svn_fs_t *fs, 2471 svn_fs_x__noderev_t *noderev, 2472 apr_pool_t *result_pool) 2473{ 2474 if (noderev->kind != svn_node_file) 2475 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 2476 _("Can't set text contents of a directory")); 2477 2478 return set_representation(stream, fs, noderev, result_pool); 2479} 2480 2481svn_error_t * 2482svn_fs_x__create_successor(svn_fs_t *fs, 2483 svn_fs_x__noderev_t *new_noderev, 2484 const svn_fs_x__id_t *copy_id, 2485 svn_fs_x__txn_id_t txn_id, 2486 apr_pool_t *scratch_pool) 2487{ 2488 new_noderev->copy_id = *copy_id; 2489 new_noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id); 2490 SVN_ERR(allocate_item_index(&new_noderev->noderev_id.number, fs, txn_id, 2491 scratch_pool)); 2492 2493 if (! new_noderev->copyroot_path) 2494 { 2495 new_noderev->copyroot_path 2496 = apr_pstrdup(scratch_pool, new_noderev->created_path); 2497 new_noderev->copyroot_rev 2498 = svn_fs_x__get_revnum(new_noderev->noderev_id.change_set); 2499 } 2500 2501 SVN_ERR(svn_fs_x__put_node_revision(fs, new_noderev, scratch_pool)); 2502 2503 return SVN_NO_ERROR; 2504} 2505 2506svn_error_t * 2507svn_fs_x__set_proplist(svn_fs_t *fs, 2508 svn_fs_x__noderev_t *noderev, 2509 apr_hash_t *proplist, 2510 apr_pool_t *scratch_pool) 2511{ 2512 const svn_fs_x__id_t *id = &noderev->noderev_id; 2513 const char *filename = svn_fs_x__path_txn_node_props(fs, id, scratch_pool, 2514 scratch_pool); 2515 apr_file_t *file; 2516 svn_stream_t *out; 2517 2518 /* Dump the property list to the mutable property file. */ 2519 SVN_ERR(svn_io_file_open(&file, filename, 2520 APR_WRITE | APR_CREATE | APR_TRUNCATE 2521 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool)); 2522 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool); 2523 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, scratch_pool)); 2524 SVN_ERR(svn_io_file_close(file, scratch_pool)); 2525 2526 /* Mark the node-rev's prop rep as mutable, if not already done. */ 2527 if (!noderev->prop_rep 2528 || svn_fs_x__is_revision(noderev->prop_rep->id.change_set)) 2529 { 2530 svn_fs_x__txn_id_t txn_id 2531 = svn_fs_x__get_txn_id(noderev->noderev_id.change_set); 2532 noderev->prop_rep = apr_pcalloc(scratch_pool, sizeof(*noderev->prop_rep)); 2533 noderev->prop_rep->id.change_set = id->change_set; 2534 SVN_ERR(allocate_item_index(&noderev->prop_rep->id.number, fs, 2535 txn_id, scratch_pool)); 2536 SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool)); 2537 } 2538 2539 return SVN_NO_ERROR; 2540} 2541 2542/* This baton is used by the stream created for write_container_rep. */ 2543typedef struct write_container_baton_t 2544{ 2545 svn_stream_t *stream; 2546 2547 apr_size_t size; 2548 2549 svn_checksum_ctx_t *md5_ctx; 2550 svn_checksum_ctx_t *sha1_ctx; 2551} write_container_baton_t; 2552 2553/* The handler for the write_container_rep stream. BATON is a 2554 write_container_baton_t, DATA has the data to write and *LEN is the number 2555 of bytes to write. */ 2556static svn_error_t * 2557write_container_handler(void *baton, 2558 const char *data, 2559 apr_size_t *len) 2560{ 2561 write_container_baton_t *whb = baton; 2562 2563 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 2564 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 2565 2566 SVN_ERR(svn_stream_write(whb->stream, data, len)); 2567 whb->size += *len; 2568 2569 return SVN_NO_ERROR; 2570} 2571 2572/* Callback function type. Write the data provided by BATON into STREAM. */ 2573typedef svn_error_t * 2574(* collection_writer_t)(svn_stream_t *stream, 2575 void *baton, 2576 apr_pool_t *scratch_pool); 2577 2578/* Implement collection_writer_t writing the C string->svn_string_t hash 2579 given as BATON. */ 2580static svn_error_t * 2581write_hash_to_stream(svn_stream_t *stream, 2582 void *baton, 2583 apr_pool_t *scratch_pool) 2584{ 2585 apr_hash_t *hash = baton; 2586 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, scratch_pool)); 2587 2588 return SVN_NO_ERROR; 2589} 2590 2591/* Implement collection_writer_t writing the svn_fs_x__dirent_t* array given 2592 as BATON. */ 2593static svn_error_t * 2594write_directory_to_stream(svn_stream_t *stream, 2595 void *baton, 2596 apr_pool_t *scratch_pool) 2597{ 2598 apr_array_header_t *dir = baton; 2599 SVN_ERR(unparse_dir_entries(dir, stream, scratch_pool)); 2600 2601 return SVN_NO_ERROR; 2602} 2603 2604 2605/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified 2606 text representation to file FILE using WRITER. In the process, record the 2607 total size and the md5 digest in REP and add the representation of type 2608 ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and 2609 REPS_HASH is not NULL, it will be used in addition to the on-disk cache to 2610 find earlier reps with the same content. When such existing reps can be 2611 found, we will truncate the one just written from the file and return the 2612 existing rep. 2613 2614 If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume 2615 that we want to a props representation as the base for our delta. 2616 If FINAL_REVISION is not SVN_INVALID_REVNUM, use it to determine whether 2617 to write to the proto-index files. 2618 Perform temporary allocations in SCRATCH_POOL. 2619 */ 2620static svn_error_t * 2621write_container_delta_rep(svn_fs_x__representation_t *rep, 2622 apr_file_t *file, 2623 void *collection, 2624 collection_writer_t writer, 2625 svn_fs_t *fs, 2626 svn_fs_x__txn_id_t txn_id, 2627 svn_fs_x__noderev_t *noderev, 2628 apr_hash_t *reps_hash, 2629 apr_uint32_t item_type, 2630 svn_revnum_t final_revision, 2631 apr_pool_t *scratch_pool) 2632{ 2633 svn_fs_x__data_t *ffd = fs->fsap_data; 2634 svn_txdelta_window_handler_t diff_wh; 2635 void *diff_whb; 2636 2637 svn_stream_t *file_stream; 2638 svn_stream_t *stream; 2639 svn_fs_x__representation_t *base_rep; 2640 svn_fs_x__representation_t *old_rep; 2641 svn_fs_x__p2l_entry_t entry; 2642 svn_stream_t *source; 2643 svn_fs_x__rep_header_t header = { 0 }; 2644 2645 apr_off_t rep_end = 0; 2646 apr_off_t delta_start = 0; 2647 apr_off_t offset = 0; 2648 2649 write_container_baton_t *whb; 2650 int diff_version = 1; 2651 svn_boolean_t is_props = (item_type == SVN_FS_X__ITEM_TYPE_FILE_PROPS) 2652 || (item_type == SVN_FS_X__ITEM_TYPE_DIR_PROPS); 2653 2654 /* Get the base for this delta. */ 2655 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool)); 2656 SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, FALSE, scratch_pool)); 2657 2658 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool)); 2659 2660 /* Write out the rep header. */ 2661 if (base_rep) 2662 { 2663 header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set); 2664 header.base_item_index = base_rep->id.number; 2665 header.base_length = base_rep->size; 2666 header.type = svn_fs_x__rep_delta; 2667 } 2668 else 2669 { 2670 header.type = svn_fs_x__rep_self_delta; 2671 } 2672 2673 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4( 2674 &entry.fnv1_checksum, 2675 svn_stream_from_aprfile2(file, TRUE, 2676 scratch_pool), 2677 scratch_pool); 2678 SVN_ERR(svn_fs_x__write_rep_header(&header, file_stream, scratch_pool)); 2679 SVN_ERR(svn_fs_x__get_file_offset(&delta_start, file, scratch_pool)); 2680 2681 /* Prepare to write the svndiff data. */ 2682 svn_txdelta_to_svndiff3(&diff_wh, 2683 &diff_whb, 2684 svn_stream_disown(file_stream, scratch_pool), 2685 diff_version, 2686 ffd->delta_compression_level, 2687 scratch_pool); 2688 2689 whb = apr_pcalloc(scratch_pool, sizeof(*whb)); 2690 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, 2691 scratch_pool); 2692 whb->size = 0; 2693 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool); 2694 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool); 2695 2696 /* serialize the hash */ 2697 stream = svn_stream_create(whb, scratch_pool); 2698 svn_stream_set_write(stream, write_container_handler); 2699 2700 SVN_ERR(writer(stream, collection, scratch_pool)); 2701 SVN_ERR(svn_stream_close(whb->stream)); 2702 2703 /* Store the results. */ 2704 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool)); 2705 2706 /* Check and see if we already have a representation somewhere that's 2707 identical to the one we just wrote out. */ 2708 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool, 2709 scratch_pool)); 2710 2711 if (old_rep) 2712 { 2713 SVN_ERR(svn_stream_close(file_stream)); 2714 2715 /* We need to erase from the protorev the data we just wrote. */ 2716 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool)); 2717 2718 /* Use the old rep for this content. */ 2719 memcpy(rep, old_rep, sizeof (*rep)); 2720 } 2721 else 2722 { 2723 svn_fs_x__id_t noderev_id; 2724 2725 /* Write out our cosmetic end marker. */ 2726 SVN_ERR(svn_fs_x__get_file_offset(&rep_end, file, scratch_pool)); 2727 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 2728 SVN_ERR(svn_stream_close(file_stream)); 2729 2730 SVN_ERR(allocate_item_index(&rep->id.number, fs, txn_id, 2731 scratch_pool)); 2732 SVN_ERR(store_l2p_index_entry(fs, txn_id, offset, rep->id.number, 2733 scratch_pool)); 2734 2735 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET; 2736 noderev_id.number = rep->id.number; 2737 2738 entry.offset = offset; 2739 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool)); 2740 entry.size = offset - entry.offset; 2741 entry.type = item_type; 2742 entry.item_count = 1; 2743 entry.items = &noderev_id; 2744 2745 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool)); 2746 2747 /* update the representation */ 2748 rep->expanded_size = whb->size; 2749 rep->size = rep_end - delta_start; 2750 } 2751 2752 return SVN_NO_ERROR; 2753} 2754 2755/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 2756 of (not yet committed) revision REV in FS. Use SCRATCH_POOL for temporary 2757 allocations. 2758 2759 If you change this function, consider updating svn_fs_x__verify() too. 2760 */ 2761static svn_error_t * 2762validate_root_noderev(svn_fs_t *fs, 2763 svn_fs_x__noderev_t *root_noderev, 2764 svn_revnum_t rev, 2765 apr_pool_t *scratch_pool) 2766{ 2767 svn_revnum_t head_revnum = rev-1; 2768 int head_predecessor_count; 2769 2770 SVN_ERR_ASSERT(rev > 0); 2771 2772 /* Compute HEAD_PREDECESSOR_COUNT. */ 2773 { 2774 svn_fs_x__id_t head_root_id; 2775 svn_fs_x__noderev_t *head_root_noderev; 2776 2777 /* Get /@HEAD's noderev. */ 2778 svn_fs_x__init_rev_root(&head_root_id, head_revnum); 2779 SVN_ERR(svn_fs_x__get_node_revision(&head_root_noderev, fs, 2780 &head_root_id, scratch_pool, 2781 scratch_pool)); 2782 2783 head_predecessor_count = head_root_noderev->predecessor_count; 2784 } 2785 2786 /* Check that the root noderev's predecessor count equals REV. 2787 2788 This kind of corruption was seen on svn.apache.org (both on 2789 the root noderev and on other fspaths' noderevs); see 2790 issue #4129. 2791 2792 Normally (rev == root_noderev->predecessor_count), but here we 2793 use a more roundabout check that should only trigger on new instances 2794 of the corruption, rather then trigger on each and every new commit 2795 to a repository that has triggered the bug somewhere in its root 2796 noderev's history. 2797 */ 2798 if ((root_noderev->predecessor_count - head_predecessor_count) 2799 != (rev - head_revnum)) 2800 { 2801 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2802 _("predecessor count for " 2803 "the root node-revision is wrong: " 2804 "found (%d+%ld != %d), committing r%ld"), 2805 head_predecessor_count, 2806 rev - head_revnum, /* This is equal to 1. */ 2807 root_noderev->predecessor_count, 2808 rev); 2809 } 2810 2811 return SVN_NO_ERROR; 2812} 2813 2814/* Given the potentially txn-local id PART, update that to a permanent ID 2815 * based on the REVISION. 2816 */ 2817static void 2818get_final_id(svn_fs_x__id_t *part, 2819 svn_revnum_t revision) 2820{ 2821 if (!svn_fs_x__is_revision(part->change_set)) 2822 part->change_set = svn_fs_x__change_set_by_rev(revision); 2823} 2824 2825/* Copy a node-revision specified by id ID in fileystem FS from a 2826 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 2827 pointer to the new noderev-id. If this is a directory, copy all 2828 children as well. 2829 2830 START_NODE_ID and START_COPY_ID are 2831 the first available node and copy ids for this filesystem, for older 2832 FS formats. 2833 2834 REV is the revision number that this proto-rev-file will represent. 2835 2836 INITIAL_OFFSET is the offset of the proto-rev-file on entry to 2837 commit_body. 2838 2839 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 2840 REPS_POOL) of each data rep that is new in this revision. 2841 2842 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 2843 of the representations of each property rep that is new in this 2844 revision. 2845 2846 AT_ROOT is true if the node revision being written is the root 2847 node-revision. It is only controls additional sanity checking 2848 logic. 2849 2850 Temporary allocations are also from SCRATCH_POOL. */ 2851static svn_error_t * 2852write_final_rev(svn_fs_x__id_t *new_id_p, 2853 apr_file_t *file, 2854 svn_revnum_t rev, 2855 svn_fs_t *fs, 2856 const svn_fs_x__id_t *id, 2857 apr_off_t initial_offset, 2858 apr_array_header_t *reps_to_cache, 2859 apr_hash_t *reps_hash, 2860 apr_pool_t *reps_pool, 2861 svn_boolean_t at_root, 2862 apr_pool_t *scratch_pool) 2863{ 2864 svn_fs_x__noderev_t *noderev; 2865 apr_off_t my_offset; 2866 svn_fs_x__id_t new_id; 2867 svn_fs_x__id_t noderev_id; 2868 svn_fs_x__data_t *ffd = fs->fsap_data; 2869 svn_fs_x__txn_id_t txn_id = svn_fs_x__get_txn_id(id->change_set); 2870 svn_fs_x__p2l_entry_t entry; 2871 svn_fs_x__change_set_t change_set = svn_fs_x__change_set_by_rev(rev); 2872 svn_stream_t *file_stream; 2873 apr_pool_t *subpool; 2874 2875 /* Check to see if this is a transaction node. */ 2876 if (txn_id == SVN_FS_X__INVALID_TXN_ID) 2877 { 2878 svn_fs_x__id_reset(new_id_p); 2879 return SVN_NO_ERROR; 2880 } 2881 2882 subpool = svn_pool_create(scratch_pool); 2883 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool, 2884 subpool)); 2885 2886 if (noderev->kind == svn_node_dir) 2887 { 2888 apr_array_header_t *entries; 2889 int i; 2890 2891 /* This is a directory. Write out all the children first. */ 2892 2893 SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, noderev, scratch_pool, 2894 subpool)); 2895 for (i = 0; i < entries->nelts; ++i) 2896 { 2897 svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i, 2898 svn_fs_x__dirent_t *); 2899 2900 svn_pool_clear(subpool); 2901 SVN_ERR(write_final_rev(&new_id, file, rev, fs, &dirent->id, 2902 initial_offset, reps_to_cache, reps_hash, 2903 reps_pool, FALSE, subpool)); 2904 if ( svn_fs_x__id_used(&new_id) 2905 && (svn_fs_x__get_revnum(new_id.change_set) == rev)) 2906 dirent->id = new_id; 2907 } 2908 2909 if (noderev->data_rep 2910 && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set)) 2911 { 2912 /* Write out the contents of this directory as a text rep. */ 2913 noderev->data_rep->id.change_set = change_set; 2914 SVN_ERR(write_container_delta_rep(noderev->data_rep, file, 2915 entries, 2916 write_directory_to_stream, 2917 fs, txn_id, noderev, NULL, 2918 SVN_FS_X__ITEM_TYPE_DIR_REP, 2919 rev, scratch_pool)); 2920 } 2921 } 2922 else 2923 { 2924 /* This is a file. We should make sure the data rep, if it 2925 exists in a "this" state, gets rewritten to our new revision 2926 num. */ 2927 2928 if (noderev->data_rep 2929 && svn_fs_x__is_txn(noderev->data_rep->id.change_set)) 2930 { 2931 noderev->data_rep->id.change_set = change_set; 2932 } 2933 } 2934 2935 svn_pool_destroy(subpool); 2936 2937 /* Fix up the property reps. */ 2938 if (noderev->prop_rep 2939 && svn_fs_x__is_txn(noderev->prop_rep->id.change_set)) 2940 { 2941 apr_hash_t *proplist; 2942 apr_uint32_t item_type = noderev->kind == svn_node_dir 2943 ? SVN_FS_X__ITEM_TYPE_DIR_PROPS 2944 : SVN_FS_X__ITEM_TYPE_FILE_PROPS; 2945 SVN_ERR(svn_fs_x__get_proplist(&proplist, fs, noderev, scratch_pool, 2946 scratch_pool)); 2947 2948 noderev->prop_rep->id.change_set = change_set; 2949 2950 SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist, 2951 write_hash_to_stream, fs, txn_id, 2952 noderev, reps_hash, item_type, rev, 2953 scratch_pool)); 2954 } 2955 2956 /* Convert our temporary ID into a permanent revision one. */ 2957 get_final_id(&noderev->node_id, rev); 2958 get_final_id(&noderev->copy_id, rev); 2959 get_final_id(&noderev->noderev_id, rev); 2960 2961 if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 2962 noderev->copyroot_rev = rev; 2963 2964 SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool)); 2965 2966 SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, 2967 noderev->noderev_id.number, scratch_pool)); 2968 new_id = noderev->noderev_id; 2969 2970 if (ffd->rep_sharing_allowed) 2971 { 2972 /* Save the data representation's hash in the rep cache. */ 2973 if ( noderev->data_rep && noderev->kind == svn_node_file 2974 && svn_fs_x__get_revnum(noderev->data_rep->id.change_set) == rev) 2975 { 2976 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 2977 APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *) 2978 = svn_fs_x__rep_copy(noderev->data_rep, reps_pool); 2979 } 2980 2981 if ( noderev->prop_rep 2982 && svn_fs_x__get_revnum(noderev->prop_rep->id.change_set) == rev) 2983 { 2984 /* Add new property reps to hash and on-disk cache. */ 2985 svn_fs_x__representation_t *copy 2986 = svn_fs_x__rep_copy(noderev->prop_rep, reps_pool); 2987 2988 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 2989 APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *) = copy; 2990 2991 apr_hash_set(reps_hash, 2992 copy->sha1_digest, 2993 APR_SHA1_DIGESTSIZE, 2994 copy); 2995 } 2996 } 2997 2998 /* don't serialize SHA1 for dirs to disk (waste of space) */ 2999 if (noderev->data_rep && noderev->kind == svn_node_dir) 3000 noderev->data_rep->has_sha1 = FALSE; 3001 3002 /* don't serialize SHA1 for props to disk (waste of space) */ 3003 if (noderev->prop_rep) 3004 noderev->prop_rep->has_sha1 = FALSE; 3005 3006 /* Write out our new node-revision. */ 3007 if (at_root) 3008 SVN_ERR(validate_root_noderev(fs, noderev, rev, scratch_pool)); 3009 3010 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4( 3011 &entry.fnv1_checksum, 3012 svn_stream_from_aprfile2(file, TRUE, 3013 scratch_pool), 3014 scratch_pool); 3015 SVN_ERR(svn_fs_x__write_noderev(file_stream, noderev, scratch_pool)); 3016 SVN_ERR(svn_stream_close(file_stream)); 3017 3018 /* reference the root noderev from the log-to-phys index */ 3019 noderev_id = noderev->noderev_id; 3020 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET; 3021 3022 entry.offset = my_offset; 3023 SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool)); 3024 entry.size = my_offset - entry.offset; 3025 entry.type = SVN_FS_X__ITEM_TYPE_NODEREV; 3026 entry.item_count = 1; 3027 entry.items = &noderev_id; 3028 3029 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool)); 3030 3031 /* Return our ID that references the revision file. */ 3032 *new_id_p = new_id; 3033 3034 return SVN_NO_ERROR; 3035} 3036 3037/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the 3038 permanent rev-file FILE representing NEW_REV in filesystem FS. *OFFSET_P 3039 is set the to offset in the file of the beginning of this information. 3040 NEW_REV is the revision currently being committed. 3041 Perform temporary allocations in SCRATCH_POOL. */ 3042static svn_error_t * 3043write_final_changed_path_info(apr_off_t *offset_p, 3044 apr_file_t *file, 3045 svn_fs_t *fs, 3046 svn_fs_x__txn_id_t txn_id, 3047 apr_hash_t *changed_paths, 3048 svn_revnum_t new_rev, 3049 apr_pool_t *scratch_pool) 3050{ 3051 apr_off_t offset; 3052 svn_stream_t *stream; 3053 svn_fs_x__p2l_entry_t entry; 3054 svn_fs_x__id_t rev_item 3055 = {SVN_INVALID_REVNUM, SVN_FS_X__ITEM_INDEX_CHANGES}; 3056 3057 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool)); 3058 3059 /* write to target file & calculate checksum */ 3060 stream = svn_checksum__wrap_write_stream_fnv1a_32x4(&entry.fnv1_checksum, 3061 svn_stream_from_aprfile2(file, TRUE, scratch_pool), 3062 scratch_pool); 3063 SVN_ERR(svn_fs_x__write_changes(stream, fs, changed_paths, TRUE, 3064 scratch_pool)); 3065 SVN_ERR(svn_stream_close(stream)); 3066 3067 *offset_p = offset; 3068 3069 /* reference changes from the indexes */ 3070 entry.offset = offset; 3071 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool)); 3072 entry.size = offset - entry.offset; 3073 entry.type = SVN_FS_X__ITEM_TYPE_CHANGES; 3074 entry.item_count = 1; 3075 entry.items = &rev_item; 3076 3077 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool)); 3078 SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset, 3079 SVN_FS_X__ITEM_INDEX_CHANGES, scratch_pool)); 3080 3081 return SVN_NO_ERROR; 3082} 3083 3084/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 3085 youngest revision" to NEW_REV, and call svn_fs_x__verify_root() on 3086 NEW_REV's revision root. 3087 3088 Intended to be called as the very last step in a commit before 'current' 3089 is bumped. This implies that we are holding the write lock. */ 3090static svn_error_t * 3091verify_as_revision_before_current_plus_plus(svn_fs_t *fs, 3092 svn_revnum_t new_rev, 3093 apr_pool_t *scratch_pool) 3094{ 3095#ifdef SVN_DEBUG 3096 svn_fs_x__data_t *ffd = fs->fsap_data; 3097 svn_fs_t *ft; /* fs++ == ft */ 3098 svn_fs_root_t *root; 3099 svn_fs_x__data_t *ft_ffd; 3100 apr_hash_t *fs_config; 3101 3102 SVN_ERR_ASSERT(ffd->svn_fs_open_); 3103 3104 /* make sure FT does not simply return data cached by other instances 3105 * but actually retrieves it from disk at least once. 3106 */ 3107 fs_config = apr_hash_make(scratch_pool); 3108 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 3109 svn_uuid_generate(scratch_pool)); 3110 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 3111 fs_config, 3112 scratch_pool, 3113 scratch_pool)); 3114 ft_ffd = ft->fsap_data; 3115 /* Don't let FT consult rep-cache.db, either. */ 3116 ft_ffd->rep_sharing_allowed = FALSE; 3117 3118 /* Time travel! */ 3119 ft_ffd->youngest_rev_cache = new_rev; 3120 3121 SVN_ERR(svn_fs_x__revision_root(&root, ft, new_rev, scratch_pool)); 3122 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 3123 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 3124 SVN_ERR(svn_fs_x__verify_root(root, scratch_pool)); 3125#endif /* SVN_DEBUG */ 3126 3127 return SVN_NO_ERROR; 3128} 3129 3130/* Verify that the user registered with FS has all the locks necessary to 3131 permit all the changes associated with TXN_NAME. 3132 The FS write lock is assumed to be held by the caller. */ 3133static svn_error_t * 3134verify_locks(svn_fs_t *fs, 3135 svn_fs_x__txn_id_t txn_id, 3136 apr_hash_t *changed_paths, 3137 apr_pool_t *scratch_pool) 3138{ 3139 apr_pool_t *iterpool; 3140 apr_array_header_t *changed_paths_sorted; 3141 svn_stringbuf_t *last_recursed = NULL; 3142 int i; 3143 3144 /* Make an array of the changed paths, and sort them depth-first-ily. */ 3145 changed_paths_sorted = svn_sort__hash(changed_paths, 3146 svn_sort_compare_items_as_paths, 3147 scratch_pool); 3148 3149 /* Now, traverse the array of changed paths, verify locks. Note 3150 that if we need to do a recursive verification a path, we'll skip 3151 over children of that path when we get to them. */ 3152 iterpool = svn_pool_create(scratch_pool); 3153 for (i = 0; i < changed_paths_sorted->nelts; i++) 3154 { 3155 const svn_sort__item_t *item; 3156 const char *path; 3157 svn_fs_x__change_t *change; 3158 svn_boolean_t recurse = TRUE; 3159 3160 svn_pool_clear(iterpool); 3161 3162 item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t); 3163 3164 /* Fetch the change associated with our path. */ 3165 path = item->key; 3166 change = item->value; 3167 3168 /* If this path has already been verified as part of a recursive 3169 check of one of its parents, no need to do it again. */ 3170 if (last_recursed 3171 && svn_fspath__skip_ancestor(last_recursed->data, path)) 3172 continue; 3173 3174 /* What does it mean to succeed at lock verification for a given 3175 path? For an existing file or directory getting modified 3176 (text, props), it means we hold the lock on the file or 3177 directory. For paths being added or removed, we need to hold 3178 the locks for that path and any children of that path. 3179 3180 WHEW! We have no reliable way to determine the node kind 3181 of deleted items, but fortunately we are going to do a 3182 recursive check on deleted paths regardless of their kind. */ 3183 if (change->change_kind == svn_fs_path_change_modify) 3184 recurse = FALSE; 3185 SVN_ERR(svn_fs_x__allow_locked_operation(path, fs, recurse, TRUE, 3186 iterpool)); 3187 3188 /* If we just did a recursive check, remember the path we 3189 checked (so children can be skipped). */ 3190 if (recurse) 3191 { 3192 if (! last_recursed) 3193 last_recursed = svn_stringbuf_create(path, scratch_pool); 3194 else 3195 svn_stringbuf_set(last_recursed, path); 3196 } 3197 } 3198 svn_pool_destroy(iterpool); 3199 return SVN_NO_ERROR; 3200} 3201 3202/* Return in *PATH the path to a file containing the properties that 3203 make up the final revision properties file. This involves setting 3204 svn:date and removing any temporary properties associated with the 3205 commit flags. */ 3206static svn_error_t * 3207write_final_revprop(const char **path, 3208 svn_fs_txn_t *txn, 3209 svn_fs_x__txn_id_t txn_id, 3210 apr_pool_t *pool) 3211{ 3212 apr_hash_t *txnprops; 3213 svn_boolean_t final_mods = FALSE; 3214 svn_string_t date; 3215 svn_string_t *client_date; 3216 3217 SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool)); 3218 3219 /* Remove any temporary txn props representing 'flags'. */ 3220 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 3221 { 3222 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL); 3223 final_mods = TRUE; 3224 } 3225 3226 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 3227 { 3228 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL); 3229 final_mods = TRUE; 3230 } 3231 3232 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE); 3233 if (client_date) 3234 { 3235 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL); 3236 final_mods = TRUE; 3237 } 3238 3239 /* Update commit time to ensure that svn:date revprops remain ordered if 3240 requested. */ 3241 if (!client_date || strcmp(client_date->data, "1")) 3242 { 3243 date.data = svn_time_to_cstring(apr_time_now(), pool); 3244 date.len = strlen(date.data); 3245 svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date); 3246 final_mods = TRUE; 3247 } 3248 3249 if (final_mods) 3250 { 3251 SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool)); 3252 *path = svn_fs_x__path_txn_props_final(txn->fs, txn_id, pool); 3253 } 3254 else 3255 { 3256 *path = svn_fs_x__path_txn_props(txn->fs, txn_id, pool); 3257 } 3258 3259 return SVN_NO_ERROR; 3260} 3261 3262svn_error_t * 3263svn_fs_x__add_index_data(svn_fs_t *fs, 3264 apr_file_t *file, 3265 const char *l2p_proto_index, 3266 const char *p2l_proto_index, 3267 svn_revnum_t revision, 3268 apr_pool_t *scratch_pool) 3269{ 3270 apr_off_t l2p_offset; 3271 apr_off_t p2l_offset; 3272 svn_stringbuf_t *footer; 3273 unsigned char footer_length; 3274 svn_checksum_t *l2p_checksum; 3275 svn_checksum_t *p2l_checksum; 3276 3277 /* Append the actual index data to the pack file. */ 3278 l2p_offset = 0; 3279 SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, scratch_pool)); 3280 SVN_ERR(svn_fs_x__l2p_index_append(&l2p_checksum, fs, file, 3281 l2p_proto_index, revision, 3282 scratch_pool, scratch_pool)); 3283 3284 p2l_offset = 0; 3285 SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, scratch_pool)); 3286 SVN_ERR(svn_fs_x__p2l_index_append(&p2l_checksum, fs, file, 3287 p2l_proto_index, revision, 3288 scratch_pool, scratch_pool)); 3289 3290 /* Append footer. */ 3291 footer = svn_fs_x__unparse_footer(l2p_offset, l2p_checksum, 3292 p2l_offset, p2l_checksum, scratch_pool, 3293 scratch_pool); 3294 SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL, 3295 scratch_pool)); 3296 3297 footer_length = footer->len; 3298 SVN_ERR_ASSERT(footer_length == footer->len); 3299 SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, 3300 scratch_pool)); 3301 3302 return SVN_NO_ERROR; 3303} 3304 3305/* Baton used for commit_body below. */ 3306typedef struct commit_baton_t { 3307 svn_revnum_t *new_rev_p; 3308 svn_fs_t *fs; 3309 svn_fs_txn_t *txn; 3310 apr_array_header_t *reps_to_cache; 3311 apr_hash_t *reps_hash; 3312 apr_pool_t *reps_pool; 3313} commit_baton_t; 3314 3315/* The work-horse for svn_fs_x__commit, called with the FS write lock. 3316 This implements the svn_fs_x__with_write_lock() 'body' callback 3317 type. BATON is a 'commit_baton_t *'. */ 3318static svn_error_t * 3319commit_body(void *baton, 3320 apr_pool_t *scratch_pool) 3321{ 3322 commit_baton_t *cb = baton; 3323 svn_fs_x__data_t *ffd = cb->fs->fsap_data; 3324 const char *old_rev_filename, *rev_filename, *proto_filename; 3325 const char *revprop_filename, *final_revprop; 3326 svn_fs_x__id_t root_id, new_root_id; 3327 svn_revnum_t old_rev, new_rev; 3328 apr_file_t *proto_file; 3329 void *proto_file_lockcookie; 3330 apr_off_t initial_offset, changed_path_offset; 3331 svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(cb->txn); 3332 apr_hash_t *changed_paths; 3333 3334 /* Re-Read the current repository format. All our repo upgrade and 3335 config evaluation strategies are such that existing information in 3336 FS and FFD remains valid. 3337 3338 Although we don't recommend upgrading hot repositories, people may 3339 still do it and we must make sure to either handle them gracefully 3340 or to error out. 3341 3342 Committing pre-format 3 txns will fail after upgrade to format 3+ 3343 because the proto-rev cannot be found; no further action needed. 3344 Upgrades from pre-f7 to f7+ means a potential change in addressing 3345 mode for the final rev. We must be sure to detect that cause because 3346 the failure would only manifest once the new revision got committed. 3347 */ 3348 SVN_ERR(svn_fs_x__read_format_file(cb->fs, scratch_pool)); 3349 3350 /* Get the current youngest revision. */ 3351 SVN_ERR(svn_fs_x__youngest_rev(&old_rev, cb->fs, scratch_pool)); 3352 3353 /* Check to make sure this transaction is based off the most recent 3354 revision. */ 3355 if (cb->txn->base_rev != old_rev) 3356 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 3357 _("Transaction out of date")); 3358 3359 /* We need the changes list for verification as well as for writing it 3360 to the final rev file. */ 3361 SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, cb->fs, txn_id, 3362 scratch_pool)); 3363 3364 /* Locks may have been added (or stolen) between the calling of 3365 previous svn_fs.h functions and svn_fs_commit_txn(), so we need 3366 to re-examine every changed-path in the txn and re-verify all 3367 discovered locks. */ 3368 SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, scratch_pool)); 3369 3370 /* We are going to be one better than this puny old revision. */ 3371 new_rev = old_rev + 1; 3372 3373 /* Get a write handle on the proto revision file. */ 3374 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 3375 cb->fs, txn_id, scratch_pool)); 3376 SVN_ERR(svn_fs_x__get_file_offset(&initial_offset, proto_file, 3377 scratch_pool)); 3378 3379 /* Write out all the node-revisions and directory contents. */ 3380 svn_fs_x__init_txn_root(&root_id, txn_id); 3381 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, &root_id, 3382 initial_offset, cb->reps_to_cache, cb->reps_hash, 3383 cb->reps_pool, TRUE, scratch_pool)); 3384 3385 /* Write the changed-path information. */ 3386 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 3387 cb->fs, txn_id, changed_paths, 3388 new_rev, scratch_pool)); 3389 3390 /* Append the index data to the rev file. */ 3391 SVN_ERR(svn_fs_x__add_index_data(cb->fs, proto_file, 3392 svn_fs_x__path_l2p_proto_index(cb->fs, txn_id, scratch_pool), 3393 svn_fs_x__path_p2l_proto_index(cb->fs, txn_id, scratch_pool), 3394 new_rev, scratch_pool)); 3395 3396 SVN_ERR(svn_io_file_flush_to_disk(proto_file, scratch_pool)); 3397 SVN_ERR(svn_io_file_close(proto_file, scratch_pool)); 3398 3399 /* We don't unlock the prototype revision file immediately to avoid a 3400 race with another caller writing to the prototype revision file 3401 before we commit it. */ 3402 3403 /* Create the shard for the rev and revprop file, if we're sharding and 3404 this is the first revision of a new shard. We don't care if this 3405 fails because the shard already existed for some reason. */ 3406 if (new_rev % ffd->max_files_per_dir == 0) 3407 { 3408 /* Create the revs shard. */ 3409 { 3410 const char *new_dir 3411 = svn_fs_x__path_rev_shard(cb->fs, new_rev, scratch_pool); 3412 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, 3413 scratch_pool); 3414 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3415 return svn_error_trace(err); 3416 svn_error_clear(err); 3417 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3418 PATH_REVS_DIR, 3419 scratch_pool), 3420 new_dir, scratch_pool)); 3421 } 3422 3423 /* Create the revprops shard. */ 3424 SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev)); 3425 { 3426 const char *new_dir 3427 = svn_fs_x__path_revprops_shard(cb->fs, new_rev, scratch_pool); 3428 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, 3429 scratch_pool); 3430 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3431 return svn_error_trace(err); 3432 svn_error_clear(err); 3433 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3434 PATH_REVPROPS_DIR, 3435 scratch_pool), 3436 new_dir, scratch_pool)); 3437 } 3438 } 3439 3440 /* Move the finished rev file into place. 3441 3442 ### This "breaks" the transaction by removing the protorev file 3443 ### but the revision is not yet complete. If this commit does 3444 ### not complete for any reason the transaction will be lost. */ 3445 old_rev_filename = svn_fs_x__path_rev_absolute(cb->fs, old_rev, 3446 scratch_pool); 3447 3448 rev_filename = svn_fs_x__path_rev(cb->fs, new_rev, scratch_pool); 3449 proto_filename = svn_fs_x__path_txn_proto_rev(cb->fs, txn_id, 3450 scratch_pool); 3451 SVN_ERR(svn_fs_x__move_into_place(proto_filename, rev_filename, 3452 old_rev_filename, scratch_pool)); 3453 3454 /* Now that we've moved the prototype revision file out of the way, 3455 we can unlock it (since further attempts to write to the file 3456 will fail as it no longer exists). We must do this so that we can 3457 remove the transaction directory later. */ 3458 SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, 3459 scratch_pool)); 3460 3461 /* Move the revprops file into place. */ 3462 SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev)); 3463 SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, 3464 scratch_pool)); 3465 final_revprop = svn_fs_x__path_revprops(cb->fs, new_rev, scratch_pool); 3466 SVN_ERR(svn_fs_x__move_into_place(revprop_filename, final_revprop, 3467 old_rev_filename, scratch_pool)); 3468 3469 /* Update the 'current' file. */ 3470 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, 3471 scratch_pool)); 3472 SVN_ERR(svn_fs_x__write_current(cb->fs, new_rev, scratch_pool)); 3473 3474 /* At this point the new revision is committed and globally visible 3475 so let the caller know it succeeded by giving it the new revision 3476 number, which fulfills svn_fs_commit_txn() contract. Any errors 3477 after this point do not change the fact that a new revision was 3478 created. */ 3479 *cb->new_rev_p = new_rev; 3480 3481 ffd->youngest_rev_cache = new_rev; 3482 3483 /* Remove this transaction directory. */ 3484 SVN_ERR(svn_fs_x__purge_txn(cb->fs, cb->txn->id, scratch_pool)); 3485 3486 return SVN_NO_ERROR; 3487} 3488 3489/* Add the representations in REPS_TO_CACHE (an array of 3490 * svn_fs_x__representation_t *) to the rep-cache database of FS. */ 3491static svn_error_t * 3492write_reps_to_cache(svn_fs_t *fs, 3493 const apr_array_header_t *reps_to_cache, 3494 apr_pool_t *scratch_pool) 3495{ 3496 int i; 3497 3498 for (i = 0; i < reps_to_cache->nelts; i++) 3499 { 3500 svn_fs_x__representation_t *rep 3501 = APR_ARRAY_IDX(reps_to_cache, i, svn_fs_x__representation_t *); 3502 3503 /* FALSE because we don't care if another parallel commit happened to 3504 * collide with us. (Non-parallel collisions will not be detected.) */ 3505 SVN_ERR(svn_fs_x__set_rep_reference(fs, rep, scratch_pool)); 3506 } 3507 3508 return SVN_NO_ERROR; 3509} 3510 3511svn_error_t * 3512svn_fs_x__commit(svn_revnum_t *new_rev_p, 3513 svn_fs_t *fs, 3514 svn_fs_txn_t *txn, 3515 apr_pool_t *scratch_pool) 3516{ 3517 commit_baton_t cb; 3518 svn_fs_x__data_t *ffd = fs->fsap_data; 3519 3520 cb.new_rev_p = new_rev_p; 3521 cb.fs = fs; 3522 cb.txn = txn; 3523 3524 if (ffd->rep_sharing_allowed) 3525 { 3526 cb.reps_to_cache = apr_array_make(scratch_pool, 5, 3527 sizeof(svn_fs_x__representation_t *)); 3528 cb.reps_hash = apr_hash_make(scratch_pool); 3529 cb.reps_pool = scratch_pool; 3530 } 3531 else 3532 { 3533 cb.reps_to_cache = NULL; 3534 cb.reps_hash = NULL; 3535 cb.reps_pool = NULL; 3536 } 3537 3538 SVN_ERR(svn_fs_x__with_write_lock(fs, commit_body, &cb, scratch_pool)); 3539 3540 /* At this point, *NEW_REV_P has been set, so errors below won't affect 3541 the success of the commit. (See svn_fs_commit_txn().) */ 3542 3543 if (ffd->rep_sharing_allowed) 3544 { 3545 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 3546 3547 /* Write new entries to the rep-sharing database. 3548 * 3549 * We use an sqlite transaction to speed things up; 3550 * see <http://www.sqlite.org/faq.html#q19>. 3551 */ 3552 /* ### A commit that touches thousands of files will starve other 3553 (reader/writer) commits for the duration of the below call. 3554 Maybe write in batches? */ 3555 SVN_SQLITE__WITH_TXN( 3556 write_reps_to_cache(fs, cb.reps_to_cache, scratch_pool), 3557 ffd->rep_cache_db); 3558 } 3559 3560 return SVN_NO_ERROR; 3561} 3562 3563 3564svn_error_t * 3565svn_fs_x__list_transactions(apr_array_header_t **names_p, 3566 svn_fs_t *fs, 3567 apr_pool_t *pool) 3568{ 3569 const char *txn_dir; 3570 apr_hash_t *dirents; 3571 apr_hash_index_t *hi; 3572 apr_array_header_t *names; 3573 apr_size_t ext_len = strlen(PATH_EXT_TXN); 3574 3575 names = apr_array_make(pool, 1, sizeof(const char *)); 3576 3577 /* Get the transactions directory. */ 3578 txn_dir = svn_fs_x__path_txns_dir(fs, pool); 3579 3580 /* Now find a listing of this directory. */ 3581 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 3582 3583 /* Loop through all the entries and return anything that ends with '.txn'. */ 3584 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 3585 { 3586 const char *name = apr_hash_this_key(hi); 3587 apr_ssize_t klen = apr_hash_this_key_len(hi); 3588 const char *id; 3589 3590 /* The name must end with ".txn" to be considered a transaction. */ 3591 if ((apr_size_t) klen <= ext_len 3592 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 3593 continue; 3594 3595 /* Truncate the ".txn" extension and store the ID. */ 3596 id = apr_pstrndup(pool, name, strlen(name) - ext_len); 3597 APR_ARRAY_PUSH(names, const char *) = id; 3598 } 3599 3600 *names_p = names; 3601 3602 return SVN_NO_ERROR; 3603} 3604 3605svn_error_t * 3606svn_fs_x__open_txn(svn_fs_txn_t **txn_p, 3607 svn_fs_t *fs, 3608 const char *name, 3609 apr_pool_t *pool) 3610{ 3611 svn_fs_txn_t *txn; 3612 fs_txn_data_t *ftd; 3613 svn_node_kind_t kind; 3614 svn_fs_x__transaction_t *local_txn; 3615 svn_fs_x__txn_id_t txn_id; 3616 3617 SVN_ERR(svn_fs_x__txn_by_name(&txn_id, name)); 3618 3619 /* First check to see if the directory exists. */ 3620 SVN_ERR(svn_io_check_path(svn_fs_x__path_txn_dir(fs, txn_id, pool), 3621 &kind, pool)); 3622 3623 /* Did we find it? */ 3624 if (kind != svn_node_dir) 3625 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 3626 _("No such transaction '%s'"), 3627 name); 3628 3629 txn = apr_pcalloc(pool, sizeof(*txn)); 3630 ftd = apr_pcalloc(pool, sizeof(*ftd)); 3631 ftd->txn_id = txn_id; 3632 3633 /* Read in the root node of this transaction. */ 3634 txn->id = apr_pstrdup(pool, name); 3635 txn->fs = fs; 3636 3637 SVN_ERR(svn_fs_x__get_txn(&local_txn, fs, txn_id, pool)); 3638 3639 txn->base_rev = local_txn->base_rev; 3640 3641 txn->vtable = &txn_vtable; 3642 txn->fsap_data = ftd; 3643 *txn_p = txn; 3644 3645 return SVN_NO_ERROR; 3646} 3647 3648svn_error_t * 3649svn_fs_x__txn_proplist(apr_hash_t **table_p, 3650 svn_fs_txn_t *txn, 3651 apr_pool_t *pool) 3652{ 3653 apr_hash_t *proplist = apr_hash_make(pool); 3654 SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_x__txn_get_id(txn), 3655 pool)); 3656 *table_p = proplist; 3657 3658 return SVN_NO_ERROR; 3659} 3660 3661svn_error_t * 3662svn_fs_x__delete_node_revision(svn_fs_t *fs, 3663 const svn_fs_x__id_t *id, 3664 apr_pool_t *scratch_pool) 3665{ 3666 svn_fs_x__noderev_t *noderev; 3667 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool, 3668 scratch_pool)); 3669 3670 /* Delete any mutable property representation. */ 3671 if (noderev->prop_rep 3672 && svn_fs_x__is_txn(noderev->prop_rep->id.change_set)) 3673 SVN_ERR(svn_io_remove_file2(svn_fs_x__path_txn_node_props(fs, id, 3674 scratch_pool, 3675 scratch_pool), 3676 FALSE, scratch_pool)); 3677 3678 /* Delete any mutable data representation. */ 3679 if (noderev->data_rep 3680 && svn_fs_x__is_txn(noderev->data_rep->id.change_set) 3681 && noderev->kind == svn_node_dir) 3682 { 3683 svn_fs_x__data_t *ffd = fs->fsap_data; 3684 const svn_fs_x__id_t *key = id; 3685 3686 SVN_ERR(svn_io_remove_file2( 3687 svn_fs_x__path_txn_node_children(fs, id, scratch_pool, 3688 scratch_pool), 3689 FALSE, scratch_pool)); 3690 3691 /* remove the corresponding entry from the cache, if such exists */ 3692 SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, scratch_pool)); 3693 } 3694 3695 return svn_io_remove_file2(svn_fs_x__path_txn_node_rev(fs, id, 3696 scratch_pool, 3697 scratch_pool), 3698 FALSE, scratch_pool); 3699} 3700 3701 3702 3703/*** Transactions ***/ 3704 3705svn_error_t * 3706svn_fs_x__get_base_rev(svn_revnum_t *revnum, 3707 svn_fs_t *fs, 3708 svn_fs_x__txn_id_t txn_id, 3709 apr_pool_t *scratch_pool) 3710{ 3711 svn_fs_x__transaction_t *txn; 3712 SVN_ERR(svn_fs_x__get_txn(&txn, fs, txn_id, scratch_pool)); 3713 *revnum = txn->base_rev; 3714 3715 return SVN_NO_ERROR; 3716} 3717 3718 3719/* Generic transaction operations. */ 3720 3721svn_error_t * 3722svn_fs_x__txn_prop(svn_string_t **value_p, 3723 svn_fs_txn_t *txn, 3724 const char *propname, 3725 apr_pool_t *pool) 3726{ 3727 apr_hash_t *table; 3728 svn_fs_t *fs = txn->fs; 3729 3730 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3731 SVN_ERR(svn_fs_x__txn_proplist(&table, txn, pool)); 3732 3733 *value_p = svn_hash_gets(table, propname); 3734 3735 return SVN_NO_ERROR; 3736} 3737 3738svn_error_t * 3739svn_fs_x__begin_txn(svn_fs_txn_t **txn_p, 3740 svn_fs_t *fs, 3741 svn_revnum_t rev, 3742 apr_uint32_t flags, 3743 apr_pool_t *result_pool, 3744 apr_pool_t *scratch_pool) 3745{ 3746 svn_string_t date; 3747 fs_txn_data_t *ftd; 3748 apr_hash_t *props = apr_hash_make(scratch_pool); 3749 3750 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3751 3752 SVN_ERR(create_txn(txn_p, fs, rev, result_pool, scratch_pool)); 3753 3754 /* Put a datestamp on the newly created txn, so we always know 3755 exactly how old it is. (This will help sysadmins identify 3756 long-abandoned txns that may need to be manually removed.) When 3757 a txn is promoted to a revision, this property will be 3758 automatically overwritten with a revision datestamp. */ 3759 date.data = svn_time_to_cstring(apr_time_now(), scratch_pool); 3760 date.len = strlen(date.data); 3761 3762 svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date); 3763 3764 /* Set temporary txn props that represent the requested 'flags' 3765 behaviors. */ 3766 if (flags & SVN_FS_TXN_CHECK_OOD) 3767 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD, 3768 svn_string_create("true", scratch_pool)); 3769 3770 if (flags & SVN_FS_TXN_CHECK_LOCKS) 3771 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS, 3772 svn_string_create("true", scratch_pool)); 3773 3774 if (flags & SVN_FS_TXN_CLIENT_DATE) 3775 svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE, 3776 svn_string_create("0", scratch_pool)); 3777 3778 ftd = (*txn_p)->fsap_data; 3779 SVN_ERR(set_txn_proplist(fs, ftd->txn_id, props, FALSE, scratch_pool)); 3780 3781 return SVN_NO_ERROR; 3782} 3783