transaction.c revision 299742
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_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool)); 2338 SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry, 2339 b->scratch_pool)); 2340 } 2341 2342 SVN_ERR(svn_io_file_close(b->file, b->scratch_pool)); 2343 SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie, 2344 b->scratch_pool)); 2345 svn_pool_destroy(b->scratch_pool); 2346 2347 return SVN_NO_ERROR; 2348} 2349 2350/* Store a writable stream in *CONTENTS_P that will receive all data 2351 written and store it as the file data representation referenced by 2352 NODEREV in filesystem FS. Perform temporary allocations in 2353 POOL. Only appropriate for file data, not props or directory 2354 contents. */ 2355static svn_error_t * 2356set_representation(svn_stream_t **contents_p, 2357 svn_fs_t *fs, 2358 node_revision_t *noderev, 2359 apr_pool_t *pool) 2360{ 2361 struct rep_write_baton *wb; 2362 2363 if (! svn_fs_fs__id_is_txn(noderev->id)) 2364 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2365 _("Attempted to write to non-transaction '%s'"), 2366 svn_fs_fs__id_unparse(noderev->id, pool)->data); 2367 2368 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool)); 2369 2370 *contents_p = svn_stream_create(wb, pool); 2371 svn_stream_set_write(*contents_p, rep_write_contents); 2372 svn_stream_set_close(*contents_p, rep_write_contents_close); 2373 2374 return SVN_NO_ERROR; 2375} 2376 2377svn_error_t * 2378svn_fs_fs__set_contents(svn_stream_t **stream, 2379 svn_fs_t *fs, 2380 node_revision_t *noderev, 2381 apr_pool_t *pool) 2382{ 2383 if (noderev->kind != svn_node_file) 2384 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 2385 _("Can't set text contents of a directory")); 2386 2387 return set_representation(stream, fs, noderev, pool); 2388} 2389 2390svn_error_t * 2391svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p, 2392 svn_fs_t *fs, 2393 const svn_fs_id_t *old_idp, 2394 node_revision_t *new_noderev, 2395 const svn_fs_fs__id_part_t *copy_id, 2396 const svn_fs_fs__id_part_t *txn_id, 2397 apr_pool_t *pool) 2398{ 2399 const svn_fs_id_t *id; 2400 2401 if (! copy_id) 2402 copy_id = svn_fs_fs__id_copy_id(old_idp); 2403 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id, 2404 txn_id, pool); 2405 2406 new_noderev->id = id; 2407 2408 if (! new_noderev->copyroot_path) 2409 { 2410 new_noderev->copyroot_path = apr_pstrdup(pool, 2411 new_noderev->created_path); 2412 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id); 2413 } 2414 2415 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE, 2416 pool)); 2417 2418 *new_id_p = id; 2419 2420 return SVN_NO_ERROR; 2421} 2422 2423svn_error_t * 2424svn_fs_fs__set_proplist(svn_fs_t *fs, 2425 node_revision_t *noderev, 2426 apr_hash_t *proplist, 2427 apr_pool_t *pool) 2428{ 2429 const char *filename 2430 = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool); 2431 apr_file_t *file; 2432 svn_stream_t *out; 2433 2434 /* Dump the property list to the mutable property file. */ 2435 SVN_ERR(svn_io_file_open(&file, filename, 2436 APR_WRITE | APR_CREATE | APR_TRUNCATE 2437 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2438 out = svn_stream_from_aprfile2(file, TRUE, pool); 2439 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool)); 2440 SVN_ERR(svn_io_file_close(file, pool)); 2441 2442 /* Mark the node-rev's prop rep as mutable, if not already done. */ 2443 if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep)) 2444 { 2445 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep)); 2446 noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id); 2447 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, 2448 pool)); 2449 } 2450 2451 return SVN_NO_ERROR; 2452} 2453 2454/* This baton is used by the stream created for write_container_rep. */ 2455struct write_container_baton 2456{ 2457 svn_stream_t *stream; 2458 2459 apr_size_t size; 2460 2461 svn_checksum_ctx_t *md5_ctx; 2462 svn_checksum_ctx_t *sha1_ctx; 2463}; 2464 2465/* The handler for the write_container_rep stream. BATON is a 2466 write_container_baton, DATA has the data to write and *LEN is the number 2467 of bytes to write. */ 2468static svn_error_t * 2469write_container_handler(void *baton, 2470 const char *data, 2471 apr_size_t *len) 2472{ 2473 struct write_container_baton *whb = baton; 2474 2475 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 2476 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 2477 2478 SVN_ERR(svn_stream_write(whb->stream, data, len)); 2479 whb->size += *len; 2480 2481 return SVN_NO_ERROR; 2482} 2483 2484/* Callback function type. Write the data provided by BATON into STREAM. */ 2485typedef svn_error_t * 2486(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool); 2487 2488/* Implement collection_writer_t writing the C string->svn_string_t hash 2489 given as BATON. */ 2490static svn_error_t * 2491write_hash_to_stream(svn_stream_t *stream, 2492 void *baton, 2493 apr_pool_t *pool) 2494{ 2495 apr_hash_t *hash = baton; 2496 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 2497 2498 return SVN_NO_ERROR; 2499} 2500 2501/* Implement collection_writer_t writing the svn_fs_dirent_t* array given 2502 as BATON. */ 2503static svn_error_t * 2504write_directory_to_stream(svn_stream_t *stream, 2505 void *baton, 2506 apr_pool_t *pool) 2507{ 2508 apr_array_header_t *dir = baton; 2509 SVN_ERR(unparse_dir_entries(dir, stream, pool)); 2510 2511 return SVN_NO_ERROR; 2512} 2513 2514/* Write out the COLLECTION as a text representation to file FILE using 2515 WRITER. In the process, record position, the total size of the dump and 2516 MD5 as well as SHA1 in REP. Add the representation of type ITEM_TYPE to 2517 the indexes if necessary. If rep sharing has been enabled and REPS_HASH 2518 is not NULL, it will be used in addition to the on-disk cache to find 2519 earlier reps with the same content. When such existing reps can be 2520 found, we will truncate the one just written from the file and return 2521 the existing rep. Perform temporary allocations in SCRATCH_POOL. */ 2522static svn_error_t * 2523write_container_rep(representation_t *rep, 2524 apr_file_t *file, 2525 void *collection, 2526 collection_writer_t writer, 2527 svn_fs_t *fs, 2528 apr_hash_t *reps_hash, 2529 apr_uint32_t item_type, 2530 apr_pool_t *scratch_pool) 2531{ 2532 svn_stream_t *stream; 2533 struct write_container_baton *whb; 2534 svn_checksum_ctx_t *fnv1a_checksum_ctx; 2535 representation_t *old_rep; 2536 apr_off_t offset = 0; 2537 2538 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2539 2540 whb = apr_pcalloc(scratch_pool, sizeof(*whb)); 2541 2542 whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 2543 svn_stream_from_aprfile2(file, TRUE, 2544 scratch_pool), 2545 scratch_pool); 2546 whb->size = 0; 2547 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool); 2548 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool); 2549 2550 stream = svn_stream_create(whb, scratch_pool); 2551 svn_stream_set_write(stream, write_container_handler); 2552 2553 SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n")); 2554 2555 SVN_ERR(writer(stream, collection, scratch_pool)); 2556 2557 /* Store the results. */ 2558 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool)); 2559 2560 /* Check and see if we already have a representation somewhere that's 2561 identical to the one we just wrote out. */ 2562 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool, 2563 scratch_pool)); 2564 2565 if (old_rep) 2566 { 2567 /* We need to erase from the protorev the data we just wrote. */ 2568 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool)); 2569 2570 /* Use the old rep for this content. */ 2571 memcpy(rep, old_rep, sizeof (*rep)); 2572 } 2573 else 2574 { 2575 svn_fs_fs__p2l_entry_t entry; 2576 2577 /* Write out our cosmetic end marker. */ 2578 SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n")); 2579 2580 SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id, 2581 offset, scratch_pool)); 2582 2583 entry.offset = offset; 2584 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2585 entry.size = offset - entry.offset; 2586 entry.type = item_type; 2587 entry.item.revision = SVN_INVALID_REVNUM; 2588 entry.item.number = rep->item_index; 2589 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 2590 fnv1a_checksum_ctx, 2591 scratch_pool)); 2592 2593 SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool)); 2594 2595 /* update the representation */ 2596 rep->size = whb->size; 2597 rep->expanded_size = whb->size; 2598 } 2599 2600 return SVN_NO_ERROR; 2601} 2602 2603/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified 2604 text representation to file FILE using WRITER. In the process, record the 2605 total size and the md5 digest in REP and add the representation of type 2606 ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and 2607 REPS_HASH is not NULL, it will be used in addition to the on-disk cache to 2608 find earlier reps with the same content. When such existing reps can be 2609 found, we will truncate the one just written from the file and return the 2610 existing rep. 2611 2612 If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume 2613 that we want to a props representation as the base for our delta. 2614 Perform temporary allocations in SCRATCH_POOL. 2615 */ 2616static svn_error_t * 2617write_container_delta_rep(representation_t *rep, 2618 apr_file_t *file, 2619 void *collection, 2620 collection_writer_t writer, 2621 svn_fs_t *fs, 2622 node_revision_t *noderev, 2623 apr_hash_t *reps_hash, 2624 apr_uint32_t item_type, 2625 apr_pool_t *scratch_pool) 2626{ 2627 svn_txdelta_window_handler_t diff_wh; 2628 void *diff_whb; 2629 2630 svn_stream_t *file_stream; 2631 svn_stream_t *stream; 2632 representation_t *base_rep; 2633 representation_t *old_rep; 2634 svn_checksum_ctx_t *fnv1a_checksum_ctx; 2635 svn_stream_t *source; 2636 svn_fs_fs__rep_header_t header = { 0 }; 2637 2638 apr_off_t rep_end = 0; 2639 apr_off_t delta_start = 0; 2640 apr_off_t offset = 0; 2641 2642 struct write_container_baton *whb; 2643 fs_fs_data_t *ffd = fs->fsap_data; 2644 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 2645 svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS) 2646 || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS); 2647 2648 /* Get the base for this delta. */ 2649 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool)); 2650 SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool)); 2651 2652 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2653 2654 /* Write out the rep header. */ 2655 if (base_rep) 2656 { 2657 header.base_revision = base_rep->revision; 2658 header.base_item_index = base_rep->item_index; 2659 header.base_length = base_rep->size; 2660 header.type = svn_fs_fs__rep_delta; 2661 } 2662 else 2663 { 2664 header.type = svn_fs_fs__rep_self_delta; 2665 } 2666 2667 file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 2668 svn_stream_from_aprfile2(file, TRUE, 2669 scratch_pool), 2670 scratch_pool); 2671 SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool)); 2672 SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool)); 2673 2674 /* Prepare to write the svndiff data. */ 2675 svn_txdelta_to_svndiff3(&diff_wh, 2676 &diff_whb, 2677 file_stream, 2678 diff_version, 2679 ffd->delta_compression_level, 2680 scratch_pool); 2681 2682 whb = apr_pcalloc(scratch_pool, sizeof(*whb)); 2683 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, 2684 scratch_pool); 2685 whb->size = 0; 2686 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool); 2687 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool); 2688 2689 /* serialize the hash */ 2690 stream = svn_stream_create(whb, scratch_pool); 2691 svn_stream_set_write(stream, write_container_handler); 2692 2693 SVN_ERR(writer(stream, collection, scratch_pool)); 2694 SVN_ERR(svn_stream_close(whb->stream)); 2695 2696 /* Store the results. */ 2697 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool)); 2698 2699 /* Check and see if we already have a representation somewhere that's 2700 identical to the one we just wrote out. */ 2701 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool, 2702 scratch_pool)); 2703 2704 if (old_rep) 2705 { 2706 /* We need to erase from the protorev the data we just wrote. */ 2707 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool)); 2708 2709 /* Use the old rep for this content. */ 2710 memcpy(rep, old_rep, sizeof (*rep)); 2711 } 2712 else 2713 { 2714 svn_fs_fs__p2l_entry_t entry; 2715 2716 /* Write out our cosmetic end marker. */ 2717 SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool)); 2718 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 2719 2720 SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id, 2721 offset, scratch_pool)); 2722 2723 entry.offset = offset; 2724 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2725 entry.size = offset - entry.offset; 2726 entry.type = item_type; 2727 entry.item.revision = SVN_INVALID_REVNUM; 2728 entry.item.number = rep->item_index; 2729 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 2730 fnv1a_checksum_ctx, 2731 scratch_pool)); 2732 2733 SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool)); 2734 2735 /* update the representation */ 2736 rep->expanded_size = whb->size; 2737 rep->size = rep_end - delta_start; 2738 } 2739 2740 return SVN_NO_ERROR; 2741} 2742 2743/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 2744 of (not yet committed) revision REV in FS. Use POOL for temporary 2745 allocations. 2746 2747 If you change this function, consider updating svn_fs_fs__verify() too. 2748 */ 2749static svn_error_t * 2750validate_root_noderev(svn_fs_t *fs, 2751 node_revision_t *root_noderev, 2752 svn_revnum_t rev, 2753 apr_pool_t *pool) 2754{ 2755 svn_revnum_t head_revnum = rev-1; 2756 int head_predecessor_count; 2757 2758 SVN_ERR_ASSERT(rev > 0); 2759 2760 /* Compute HEAD_PREDECESSOR_COUNT. */ 2761 { 2762 svn_fs_root_t *head_revision; 2763 const svn_fs_id_t *head_root_id; 2764 node_revision_t *head_root_noderev; 2765 2766 /* Get /@HEAD's noderev. */ 2767 SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool)); 2768 SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool)); 2769 SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id, 2770 pool, pool)); 2771 head_predecessor_count = head_root_noderev->predecessor_count; 2772 } 2773 2774 /* Check that the root noderev's predecessor count equals REV. 2775 2776 This kind of corruption was seen on svn.apache.org (both on 2777 the root noderev and on other fspaths' noderevs); see 2778 issue #4129. 2779 2780 Normally (rev == root_noderev->predecessor_count), but here we 2781 use a more roundabout check that should only trigger on new instances 2782 of the corruption, rather then trigger on each and every new commit 2783 to a repository that has triggered the bug somewhere in its root 2784 noderev's history. 2785 */ 2786 if (root_noderev->predecessor_count != -1 2787 && (root_noderev->predecessor_count - head_predecessor_count) 2788 != (rev - head_revnum)) 2789 { 2790 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2791 _("predecessor count for " 2792 "the root node-revision is wrong: " 2793 "found (%d+%ld != %d), committing r%ld"), 2794 head_predecessor_count, 2795 rev - head_revnum, /* This is equal to 1. */ 2796 root_noderev->predecessor_count, 2797 rev); 2798 } 2799 2800 return SVN_NO_ERROR; 2801} 2802 2803/* Given the potentially txn-local id PART, update that to a permanent ID 2804 * based on the REVISION currently being written and the START_ID for that 2805 * revision. Use the repo FORMAT to decide which implementation to use. 2806 */ 2807static void 2808get_final_id(svn_fs_fs__id_part_t *part, 2809 svn_revnum_t revision, 2810 apr_uint64_t start_id, 2811 int format) 2812{ 2813 if (part->revision == SVN_INVALID_REVNUM) 2814 { 2815 if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 2816 { 2817 part->revision = revision; 2818 } 2819 else 2820 { 2821 part->revision = 0; 2822 part->number += start_id; 2823 } 2824 } 2825} 2826 2827/* Copy a node-revision specified by id ID in fileystem FS from a 2828 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 2829 pointer to the new node-id which will be allocated in POOL. 2830 If this is a directory, copy all children as well. 2831 2832 START_NODE_ID and START_COPY_ID are 2833 the first available node and copy ids for this filesystem, for older 2834 FS formats. 2835 2836 REV is the revision number that this proto-rev-file will represent. 2837 2838 INITIAL_OFFSET is the offset of the proto-rev-file on entry to 2839 commit_body. 2840 2841 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 2842 REPS_POOL) of each data rep that is new in this revision. 2843 2844 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 2845 of the representations of each property rep that is new in this 2846 revision. 2847 2848 AT_ROOT is true if the node revision being written is the root 2849 node-revision. It is only controls additional sanity checking 2850 logic. 2851 2852 Temporary allocations are also from POOL. */ 2853static svn_error_t * 2854write_final_rev(const svn_fs_id_t **new_id_p, 2855 apr_file_t *file, 2856 svn_revnum_t rev, 2857 svn_fs_t *fs, 2858 const svn_fs_id_t *id, 2859 apr_uint64_t start_node_id, 2860 apr_uint64_t start_copy_id, 2861 apr_off_t initial_offset, 2862 apr_array_header_t *reps_to_cache, 2863 apr_hash_t *reps_hash, 2864 apr_pool_t *reps_pool, 2865 svn_boolean_t at_root, 2866 apr_pool_t *pool) 2867{ 2868 node_revision_t *noderev; 2869 apr_off_t my_offset; 2870 const svn_fs_id_t *new_id; 2871 svn_fs_fs__id_part_t node_id, copy_id, rev_item; 2872 fs_fs_data_t *ffd = fs->fsap_data; 2873 const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id); 2874 svn_stream_t *file_stream; 2875 svn_checksum_ctx_t *fnv1a_checksum_ctx; 2876 apr_pool_t *subpool; 2877 2878 *new_id_p = NULL; 2879 2880 /* Check to see if this is a transaction node. */ 2881 if (! svn_fs_fs__id_is_txn(id)) 2882 return SVN_NO_ERROR; 2883 2884 subpool = svn_pool_create(pool); 2885 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool)); 2886 2887 if (noderev->kind == svn_node_dir) 2888 { 2889 apr_array_header_t *entries; 2890 int i; 2891 2892 /* This is a directory. Write out all the children first. */ 2893 2894 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, 2895 subpool)); 2896 for (i = 0; i < entries->nelts; ++i) 2897 { 2898 svn_fs_dirent_t *dirent 2899 = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *); 2900 2901 svn_pool_clear(subpool); 2902 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id, 2903 start_node_id, start_copy_id, initial_offset, 2904 reps_to_cache, reps_hash, reps_pool, FALSE, 2905 subpool)); 2906 if (new_id && (svn_fs_fs__id_rev(new_id) == rev)) 2907 dirent->id = svn_fs_fs__id_copy(new_id, pool); 2908 } 2909 2910 if (noderev->data_rep && is_txn_rep(noderev->data_rep)) 2911 { 2912 /* Write out the contents of this directory as a text rep. */ 2913 noderev->data_rep->revision = rev; 2914 if (ffd->deltify_directories) 2915 SVN_ERR(write_container_delta_rep(noderev->data_rep, file, 2916 entries, 2917 write_directory_to_stream, 2918 fs, noderev, NULL, 2919 SVN_FS_FS__ITEM_TYPE_DIR_REP, 2920 pool)); 2921 else 2922 SVN_ERR(write_container_rep(noderev->data_rep, file, entries, 2923 write_directory_to_stream, fs, NULL, 2924 SVN_FS_FS__ITEM_TYPE_DIR_REP, pool)); 2925 2926 reset_txn_in_rep(noderev->data_rep); 2927 } 2928 } 2929 else 2930 { 2931 /* This is a file. We should make sure the data rep, if it 2932 exists in a "this" state, gets rewritten to our new revision 2933 num. */ 2934 2935 if (noderev->data_rep && is_txn_rep(noderev->data_rep)) 2936 { 2937 reset_txn_in_rep(noderev->data_rep); 2938 noderev->data_rep->revision = rev; 2939 2940 if (!svn_fs_fs__use_log_addressing(fs)) 2941 { 2942 /* See issue 3845. Some unknown mechanism caused the 2943 protorev file to get truncated, so check for that 2944 here. */ 2945 if (noderev->data_rep->item_index + noderev->data_rep->size 2946 > initial_offset) 2947 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2948 _("Truncated protorev file detected")); 2949 } 2950 } 2951 } 2952 2953 svn_pool_destroy(subpool); 2954 2955 /* Fix up the property reps. */ 2956 if (noderev->prop_rep && is_txn_rep(noderev->prop_rep)) 2957 { 2958 apr_hash_t *proplist; 2959 apr_uint32_t item_type = noderev->kind == svn_node_dir 2960 ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS 2961 : SVN_FS_FS__ITEM_TYPE_FILE_PROPS; 2962 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool)); 2963 2964 noderev->prop_rep->revision = rev; 2965 2966 if (ffd->deltify_properties) 2967 SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist, 2968 write_hash_to_stream, fs, noderev, 2969 reps_hash, item_type, pool)); 2970 else 2971 SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist, 2972 write_hash_to_stream, fs, reps_hash, 2973 item_type, pool)); 2974 2975 reset_txn_in_rep(noderev->prop_rep); 2976 } 2977 2978 /* Convert our temporary ID into a permanent revision one. */ 2979 node_id = *svn_fs_fs__id_node_id(noderev->id); 2980 get_final_id(&node_id, rev, start_node_id, ffd->format); 2981 copy_id = *svn_fs_fs__id_copy_id(noderev->id); 2982 get_final_id(©_id, rev, start_copy_id, ffd->format); 2983 2984 if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 2985 noderev->copyroot_rev = rev; 2986 2987 /* root nodes have a fixed ID in log addressing mode */ 2988 SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool)); 2989 if (svn_fs_fs__use_log_addressing(fs) && at_root) 2990 { 2991 /* reference the root noderev from the log-to-phys index */ 2992 rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE; 2993 SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, 2994 rev_item.number, pool)); 2995 } 2996 else 2997 SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id, 2998 my_offset, pool)); 2999 3000 rev_item.revision = rev; 3001 new_id = svn_fs_fs__id_rev_create(&node_id, ©_id, &rev_item, pool); 3002 3003 noderev->id = new_id; 3004 3005 if (ffd->rep_sharing_allowed) 3006 { 3007 /* Save the data representation's hash in the rep cache. */ 3008 if ( noderev->data_rep && noderev->kind == svn_node_file 3009 && noderev->data_rep->revision == rev) 3010 { 3011 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 3012 APR_ARRAY_PUSH(reps_to_cache, representation_t *) 3013 = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool); 3014 } 3015 3016 if (noderev->prop_rep && noderev->prop_rep->revision == rev) 3017 { 3018 /* Add new property reps to hash and on-disk cache. */ 3019 representation_t *copy 3020 = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool); 3021 3022 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 3023 APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy; 3024 3025 apr_hash_set(reps_hash, 3026 copy->sha1_digest, 3027 APR_SHA1_DIGESTSIZE, 3028 copy); 3029 } 3030 } 3031 3032 /* don't serialize SHA1 for dirs to disk (waste of space) */ 3033 if (noderev->data_rep && noderev->kind == svn_node_dir) 3034 noderev->data_rep->has_sha1 = FALSE; 3035 3036 /* don't serialize SHA1 for props to disk (waste of space) */ 3037 if (noderev->prop_rep) 3038 noderev->prop_rep->has_sha1 = FALSE; 3039 3040 /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 3041 noderev->is_fresh_txn_root = FALSE; 3042 3043 /* Write out our new node-revision. */ 3044 if (at_root) 3045 SVN_ERR(validate_root_noderev(fs, noderev, rev, pool)); 3046 3047 file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 3048 svn_stream_from_aprfile2(file, TRUE, pool), 3049 pool); 3050 SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format, 3051 svn_fs_fs__fs_supports_mergeinfo(fs), 3052 pool)); 3053 3054 /* reference the root noderev from the log-to-phys index */ 3055 if (svn_fs_fs__use_log_addressing(fs)) 3056 { 3057 svn_fs_fs__p2l_entry_t entry; 3058 rev_item.revision = SVN_INVALID_REVNUM; 3059 3060 entry.offset = my_offset; 3061 SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool)); 3062 entry.size = my_offset - entry.offset; 3063 entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV; 3064 entry.item = rev_item; 3065 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 3066 fnv1a_checksum_ctx, 3067 pool)); 3068 3069 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool)); 3070 } 3071 3072 /* Return our ID that references the revision file. */ 3073 *new_id_p = noderev->id; 3074 3075 return SVN_NO_ERROR; 3076} 3077 3078/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the 3079 permanent rev-file FILE in filesystem FS. *OFFSET_P is set the to offset 3080 in the file of the beginning of this information. Perform temporary 3081 allocations in POOL. */ 3082static svn_error_t * 3083write_final_changed_path_info(apr_off_t *offset_p, 3084 apr_file_t *file, 3085 svn_fs_t *fs, 3086 const svn_fs_fs__id_part_t *txn_id, 3087 apr_hash_t *changed_paths, 3088 apr_pool_t *pool) 3089{ 3090 apr_off_t offset; 3091 svn_stream_t *stream; 3092 svn_checksum_ctx_t *fnv1a_checksum_ctx; 3093 3094 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool)); 3095 3096 /* write to target file & calculate checksum */ 3097 stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 3098 svn_stream_from_aprfile2(file, TRUE, pool), 3099 pool); 3100 SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool)); 3101 3102 *offset_p = offset; 3103 3104 /* reference changes from the indexes */ 3105 if (svn_fs_fs__use_log_addressing(fs)) 3106 { 3107 svn_fs_fs__p2l_entry_t entry; 3108 3109 entry.offset = offset; 3110 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool)); 3111 entry.size = offset - entry.offset; 3112 entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES; 3113 entry.item.revision = SVN_INVALID_REVNUM; 3114 entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES; 3115 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 3116 fnv1a_checksum_ctx, 3117 pool)); 3118 3119 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool)); 3120 SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset, 3121 SVN_FS_FS__ITEM_INDEX_CHANGES, pool)); 3122 } 3123 3124 return SVN_NO_ERROR; 3125} 3126 3127/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 3128 youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on 3129 NEW_REV's revision root. 3130 3131 Intended to be called as the very last step in a commit before 'current' 3132 is bumped. This implies that we are holding the write lock. */ 3133static svn_error_t * 3134verify_as_revision_before_current_plus_plus(svn_fs_t *fs, 3135 svn_revnum_t new_rev, 3136 apr_pool_t *pool) 3137{ 3138#ifdef SVN_DEBUG 3139 fs_fs_data_t *ffd = fs->fsap_data; 3140 svn_fs_t *ft; /* fs++ == ft */ 3141 svn_fs_root_t *root; 3142 fs_fs_data_t *ft_ffd; 3143 apr_hash_t *fs_config; 3144 3145 SVN_ERR_ASSERT(ffd->svn_fs_open_); 3146 3147 /* make sure FT does not simply return data cached by other instances 3148 * but actually retrieves it from disk at least once. 3149 */ 3150 fs_config = apr_hash_make(pool); 3151 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 3152 svn_uuid_generate(pool)); 3153 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 3154 fs_config, 3155 pool, 3156 pool)); 3157 ft_ffd = ft->fsap_data; 3158 /* Don't let FT consult rep-cache.db, either. */ 3159 ft_ffd->rep_sharing_allowed = FALSE; 3160 3161 /* Time travel! */ 3162 ft_ffd->youngest_rev_cache = new_rev; 3163 3164 SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool)); 3165 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 3166 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 3167 SVN_ERR(svn_fs_fs__verify_root(root, pool)); 3168#endif /* SVN_DEBUG */ 3169 3170 return SVN_NO_ERROR; 3171} 3172 3173/* Update the 'current' file to hold the correct next node and copy_ids 3174 from transaction TXN_ID in filesystem FS. The current revision is 3175 set to REV. Perform temporary allocations in POOL. */ 3176static svn_error_t * 3177write_final_current(svn_fs_t *fs, 3178 const svn_fs_fs__id_part_t *txn_id, 3179 svn_revnum_t rev, 3180 apr_uint64_t start_node_id, 3181 apr_uint64_t start_copy_id, 3182 apr_pool_t *pool) 3183{ 3184 apr_uint64_t txn_node_id; 3185 apr_uint64_t txn_copy_id; 3186 fs_fs_data_t *ffd = fs->fsap_data; 3187 3188 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 3189 return svn_fs_fs__write_current(fs, rev, 0, 0, pool); 3190 3191 /* To find the next available ids, we add the id that used to be in 3192 the 'current' file, to the next ids from the transaction file. */ 3193 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool)); 3194 3195 start_node_id += txn_node_id; 3196 start_copy_id += txn_copy_id; 3197 3198 return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id, 3199 pool); 3200} 3201 3202/* Verify that the user registered with FS has all the locks necessary to 3203 permit all the changes associated with TXN_NAME. 3204 The FS write lock is assumed to be held by the caller. */ 3205static svn_error_t * 3206verify_locks(svn_fs_t *fs, 3207 const svn_fs_fs__id_part_t *txn_id, 3208 apr_hash_t *changed_paths, 3209 apr_pool_t *pool) 3210{ 3211 apr_pool_t *iterpool; 3212 apr_array_header_t *changed_paths_sorted; 3213 svn_stringbuf_t *last_recursed = NULL; 3214 int i; 3215 3216 /* Make an array of the changed paths, and sort them depth-first-ily. */ 3217 changed_paths_sorted = svn_sort__hash(changed_paths, 3218 svn_sort_compare_items_as_paths, 3219 pool); 3220 3221 /* Now, traverse the array of changed paths, verify locks. Note 3222 that if we need to do a recursive verification a path, we'll skip 3223 over children of that path when we get to them. */ 3224 iterpool = svn_pool_create(pool); 3225 for (i = 0; i < changed_paths_sorted->nelts; i++) 3226 { 3227 const svn_sort__item_t *item; 3228 const char *path; 3229 svn_fs_path_change2_t *change; 3230 svn_boolean_t recurse = TRUE; 3231 3232 svn_pool_clear(iterpool); 3233 3234 item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t); 3235 3236 /* Fetch the change associated with our path. */ 3237 path = item->key; 3238 change = item->value; 3239 3240 /* If this path has already been verified as part of a recursive 3241 check of one of its parents, no need to do it again. */ 3242 if (last_recursed 3243 && svn_fspath__skip_ancestor(last_recursed->data, path)) 3244 continue; 3245 3246 /* What does it mean to succeed at lock verification for a given 3247 path? For an existing file or directory getting modified 3248 (text, props), it means we hold the lock on the file or 3249 directory. For paths being added or removed, we need to hold 3250 the locks for that path and any children of that path. 3251 3252 WHEW! We have no reliable way to determine the node kind 3253 of deleted items, but fortunately we are going to do a 3254 recursive check on deleted paths regardless of their kind. */ 3255 if (change->change_kind == svn_fs_path_change_modify) 3256 recurse = FALSE; 3257 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE, 3258 iterpool)); 3259 3260 /* If we just did a recursive check, remember the path we 3261 checked (so children can be skipped). */ 3262 if (recurse) 3263 { 3264 if (! last_recursed) 3265 last_recursed = svn_stringbuf_create(path, pool); 3266 else 3267 svn_stringbuf_set(last_recursed, path); 3268 } 3269 } 3270 svn_pool_destroy(iterpool); 3271 return SVN_NO_ERROR; 3272} 3273 3274/* Return in *PATH the path to a file containing the properties that 3275 make up the final revision properties file. This involves setting 3276 svn:date and removing any temporary properties associated with the 3277 commit flags. */ 3278static svn_error_t * 3279write_final_revprop(const char **path, 3280 svn_fs_txn_t *txn, 3281 const svn_fs_fs__id_part_t *txn_id, 3282 apr_pool_t *pool) 3283{ 3284 apr_hash_t *txnprops; 3285 svn_boolean_t final_mods = FALSE; 3286 svn_string_t date; 3287 svn_string_t *client_date; 3288 3289 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool)); 3290 3291 /* Remove any temporary txn props representing 'flags'. */ 3292 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 3293 { 3294 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL); 3295 final_mods = TRUE; 3296 } 3297 3298 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 3299 { 3300 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL); 3301 final_mods = TRUE; 3302 } 3303 3304 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE); 3305 if (client_date) 3306 { 3307 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL); 3308 final_mods = TRUE; 3309 } 3310 3311 /* Update commit time to ensure that svn:date revprops remain ordered if 3312 requested. */ 3313 if (!client_date || strcmp(client_date->data, "1")) 3314 { 3315 date.data = svn_time_to_cstring(apr_time_now(), pool); 3316 date.len = strlen(date.data); 3317 svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date); 3318 final_mods = TRUE; 3319 } 3320 3321 if (final_mods) 3322 { 3323 SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool)); 3324 *path = path_txn_props_final(txn->fs, txn_id, pool); 3325 } 3326 else 3327 { 3328 *path = path_txn_props(txn->fs, txn_id, pool); 3329 } 3330 3331 return SVN_NO_ERROR; 3332} 3333 3334svn_error_t * 3335svn_fs_fs__add_index_data(svn_fs_t *fs, 3336 apr_file_t *file, 3337 const char *l2p_proto_index, 3338 const char *p2l_proto_index, 3339 svn_revnum_t revision, 3340 apr_pool_t *pool) 3341{ 3342 apr_off_t l2p_offset; 3343 apr_off_t p2l_offset; 3344 svn_stringbuf_t *footer; 3345 unsigned char footer_length; 3346 svn_checksum_t *l2p_checksum; 3347 svn_checksum_t *p2l_checksum; 3348 3349 /* Append the actual index data to the pack file. */ 3350 l2p_offset = 0; 3351 SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool)); 3352 SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file, 3353 l2p_proto_index, revision, 3354 pool, pool)); 3355 3356 p2l_offset = 0; 3357 SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool)); 3358 SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file, 3359 p2l_proto_index, revision, 3360 pool, pool)); 3361 3362 /* Append footer. */ 3363 footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum, 3364 p2l_offset, p2l_checksum, pool, pool); 3365 SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL, 3366 pool)); 3367 3368 footer_length = footer->len; 3369 SVN_ERR_ASSERT(footer_length == footer->len); 3370 SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool)); 3371 3372 return SVN_NO_ERROR; 3373} 3374 3375/* Baton used for commit_body below. */ 3376struct commit_baton { 3377 svn_revnum_t *new_rev_p; 3378 svn_fs_t *fs; 3379 svn_fs_txn_t *txn; 3380 apr_array_header_t *reps_to_cache; 3381 apr_hash_t *reps_hash; 3382 apr_pool_t *reps_pool; 3383}; 3384 3385/* The work-horse for svn_fs_fs__commit, called with the FS write lock. 3386 This implements the svn_fs_fs__with_write_lock() 'body' callback 3387 type. BATON is a 'struct commit_baton *'. */ 3388static svn_error_t * 3389commit_body(void *baton, apr_pool_t *pool) 3390{ 3391 struct commit_baton *cb = baton; 3392 fs_fs_data_t *ffd = cb->fs->fsap_data; 3393 const char *old_rev_filename, *rev_filename, *proto_filename; 3394 const char *revprop_filename, *final_revprop; 3395 const svn_fs_id_t *root_id, *new_root_id; 3396 apr_uint64_t start_node_id; 3397 apr_uint64_t start_copy_id; 3398 svn_revnum_t old_rev, new_rev; 3399 apr_file_t *proto_file; 3400 void *proto_file_lockcookie; 3401 apr_off_t initial_offset, changed_path_offset; 3402 const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn); 3403 apr_hash_t *changed_paths; 3404 3405 /* Re-Read the current repository format. All our repo upgrade and 3406 config evaluation strategies are such that existing information in 3407 FS and FFD remains valid. 3408 3409 Although we don't recommend upgrading hot repositories, people may 3410 still do it and we must make sure to either handle them gracefully 3411 or to error out. 3412 3413 Committing pre-format 3 txns will fail after upgrade to format 3+ 3414 because the proto-rev cannot be found; no further action needed. 3415 Upgrades from pre-f7 to f7+ means a potential change in addressing 3416 mode for the final rev. We must be sure to detect that cause because 3417 the failure would only manifest once the new revision got committed. 3418 */ 3419 SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool)); 3420 3421 /* Read the current youngest revision and, possibly, the next available 3422 node id and copy id (for old format filesystems). Update the cached 3423 value for the youngest revision, because we have just checked it. */ 3424 SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id, 3425 cb->fs, pool)); 3426 ffd->youngest_rev_cache = old_rev; 3427 3428 /* Check to make sure this transaction is based off the most recent 3429 revision. */ 3430 if (cb->txn->base_rev != old_rev) 3431 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 3432 _("Transaction out of date")); 3433 3434 /* We need the changes list for verification as well as for writing it 3435 to the final rev file. */ 3436 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id, 3437 pool)); 3438 3439 /* Locks may have been added (or stolen) between the calling of 3440 previous svn_fs.h functions and svn_fs_commit_txn(), so we need 3441 to re-examine every changed-path in the txn and re-verify all 3442 discovered locks. */ 3443 SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool)); 3444 3445 /* We are going to be one better than this puny old revision. */ 3446 new_rev = old_rev + 1; 3447 3448 /* Get a write handle on the proto revision file. */ 3449 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 3450 cb->fs, txn_id, pool)); 3451 SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool)); 3452 3453 /* Write out all the node-revisions and directory contents. */ 3454 root_id = svn_fs_fs__id_txn_create_root(txn_id, pool); 3455 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id, 3456 start_node_id, start_copy_id, initial_offset, 3457 cb->reps_to_cache, cb->reps_hash, cb->reps_pool, 3458 TRUE, pool)); 3459 3460 /* Write the changed-path information. */ 3461 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 3462 cb->fs, txn_id, changed_paths, 3463 pool)); 3464 3465 if (svn_fs_fs__use_log_addressing(cb->fs)) 3466 { 3467 /* Append the index data to the rev file. */ 3468 SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file, 3469 svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool), 3470 svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool), 3471 new_rev, pool)); 3472 } 3473 else 3474 { 3475 /* Write the final line. */ 3476 3477 svn_stringbuf_t *trailer 3478 = svn_fs_fs__unparse_revision_trailer 3479 ((apr_off_t)svn_fs_fs__id_item(new_root_id), 3480 changed_path_offset, 3481 pool); 3482 SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len, 3483 NULL, pool)); 3484 } 3485 3486 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool)); 3487 SVN_ERR(svn_io_file_close(proto_file, pool)); 3488 3489 /* We don't unlock the prototype revision file immediately to avoid a 3490 race with another caller writing to the prototype revision file 3491 before we commit it. */ 3492 3493 /* Create the shard for the rev and revprop file, if we're sharding and 3494 this is the first revision of a new shard. We don't care if this 3495 fails because the shard already existed for some reason. */ 3496 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0) 3497 { 3498 /* Create the revs shard. */ 3499 { 3500 const char *new_dir 3501 = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool); 3502 svn_error_t *err 3503 = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 3504 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3505 return svn_error_trace(err); 3506 svn_error_clear(err); 3507 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3508 PATH_REVS_DIR, 3509 pool), 3510 new_dir, pool)); 3511 } 3512 3513 /* Create the revprops shard. */ 3514 SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev)); 3515 { 3516 const char *new_dir 3517 = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool); 3518 svn_error_t *err 3519 = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 3520 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3521 return svn_error_trace(err); 3522 svn_error_clear(err); 3523 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3524 PATH_REVPROPS_DIR, 3525 pool), 3526 new_dir, pool)); 3527 } 3528 } 3529 3530 /* Move the finished rev file into place. 3531 3532 ### This "breaks" the transaction by removing the protorev file 3533 ### but the revision is not yet complete. If this commit does 3534 ### not complete for any reason the transaction will be lost. */ 3535 old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool); 3536 rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool); 3537 proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool); 3538 SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename, 3539 old_rev_filename, pool)); 3540 3541 /* Now that we've moved the prototype revision file out of the way, 3542 we can unlock it (since further attempts to write to the file 3543 will fail as it no longer exists). We must do this so that we can 3544 remove the transaction directory later. */ 3545 SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool)); 3546 3547 /* Move the revprops file into place. */ 3548 SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev)); 3549 SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool)); 3550 final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool); 3551 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop, 3552 old_rev_filename, pool)); 3553 3554 /* Update the 'current' file. */ 3555 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool)); 3556 SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id, 3557 start_copy_id, pool)); 3558 3559 /* At this point the new revision is committed and globally visible 3560 so let the caller know it succeeded by giving it the new revision 3561 number, which fulfills svn_fs_commit_txn() contract. Any errors 3562 after this point do not change the fact that a new revision was 3563 created. */ 3564 *cb->new_rev_p = new_rev; 3565 3566 ffd->youngest_rev_cache = new_rev; 3567 3568 /* Remove this transaction directory. */ 3569 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool)); 3570 3571 return SVN_NO_ERROR; 3572} 3573 3574/* Add the representations in REPS_TO_CACHE (an array of representation_t *) 3575 * to the rep-cache database of FS. */ 3576static svn_error_t * 3577write_reps_to_cache(svn_fs_t *fs, 3578 const apr_array_header_t *reps_to_cache, 3579 apr_pool_t *scratch_pool) 3580{ 3581 int i; 3582 3583 for (i = 0; i < reps_to_cache->nelts; i++) 3584 { 3585 representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *); 3586 3587 SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool)); 3588 } 3589 3590 return SVN_NO_ERROR; 3591} 3592 3593svn_error_t * 3594svn_fs_fs__commit(svn_revnum_t *new_rev_p, 3595 svn_fs_t *fs, 3596 svn_fs_txn_t *txn, 3597 apr_pool_t *pool) 3598{ 3599 struct commit_baton cb; 3600 fs_fs_data_t *ffd = fs->fsap_data; 3601 3602 cb.new_rev_p = new_rev_p; 3603 cb.fs = fs; 3604 cb.txn = txn; 3605 3606 if (ffd->rep_sharing_allowed) 3607 { 3608 cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *)); 3609 cb.reps_hash = apr_hash_make(pool); 3610 cb.reps_pool = pool; 3611 } 3612 else 3613 { 3614 cb.reps_to_cache = NULL; 3615 cb.reps_hash = NULL; 3616 cb.reps_pool = NULL; 3617 } 3618 3619 SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool)); 3620 3621 /* At this point, *NEW_REV_P has been set, so errors below won't affect 3622 the success of the commit. (See svn_fs_commit_txn().) */ 3623 3624 if (ffd->rep_sharing_allowed) 3625 { 3626 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 3627 3628 /* Write new entries to the rep-sharing database. 3629 * 3630 * We use an sqlite transaction to speed things up; 3631 * see <http://www.sqlite.org/faq.html#q19>. 3632 */ 3633 /* ### A commit that touches thousands of files will starve other 3634 (reader/writer) commits for the duration of the below call. 3635 Maybe write in batches? */ 3636 SVN_SQLITE__WITH_TXN( 3637 write_reps_to_cache(fs, cb.reps_to_cache, pool), 3638 ffd->rep_cache_db); 3639 } 3640 3641 return SVN_NO_ERROR; 3642} 3643 3644 3645svn_error_t * 3646svn_fs_fs__list_transactions(apr_array_header_t **names_p, 3647 svn_fs_t *fs, 3648 apr_pool_t *pool) 3649{ 3650 const char *txn_dir; 3651 apr_hash_t *dirents; 3652 apr_hash_index_t *hi; 3653 apr_array_header_t *names; 3654 apr_size_t ext_len = strlen(PATH_EXT_TXN); 3655 3656 names = apr_array_make(pool, 1, sizeof(const char *)); 3657 3658 /* Get the transactions directory. */ 3659 txn_dir = svn_fs_fs__path_txns_dir(fs, pool); 3660 3661 /* Now find a listing of this directory. */ 3662 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 3663 3664 /* Loop through all the entries and return anything that ends with '.txn'. */ 3665 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 3666 { 3667 const char *name = apr_hash_this_key(hi); 3668 apr_ssize_t klen = apr_hash_this_key_len(hi); 3669 const char *id; 3670 3671 /* The name must end with ".txn" to be considered a transaction. */ 3672 if ((apr_size_t) klen <= ext_len 3673 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 3674 continue; 3675 3676 /* Truncate the ".txn" extension and store the ID. */ 3677 id = apr_pstrndup(pool, name, strlen(name) - ext_len); 3678 APR_ARRAY_PUSH(names, const char *) = id; 3679 } 3680 3681 *names_p = names; 3682 3683 return SVN_NO_ERROR; 3684} 3685 3686svn_error_t * 3687svn_fs_fs__open_txn(svn_fs_txn_t **txn_p, 3688 svn_fs_t *fs, 3689 const char *name, 3690 apr_pool_t *pool) 3691{ 3692 svn_fs_txn_t *txn; 3693 fs_txn_data_t *ftd; 3694 svn_node_kind_t kind; 3695 transaction_t *local_txn; 3696 svn_fs_fs__id_part_t txn_id; 3697 3698 SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name)); 3699 3700 /* First check to see if the directory exists. */ 3701 SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool), 3702 &kind, pool)); 3703 3704 /* Did we find it? */ 3705 if (kind != svn_node_dir) 3706 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 3707 _("No such transaction '%s'"), 3708 name); 3709 3710 txn = apr_pcalloc(pool, sizeof(*txn)); 3711 ftd = apr_pcalloc(pool, sizeof(*ftd)); 3712 ftd->txn_id = txn_id; 3713 3714 /* Read in the root node of this transaction. */ 3715 txn->id = apr_pstrdup(pool, name); 3716 txn->fs = fs; 3717 3718 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool)); 3719 3720 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id); 3721 3722 txn->vtable = &txn_vtable; 3723 txn->fsap_data = ftd; 3724 *txn_p = txn; 3725 3726 return SVN_NO_ERROR; 3727} 3728 3729svn_error_t * 3730svn_fs_fs__txn_proplist(apr_hash_t **table_p, 3731 svn_fs_txn_t *txn, 3732 apr_pool_t *pool) 3733{ 3734 apr_hash_t *proplist = apr_hash_make(pool); 3735 SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn), 3736 pool)); 3737 *table_p = proplist; 3738 3739 return SVN_NO_ERROR; 3740} 3741 3742 3743svn_error_t * 3744svn_fs_fs__delete_node_revision(svn_fs_t *fs, 3745 const svn_fs_id_t *id, 3746 apr_pool_t *pool) 3747{ 3748 node_revision_t *noderev; 3749 3750 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool)); 3751 3752 /* Delete any mutable property representation. */ 3753 if (noderev->prop_rep && is_txn_rep(noderev->prop_rep)) 3754 SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool), 3755 FALSE, pool)); 3756 3757 /* Delete any mutable data representation. */ 3758 if (noderev->data_rep && is_txn_rep(noderev->data_rep) 3759 && noderev->kind == svn_node_dir) 3760 { 3761 fs_fs_data_t *ffd = fs->fsap_data; 3762 SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id, 3763 pool), 3764 FALSE, pool)); 3765 3766 /* remove the corresponding entry from the cache, if such exists */ 3767 if (ffd->txn_dir_cache) 3768 { 3769 const char *key = svn_fs_fs__id_unparse(id, pool)->data; 3770 SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool)); 3771 } 3772 } 3773 3774 return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool), 3775 FALSE, pool); 3776} 3777 3778 3779 3780/*** Transactions ***/ 3781 3782svn_error_t * 3783svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p, 3784 const svn_fs_id_t **base_root_id_p, 3785 svn_fs_t *fs, 3786 const svn_fs_fs__id_part_t *txn_id, 3787 apr_pool_t *pool) 3788{ 3789 transaction_t *txn; 3790 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool)); 3791 *root_id_p = txn->root_id; 3792 *base_root_id_p = txn->base_id; 3793 return SVN_NO_ERROR; 3794} 3795 3796 3797/* Generic transaction operations. */ 3798 3799svn_error_t * 3800svn_fs_fs__txn_prop(svn_string_t **value_p, 3801 svn_fs_txn_t *txn, 3802 const char *propname, 3803 apr_pool_t *pool) 3804{ 3805 apr_hash_t *table; 3806 svn_fs_t *fs = txn->fs; 3807 3808 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3809 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool)); 3810 3811 *value_p = svn_hash_gets(table, propname); 3812 3813 return SVN_NO_ERROR; 3814} 3815 3816svn_error_t * 3817svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, 3818 svn_fs_t *fs, 3819 svn_revnum_t rev, 3820 apr_uint32_t flags, 3821 apr_pool_t *pool) 3822{ 3823 svn_string_t date; 3824 fs_txn_data_t *ftd; 3825 apr_hash_t *props = apr_hash_make(pool); 3826 3827 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3828 3829 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool)); 3830 3831 /* Put a datestamp on the newly created txn, so we always know 3832 exactly how old it is. (This will help sysadmins identify 3833 long-abandoned txns that may need to be manually removed.) When 3834 a txn is promoted to a revision, this property will be 3835 automatically overwritten with a revision datestamp. */ 3836 date.data = svn_time_to_cstring(apr_time_now(), pool); 3837 date.len = strlen(date.data); 3838 3839 svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date); 3840 3841 /* Set temporary txn props that represent the requested 'flags' 3842 behaviors. */ 3843 if (flags & SVN_FS_TXN_CHECK_OOD) 3844 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD, 3845 svn_string_create("true", pool)); 3846 3847 if (flags & SVN_FS_TXN_CHECK_LOCKS) 3848 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS, 3849 svn_string_create("true", pool)); 3850 3851 if (flags & SVN_FS_TXN_CLIENT_DATE) 3852 svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE, 3853 svn_string_create("0", pool)); 3854 3855 ftd = (*txn_p)->fsap_data; 3856 return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE, 3857 pool)); 3858} 3859