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