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