1/* fs_fs.c --- filesystem operations specific to fs_fs 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 <stdlib.h> 24#include <stdio.h> 25#include <string.h> 26#include <ctype.h> 27#include <assert.h> 28#include <errno.h> 29 30#include <apr_general.h> 31#include <apr_pools.h> 32#include <apr_file_io.h> 33#include <apr_uuid.h> 34#include <apr_lib.h> 35#include <apr_md5.h> 36#include <apr_sha1.h> 37#include <apr_strings.h> 38#include <apr_thread_mutex.h> 39 40#include "svn_pools.h" 41#include "svn_fs.h" 42#include "svn_dirent_uri.h" 43#include "svn_path.h" 44#include "svn_hash.h" 45#include "svn_props.h" 46#include "svn_sorts.h" 47#include "svn_string.h" 48#include "svn_time.h" 49#include "svn_mergeinfo.h" 50#include "svn_config.h" 51#include "svn_ctype.h" 52#include "svn_version.h" 53 54#include "fs.h" 55#include "tree.h" 56#include "lock.h" 57#include "key-gen.h" 58#include "fs_fs.h" 59#include "id.h" 60#include "rep-cache.h" 61#include "temp_serializer.h" 62 63#include "private/svn_string_private.h" 64#include "private/svn_fs_util.h" 65#include "private/svn_subr_private.h" 66#include "private/svn_delta_private.h" 67#include "../libsvn_fs/fs-loader.h" 68 69#include "svn_private_config.h" 70#include "temp_serializer.h" 71 72/* An arbitrary maximum path length, so clients can't run us out of memory 73 * by giving us arbitrarily large paths. */ 74#define FSFS_MAX_PATH_LEN 4096 75 76/* The default maximum number of files per directory to store in the 77 rev and revprops directory. The number below is somewhat arbitrary, 78 and can be overridden by defining the macro while compiling; the 79 figure of 1000 is reasonable for VFAT filesystems, which are by far 80 the worst performers in this area. */ 81#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 82#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000 83#endif 84 85/* Begin deltification after a node history exceeded this this limit. 86 Useful values are 4 to 64 with 16 being a good compromise between 87 computational overhead and repository size savings. 88 Should be a power of 2. 89 Values < 2 will result in standard skip-delta behavior. */ 90#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16 91 92/* Finding a deltification base takes operations proportional to the 93 number of changes being skipped. To prevent exploding runtime 94 during commits, limit the deltification range to this value. 95 Should be a power of 2 minus one. 96 Values < 1 disable deltification. */ 97#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023 98 99/* Give writing processes 10 seconds to replace an existing revprop 100 file with a new one. After that time, we assume that the writing 101 process got aborted and that we have re-read revprops. */ 102#define REVPROP_CHANGE_TIMEOUT (10 * 1000000) 103 104/* The following are names of atomics that will be used to communicate 105 * revprop updates across all processes on this machine. */ 106#define ATOMIC_REVPROP_GENERATION "rev-prop-generation" 107#define ATOMIC_REVPROP_TIMEOUT "rev-prop-timeout" 108#define ATOMIC_REVPROP_NAMESPACE "rev-prop-atomics" 109 110/* Following are defines that specify the textual elements of the 111 native filesystem directories and revision files. */ 112 113/* Headers used to describe node-revision in the revision file. */ 114#define HEADER_ID "id" 115#define HEADER_TYPE "type" 116#define HEADER_COUNT "count" 117#define HEADER_PROPS "props" 118#define HEADER_TEXT "text" 119#define HEADER_CPATH "cpath" 120#define HEADER_PRED "pred" 121#define HEADER_COPYFROM "copyfrom" 122#define HEADER_COPYROOT "copyroot" 123#define HEADER_FRESHTXNRT "is-fresh-txn-root" 124#define HEADER_MINFO_HERE "minfo-here" 125#define HEADER_MINFO_CNT "minfo-cnt" 126 127/* Kinds that a change can be. */ 128#define ACTION_MODIFY "modify" 129#define ACTION_ADD "add" 130#define ACTION_DELETE "delete" 131#define ACTION_REPLACE "replace" 132#define ACTION_RESET "reset" 133 134/* True and False flags. */ 135#define FLAG_TRUE "true" 136#define FLAG_FALSE "false" 137 138/* Kinds that a node-rev can be. */ 139#define KIND_FILE "file" 140#define KIND_DIR "dir" 141 142/* Kinds of representation. */ 143#define REP_PLAIN "PLAIN" 144#define REP_DELTA "DELTA" 145 146/* Notes: 147 148To avoid opening and closing the rev-files all the time, it would 149probably be advantageous to keep each rev-file open for the 150lifetime of the transaction object. I'll leave that as a later 151optimization for now. 152 153I didn't keep track of pool lifetimes at all in this code. There 154are likely some errors because of that. 155 156*/ 157 158/* The vtable associated with an open transaction object. */ 159static txn_vtable_t txn_vtable = { 160 svn_fs_fs__commit_txn, 161 svn_fs_fs__abort_txn, 162 svn_fs_fs__txn_prop, 163 svn_fs_fs__txn_proplist, 164 svn_fs_fs__change_txn_prop, 165 svn_fs_fs__txn_root, 166 svn_fs_fs__change_txn_props 167}; 168 169/* Declarations. */ 170 171static svn_error_t * 172read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev, 173 const char *path, 174 apr_pool_t *pool); 175 176static svn_error_t * 177update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool); 178 179static svn_error_t * 180get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool); 181 182static svn_error_t * 183verify_walker(representation_t *rep, 184 void *baton, 185 svn_fs_t *fs, 186 apr_pool_t *scratch_pool); 187 188/* Pathname helper functions */ 189 190/* Return TRUE is REV is packed in FS, FALSE otherwise. */ 191static svn_boolean_t 192is_packed_rev(svn_fs_t *fs, svn_revnum_t rev) 193{ 194 fs_fs_data_t *ffd = fs->fsap_data; 195 196 return (rev < ffd->min_unpacked_rev); 197} 198 199/* Return TRUE is REV is packed in FS, FALSE otherwise. */ 200static svn_boolean_t 201is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev) 202{ 203 fs_fs_data_t *ffd = fs->fsap_data; 204 205 /* rev 0 will not be packed */ 206 return (rev < ffd->min_unpacked_rev) 207 && (rev != 0) 208 && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT); 209} 210 211static const char * 212path_format(svn_fs_t *fs, apr_pool_t *pool) 213{ 214 return svn_dirent_join(fs->path, PATH_FORMAT, pool); 215} 216 217static APR_INLINE const char * 218path_uuid(svn_fs_t *fs, apr_pool_t *pool) 219{ 220 return svn_dirent_join(fs->path, PATH_UUID, pool); 221} 222 223const char * 224svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool) 225{ 226 return svn_dirent_join(fs->path, PATH_CURRENT, pool); 227} 228 229static APR_INLINE const char * 230path_txn_current(svn_fs_t *fs, apr_pool_t *pool) 231{ 232 return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool); 233} 234 235static APR_INLINE const char * 236path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool) 237{ 238 return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool); 239} 240 241static APR_INLINE const char * 242path_lock(svn_fs_t *fs, apr_pool_t *pool) 243{ 244 return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool); 245} 246 247static const char * 248path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool) 249{ 250 return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool); 251} 252 253static const char * 254path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind, 255 apr_pool_t *pool) 256{ 257 fs_fs_data_t *ffd = fs->fsap_data; 258 259 assert(ffd->max_files_per_dir); 260 assert(is_packed_rev(fs, rev)); 261 262 return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 263 apr_psprintf(pool, 264 "%ld" PATH_EXT_PACKED_SHARD, 265 rev / ffd->max_files_per_dir), 266 kind, NULL); 267} 268 269static const char * 270path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 271{ 272 fs_fs_data_t *ffd = fs->fsap_data; 273 274 assert(ffd->max_files_per_dir); 275 return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 276 apr_psprintf(pool, "%ld", 277 rev / ffd->max_files_per_dir), 278 NULL); 279} 280 281static const char * 282path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 283{ 284 fs_fs_data_t *ffd = fs->fsap_data; 285 286 assert(! is_packed_rev(fs, rev)); 287 288 if (ffd->max_files_per_dir) 289 { 290 return svn_dirent_join(path_rev_shard(fs, rev, pool), 291 apr_psprintf(pool, "%ld", rev), 292 pool); 293 } 294 295 return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 296 apr_psprintf(pool, "%ld", rev), NULL); 297} 298 299svn_error_t * 300svn_fs_fs__path_rev_absolute(const char **path, 301 svn_fs_t *fs, 302 svn_revnum_t rev, 303 apr_pool_t *pool) 304{ 305 fs_fs_data_t *ffd = fs->fsap_data; 306 307 if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT 308 || ! is_packed_rev(fs, rev)) 309 { 310 *path = path_rev(fs, rev, pool); 311 } 312 else 313 { 314 *path = path_rev_packed(fs, rev, PATH_PACKED, pool); 315 } 316 317 return SVN_NO_ERROR; 318} 319 320static const char * 321path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 322{ 323 fs_fs_data_t *ffd = fs->fsap_data; 324 325 assert(ffd->max_files_per_dir); 326 return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 327 apr_psprintf(pool, "%ld", 328 rev / ffd->max_files_per_dir), 329 NULL); 330} 331 332static const char * 333path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 334{ 335 fs_fs_data_t *ffd = fs->fsap_data; 336 337 assert(ffd->max_files_per_dir); 338 return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 339 apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD, 340 rev / ffd->max_files_per_dir), 341 NULL); 342} 343 344static const char * 345path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 346{ 347 fs_fs_data_t *ffd = fs->fsap_data; 348 349 if (ffd->max_files_per_dir) 350 { 351 return svn_dirent_join(path_revprops_shard(fs, rev, pool), 352 apr_psprintf(pool, "%ld", rev), 353 pool); 354 } 355 356 return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 357 apr_psprintf(pool, "%ld", rev), NULL); 358} 359 360static APR_INLINE const char * 361path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 362{ 363 SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL); 364 return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR, 365 apr_pstrcat(pool, txn_id, PATH_EXT_TXN, 366 (char *)NULL), 367 NULL); 368} 369 370/* Return the name of the sha1->rep mapping file in transaction TXN_ID 371 * within FS for the given SHA1 checksum. Use POOL for allocations. 372 */ 373static APR_INLINE const char * 374path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1, 375 apr_pool_t *pool) 376{ 377 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), 378 svn_checksum_to_cstring(sha1, pool), 379 pool); 380} 381 382static APR_INLINE const char * 383path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 384{ 385 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool); 386} 387 388static APR_INLINE const char * 389path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 390{ 391 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool); 392} 393 394static APR_INLINE const char * 395path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 396{ 397 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool); 398} 399 400static APR_INLINE const char * 401path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool) 402{ 403 return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool); 404} 405 406 407static APR_INLINE const char * 408path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 409{ 410 fs_fs_data_t *ffd = fs->fsap_data; 411 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 412 return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR, 413 apr_pstrcat(pool, txn_id, PATH_EXT_REV, 414 (char *)NULL), 415 NULL); 416 else 417 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool); 418} 419 420static APR_INLINE const char * 421path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 422{ 423 fs_fs_data_t *ffd = fs->fsap_data; 424 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 425 return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR, 426 apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK, 427 (char *)NULL), 428 NULL); 429 else 430 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK, 431 pool); 432} 433 434static const char * 435path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 436{ 437 const char *txn_id = svn_fs_fs__id_txn_id(id); 438 const char *node_id = svn_fs_fs__id_node_id(id); 439 const char *copy_id = svn_fs_fs__id_copy_id(id); 440 const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s", 441 node_id, copy_id); 442 443 return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool); 444} 445 446static APR_INLINE const char * 447path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 448{ 449 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS, 450 (char *)NULL); 451} 452 453static APR_INLINE const char * 454path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 455{ 456 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), 457 PATH_EXT_CHILDREN, (char *)NULL); 458} 459 460static APR_INLINE const char * 461path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool) 462{ 463 size_t len = strlen(node_id); 464 const char *node_id_minus_last_char = 465 (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1); 466 return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR, 467 node_id_minus_last_char, NULL); 468} 469 470static APR_INLINE const char * 471path_and_offset_of(apr_file_t *file, apr_pool_t *pool) 472{ 473 const char *path; 474 apr_off_t offset = 0; 475 476 if (apr_file_name_get(&path, file) != APR_SUCCESS) 477 path = "(unknown)"; 478 479 if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS) 480 offset = -1; 481 482 return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset); 483} 484 485 486 487/* Functions for working with shared transaction data. */ 488 489/* Return the transaction object for transaction TXN_ID from the 490 transaction list of filesystem FS (which must already be locked via the 491 txn_list_lock mutex). If the transaction does not exist in the list, 492 then create a new transaction object and return it (if CREATE_NEW is 493 true) or return NULL (otherwise). */ 494static fs_fs_shared_txn_data_t * 495get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new) 496{ 497 fs_fs_data_t *ffd = fs->fsap_data; 498 fs_fs_shared_data_t *ffsd = ffd->shared; 499 fs_fs_shared_txn_data_t *txn; 500 501 for (txn = ffsd->txns; txn; txn = txn->next) 502 if (strcmp(txn->txn_id, txn_id) == 0) 503 break; 504 505 if (txn || !create_new) 506 return txn; 507 508 /* Use the transaction object from the (single-object) freelist, 509 if one is available, or otherwise create a new object. */ 510 if (ffsd->free_txn) 511 { 512 txn = ffsd->free_txn; 513 ffsd->free_txn = NULL; 514 } 515 else 516 { 517 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool); 518 txn = apr_palloc(subpool, sizeof(*txn)); 519 txn->pool = subpool; 520 } 521 522 assert(strlen(txn_id) < sizeof(txn->txn_id)); 523 apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id)); 524 txn->being_written = FALSE; 525 526 /* Link this transaction into the head of the list. We will typically 527 be dealing with only one active transaction at a time, so it makes 528 sense for searches through the transaction list to look at the 529 newest transactions first. */ 530 txn->next = ffsd->txns; 531 ffsd->txns = txn; 532 533 return txn; 534} 535 536/* Free the transaction object for transaction TXN_ID, and remove it 537 from the transaction list of filesystem FS (which must already be 538 locked via the txn_list_lock mutex). Do nothing if the transaction 539 does not exist. */ 540static void 541free_shared_txn(svn_fs_t *fs, const char *txn_id) 542{ 543 fs_fs_data_t *ffd = fs->fsap_data; 544 fs_fs_shared_data_t *ffsd = ffd->shared; 545 fs_fs_shared_txn_data_t *txn, *prev = NULL; 546 547 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next) 548 if (strcmp(txn->txn_id, txn_id) == 0) 549 break; 550 551 if (!txn) 552 return; 553 554 if (prev) 555 prev->next = txn->next; 556 else 557 ffsd->txns = txn->next; 558 559 /* As we typically will be dealing with one transaction after another, 560 we will maintain a single-object free list so that we can hopefully 561 keep reusing the same transaction object. */ 562 if (!ffsd->free_txn) 563 ffsd->free_txn = txn; 564 else 565 svn_pool_destroy(txn->pool); 566} 567 568 569/* Obtain a lock on the transaction list of filesystem FS, call BODY 570 with FS, BATON, and POOL, and then unlock the transaction list. 571 Return what BODY returned. */ 572static svn_error_t * 573with_txnlist_lock(svn_fs_t *fs, 574 svn_error_t *(*body)(svn_fs_t *fs, 575 const void *baton, 576 apr_pool_t *pool), 577 const void *baton, 578 apr_pool_t *pool) 579{ 580 fs_fs_data_t *ffd = fs->fsap_data; 581 fs_fs_shared_data_t *ffsd = ffd->shared; 582 583 SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock, 584 body(fs, baton, pool)); 585 586 return SVN_NO_ERROR; 587} 588 589 590/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */ 591static svn_error_t * 592get_lock_on_filesystem(const char *lock_filename, 593 apr_pool_t *pool) 594{ 595 svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool); 596 597 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 598 { 599 /* No lock file? No big deal; these are just empty files 600 anyway. Create it and try again. */ 601 svn_error_clear(err); 602 err = NULL; 603 604 SVN_ERR(svn_io_file_create(lock_filename, "", pool)); 605 SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool)); 606 } 607 608 return svn_error_trace(err); 609} 610 611/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID. 612 When registered with the pool holding the lock on the lock file, 613 this makes sure the flag gets reset just before we release the lock. */ 614static apr_status_t 615reset_lock_flag(void *baton_void) 616{ 617 fs_fs_data_t *ffd = baton_void; 618 ffd->has_write_lock = FALSE; 619 return APR_SUCCESS; 620} 621 622/* Obtain a write lock on the file LOCK_FILENAME (protecting with 623 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with 624 BATON and that subpool, destroy the subpool (releasing the write 625 lock) and return what BODY returned. If IS_GLOBAL_LOCK is set, 626 set the HAS_WRITE_LOCK flag while we keep the write lock. */ 627static svn_error_t * 628with_some_lock_file(svn_fs_t *fs, 629 svn_error_t *(*body)(void *baton, 630 apr_pool_t *pool), 631 void *baton, 632 const char *lock_filename, 633 svn_boolean_t is_global_lock, 634 apr_pool_t *pool) 635{ 636 apr_pool_t *subpool = svn_pool_create(pool); 637 svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool); 638 639 if (!err) 640 { 641 fs_fs_data_t *ffd = fs->fsap_data; 642 643 if (is_global_lock) 644 { 645 /* set the "got the lock" flag and register reset function */ 646 apr_pool_cleanup_register(subpool, 647 ffd, 648 reset_lock_flag, 649 apr_pool_cleanup_null); 650 ffd->has_write_lock = TRUE; 651 } 652 653 /* nobody else will modify the repo state 654 => read HEAD & pack info once */ 655 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 656 SVN_ERR(update_min_unpacked_rev(fs, pool)); 657 SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path, 658 pool)); 659 err = body(baton, subpool); 660 } 661 662 svn_pool_destroy(subpool); 663 664 return svn_error_trace(err); 665} 666 667svn_error_t * 668svn_fs_fs__with_write_lock(svn_fs_t *fs, 669 svn_error_t *(*body)(void *baton, 670 apr_pool_t *pool), 671 void *baton, 672 apr_pool_t *pool) 673{ 674 fs_fs_data_t *ffd = fs->fsap_data; 675 fs_fs_shared_data_t *ffsd = ffd->shared; 676 677 SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock, 678 with_some_lock_file(fs, body, baton, 679 path_lock(fs, pool), 680 TRUE, 681 pool)); 682 683 return SVN_NO_ERROR; 684} 685 686/* Run BODY (with BATON and POOL) while the txn-current file 687 of FS is locked. */ 688static svn_error_t * 689with_txn_current_lock(svn_fs_t *fs, 690 svn_error_t *(*body)(void *baton, 691 apr_pool_t *pool), 692 void *baton, 693 apr_pool_t *pool) 694{ 695 fs_fs_data_t *ffd = fs->fsap_data; 696 fs_fs_shared_data_t *ffsd = ffd->shared; 697 698 SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock, 699 with_some_lock_file(fs, body, baton, 700 path_txn_current_lock(fs, pool), 701 FALSE, 702 pool)); 703 704 return SVN_NO_ERROR; 705} 706 707/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(), 708 which see. */ 709struct unlock_proto_rev_baton 710{ 711 const char *txn_id; 712 void *lockcookie; 713}; 714 715/* Callback used in the implementation of unlock_proto_rev(). */ 716static svn_error_t * 717unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 718{ 719 const struct unlock_proto_rev_baton *b = baton; 720 const char *txn_id = b->txn_id; 721 apr_file_t *lockfile = b->lockcookie; 722 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE); 723 apr_status_t apr_err; 724 725 if (!txn) 726 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 727 _("Can't unlock unknown transaction '%s'"), 728 txn_id); 729 if (!txn->being_written) 730 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 731 _("Can't unlock nonlocked transaction '%s'"), 732 txn_id); 733 734 apr_err = apr_file_unlock(lockfile); 735 if (apr_err) 736 return svn_error_wrap_apr 737 (apr_err, 738 _("Can't unlock prototype revision lockfile for transaction '%s'"), 739 txn_id); 740 apr_err = apr_file_close(lockfile); 741 if (apr_err) 742 return svn_error_wrap_apr 743 (apr_err, 744 _("Can't close prototype revision lockfile for transaction '%s'"), 745 txn_id); 746 747 txn->being_written = FALSE; 748 749 return SVN_NO_ERROR; 750} 751 752/* Unlock the prototype revision file for transaction TXN_ID in filesystem 753 FS using cookie LOCKCOOKIE. The original prototype revision file must 754 have been closed _before_ calling this function. 755 756 Perform temporary allocations in POOL. */ 757static svn_error_t * 758unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie, 759 apr_pool_t *pool) 760{ 761 struct unlock_proto_rev_baton b; 762 763 b.txn_id = txn_id; 764 b.lockcookie = lockcookie; 765 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool); 766} 767 768/* Same as unlock_proto_rev(), but requires that the transaction list 769 lock is already held. */ 770static svn_error_t * 771unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id, 772 void *lockcookie, 773 apr_pool_t *pool) 774{ 775 struct unlock_proto_rev_baton b; 776 777 b.txn_id = txn_id; 778 b.lockcookie = lockcookie; 779 return unlock_proto_rev_body(fs, &b, pool); 780} 781 782/* A structure used by get_writable_proto_rev() and 783 get_writable_proto_rev_body(), which see. */ 784struct get_writable_proto_rev_baton 785{ 786 apr_file_t **file; 787 void **lockcookie; 788 const char *txn_id; 789}; 790 791/* Callback used in the implementation of get_writable_proto_rev(). */ 792static svn_error_t * 793get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 794{ 795 const struct get_writable_proto_rev_baton *b = baton; 796 apr_file_t **file = b->file; 797 void **lockcookie = b->lockcookie; 798 const char *txn_id = b->txn_id; 799 svn_error_t *err; 800 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE); 801 802 /* First, ensure that no thread in this process (including this one) 803 is currently writing to this transaction's proto-rev file. */ 804 if (txn->being_written) 805 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 806 _("Cannot write to the prototype revision file " 807 "of transaction '%s' because a previous " 808 "representation is currently being written by " 809 "this process"), 810 txn_id); 811 812 813 /* We know that no thread in this process is writing to the proto-rev 814 file, and by extension, that no thread in this process is holding a 815 lock on the prototype revision lock file. It is therefore safe 816 for us to attempt to lock this file, to see if any other process 817 is holding a lock. */ 818 819 { 820 apr_file_t *lockfile; 821 apr_status_t apr_err; 822 const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool); 823 824 /* Open the proto-rev lockfile, creating it if necessary, as it may 825 not exist if the transaction dates from before the lockfiles were 826 introduced. 827 828 ### We'd also like to use something like svn_io_file_lock2(), but 829 that forces us to create a subpool just to be able to unlock 830 the file, which seems a waste. */ 831 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path, 832 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 833 834 apr_err = apr_file_lock(lockfile, 835 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK); 836 if (apr_err) 837 { 838 svn_error_clear(svn_io_file_close(lockfile, pool)); 839 840 if (APR_STATUS_IS_EAGAIN(apr_err)) 841 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 842 _("Cannot write to the prototype revision " 843 "file of transaction '%s' because a " 844 "previous representation is currently " 845 "being written by another process"), 846 txn_id); 847 848 return svn_error_wrap_apr(apr_err, 849 _("Can't get exclusive lock on file '%s'"), 850 svn_dirent_local_style(lockfile_path, pool)); 851 } 852 853 *lockcookie = lockfile; 854 } 855 856 /* We've successfully locked the transaction; mark it as such. */ 857 txn->being_written = TRUE; 858 859 860 /* Now open the prototype revision file and seek to the end. */ 861 err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool), 862 APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool); 863 864 /* You might expect that we could dispense with the following seek 865 and achieve the same thing by opening the file using APR_APPEND. 866 Unfortunately, APR's buffered file implementation unconditionally 867 places its initial file pointer at the start of the file (even for 868 files opened with APR_APPEND), so we need this seek to reconcile 869 the APR file pointer to the OS file pointer (since we need to be 870 able to read the current file position later). */ 871 if (!err) 872 { 873 apr_off_t offset = 0; 874 err = svn_io_file_seek(*file, APR_END, &offset, pool); 875 } 876 877 if (err) 878 { 879 err = svn_error_compose_create( 880 err, 881 unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool)); 882 883 *lockcookie = NULL; 884 } 885 886 return svn_error_trace(err); 887} 888 889/* Get a handle to the prototype revision file for transaction TXN_ID in 890 filesystem FS, and lock it for writing. Return FILE, a file handle 891 positioned at the end of the file, and LOCKCOOKIE, a cookie that 892 should be passed to unlock_proto_rev() to unlock the file once FILE 893 has been closed. 894 895 If the prototype revision file is already locked, return error 896 SVN_ERR_FS_REP_BEING_WRITTEN. 897 898 Perform all allocations in POOL. */ 899static svn_error_t * 900get_writable_proto_rev(apr_file_t **file, 901 void **lockcookie, 902 svn_fs_t *fs, const char *txn_id, 903 apr_pool_t *pool) 904{ 905 struct get_writable_proto_rev_baton b; 906 907 b.file = file; 908 b.lockcookie = lockcookie; 909 b.txn_id = txn_id; 910 911 return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool); 912} 913 914/* Callback used in the implementation of purge_shared_txn(). */ 915static svn_error_t * 916purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 917{ 918 const char *txn_id = baton; 919 920 free_shared_txn(fs, txn_id); 921 svn_fs_fs__reset_txn_caches(fs); 922 923 return SVN_NO_ERROR; 924} 925 926/* Purge the shared data for transaction TXN_ID in filesystem FS. 927 Perform all allocations in POOL. */ 928static svn_error_t * 929purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 930{ 931 return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool); 932} 933 934 935 936/* Fetch the current offset of FILE into *OFFSET_P. */ 937static svn_error_t * 938get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool) 939{ 940 apr_off_t offset; 941 942 /* Note that, for buffered files, one (possibly surprising) side-effect 943 of this call is to flush any unwritten data to disk. */ 944 offset = 0; 945 SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); 946 *offset_p = offset; 947 948 return SVN_NO_ERROR; 949} 950 951 952/* Check that BUF, a nul-terminated buffer of text from file PATH, 953 contains only digits at OFFSET and beyond, raising an error if not. 954 TITLE contains a user-visible description of the file, usually the 955 short file name. 956 957 Uses POOL for temporary allocation. */ 958static svn_error_t * 959check_file_buffer_numeric(const char *buf, apr_off_t offset, 960 const char *path, const char *title, 961 apr_pool_t *pool) 962{ 963 const char *p; 964 965 for (p = buf + offset; *p; p++) 966 if (!svn_ctype_isdigit(*p)) 967 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 968 _("%s file '%s' contains unexpected non-digit '%c' within '%s'"), 969 title, svn_dirent_local_style(path, pool), *p, buf); 970 971 return SVN_NO_ERROR; 972} 973 974/* Check that BUF, a nul-terminated buffer of text from format file PATH, 975 contains only digits at OFFSET and beyond, raising an error if not. 976 977 Uses POOL for temporary allocation. */ 978static svn_error_t * 979check_format_file_buffer_numeric(const char *buf, apr_off_t offset, 980 const char *path, apr_pool_t *pool) 981{ 982 return check_file_buffer_numeric(buf, offset, path, "Format", pool); 983} 984 985/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format 986 number is not the same as a format number supported by this 987 Subversion. */ 988static svn_error_t * 989check_format(int format) 990{ 991 /* Blacklist. These formats may be either younger or older than 992 SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */ 993 if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT) 994 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 995 _("Found format '%d', only created by " 996 "unreleased dev builds; see " 997 "http://subversion.apache.org" 998 "/docs/release-notes/1.7#revprop-packing"), 999 format); 1000 1001 /* We support all formats from 1-current simultaneously */ 1002 if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER) 1003 return SVN_NO_ERROR; 1004 1005 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 1006 _("Expected FS format between '1' and '%d'; found format '%d'"), 1007 SVN_FS_FS__FORMAT_NUMBER, format); 1008} 1009 1010/* Read the format number and maximum number of files per directory 1011 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR 1012 respectively. 1013 1014 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and 1015 will be set to zero if a linear scheme should be used. 1016 1017 Use POOL for temporary allocation. */ 1018static svn_error_t * 1019read_format(int *pformat, int *max_files_per_dir, 1020 const char *path, apr_pool_t *pool) 1021{ 1022 svn_error_t *err; 1023 svn_stream_t *stream; 1024 svn_stringbuf_t *content; 1025 svn_stringbuf_t *buf; 1026 svn_boolean_t eos = FALSE; 1027 1028 err = svn_stringbuf_from_file2(&content, path, pool); 1029 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1030 { 1031 /* Treat an absent format file as format 1. Do not try to 1032 create the format file on the fly, because the repository 1033 might be read-only for us, or this might be a read-only 1034 operation, and the spirit of FSFS is to make no changes 1035 whatseover in read-only operations. See thread starting at 1036 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600 1037 for more. */ 1038 svn_error_clear(err); 1039 *pformat = 1; 1040 *max_files_per_dir = 0; 1041 1042 return SVN_NO_ERROR; 1043 } 1044 SVN_ERR(err); 1045 1046 stream = svn_stream_from_stringbuf(content, pool); 1047 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 1048 if (buf->len == 0 && eos) 1049 { 1050 /* Return a more useful error message. */ 1051 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 1052 _("Can't read first line of format file '%s'"), 1053 svn_dirent_local_style(path, pool)); 1054 } 1055 1056 /* Check that the first line contains only digits. */ 1057 SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool)); 1058 SVN_ERR(svn_cstring_atoi(pformat, buf->data)); 1059 1060 /* Check that we support this format at all */ 1061 SVN_ERR(check_format(*pformat)); 1062 1063 /* Set the default values for anything that can be set via an option. */ 1064 *max_files_per_dir = 0; 1065 1066 /* Read any options. */ 1067 while (!eos) 1068 { 1069 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 1070 if (buf->len == 0) 1071 break; 1072 1073 if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT && 1074 strncmp(buf->data, "layout ", 7) == 0) 1075 { 1076 if (strcmp(buf->data + 7, "linear") == 0) 1077 { 1078 *max_files_per_dir = 0; 1079 continue; 1080 } 1081 1082 if (strncmp(buf->data + 7, "sharded ", 8) == 0) 1083 { 1084 /* Check that the argument is numeric. */ 1085 SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool)); 1086 SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15)); 1087 continue; 1088 } 1089 } 1090 1091 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 1092 _("'%s' contains invalid filesystem format option '%s'"), 1093 svn_dirent_local_style(path, pool), buf->data); 1094 } 1095 1096 return SVN_NO_ERROR; 1097} 1098 1099/* Write the format number and maximum number of files per directory 1100 to a new format file in PATH, possibly expecting to overwrite a 1101 previously existing file. 1102 1103 Use POOL for temporary allocation. */ 1104static svn_error_t * 1105write_format(const char *path, int format, int max_files_per_dir, 1106 svn_boolean_t overwrite, apr_pool_t *pool) 1107{ 1108 svn_stringbuf_t *sb; 1109 1110 SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER); 1111 1112 sb = svn_stringbuf_createf(pool, "%d\n", format); 1113 1114 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 1115 { 1116 if (max_files_per_dir) 1117 svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n", 1118 max_files_per_dir)); 1119 else 1120 svn_stringbuf_appendcstr(sb, "layout linear\n"); 1121 } 1122 1123 /* svn_io_write_version_file() does a load of magic to allow it to 1124 replace version files that already exist. We only need to do 1125 that when we're allowed to overwrite an existing file. */ 1126 if (! overwrite) 1127 { 1128 /* Create the file */ 1129 SVN_ERR(svn_io_file_create(path, sb->data, pool)); 1130 } 1131 else 1132 { 1133 const char *path_tmp; 1134 1135 SVN_ERR(svn_io_write_unique(&path_tmp, 1136 svn_dirent_dirname(path, pool), 1137 sb->data, sb->len, 1138 svn_io_file_del_none, pool)); 1139 1140 /* rename the temp file as the real destination */ 1141 SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); 1142 } 1143 1144 /* And set the perms to make it read only */ 1145 return svn_io_set_file_read_only(path, FALSE, pool); 1146} 1147 1148svn_boolean_t 1149svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs) 1150{ 1151 fs_fs_data_t *ffd = fs->fsap_data; 1152 return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT; 1153} 1154 1155/* Read the configuration information of the file system at FS_PATH 1156 * and set the respective values in FFD. Use POOL for allocations. 1157 */ 1158static svn_error_t * 1159read_config(fs_fs_data_t *ffd, 1160 const char *fs_path, 1161 apr_pool_t *pool) 1162{ 1163 SVN_ERR(svn_config_read3(&ffd->config, 1164 svn_dirent_join(fs_path, PATH_CONFIG, pool), 1165 FALSE, FALSE, FALSE, pool)); 1166 1167 /* Initialize ffd->rep_sharing_allowed. */ 1168 if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 1169 SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed, 1170 CONFIG_SECTION_REP_SHARING, 1171 CONFIG_OPTION_ENABLE_REP_SHARING, TRUE)); 1172 else 1173 ffd->rep_sharing_allowed = FALSE; 1174 1175 /* Initialize deltification settings in ffd. */ 1176 if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT) 1177 { 1178 SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories, 1179 CONFIG_SECTION_DELTIFICATION, 1180 CONFIG_OPTION_ENABLE_DIR_DELTIFICATION, 1181 FALSE)); 1182 SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties, 1183 CONFIG_SECTION_DELTIFICATION, 1184 CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION, 1185 FALSE)); 1186 SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk, 1187 CONFIG_SECTION_DELTIFICATION, 1188 CONFIG_OPTION_MAX_DELTIFICATION_WALK, 1189 SVN_FS_FS_MAX_DELTIFICATION_WALK)); 1190 SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification, 1191 CONFIG_SECTION_DELTIFICATION, 1192 CONFIG_OPTION_MAX_LINEAR_DELTIFICATION, 1193 SVN_FS_FS_MAX_LINEAR_DELTIFICATION)); 1194 } 1195 else 1196 { 1197 ffd->deltify_directories = FALSE; 1198 ffd->deltify_properties = FALSE; 1199 ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK; 1200 ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION; 1201 } 1202 1203 /* Initialize revprop packing settings in ffd. */ 1204 if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 1205 { 1206 SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops, 1207 CONFIG_SECTION_PACKED_REVPROPS, 1208 CONFIG_OPTION_COMPRESS_PACKED_REVPROPS, 1209 FALSE)); 1210 SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size, 1211 CONFIG_SECTION_PACKED_REVPROPS, 1212 CONFIG_OPTION_REVPROP_PACK_SIZE, 1213 ffd->compress_packed_revprops 1214 ? 0x100 1215 : 0x40)); 1216 1217 ffd->revprop_pack_size *= 1024; 1218 } 1219 else 1220 { 1221 ffd->revprop_pack_size = 0x10000; 1222 ffd->compress_packed_revprops = FALSE; 1223 } 1224 1225 return SVN_NO_ERROR; 1226} 1227 1228static svn_error_t * 1229write_config(svn_fs_t *fs, 1230 apr_pool_t *pool) 1231{ 1232#define NL APR_EOL_STR 1233 static const char * const fsfs_conf_contents = 1234"### This file controls the configuration of the FSFS filesystem." NL 1235"" NL 1236"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL 1237"### These options name memcached servers used to cache internal FSFS" NL 1238"### data. See http://www.danga.com/memcached/ for more information on" NL 1239"### memcached. To use memcached with FSFS, run one or more memcached" NL 1240"### servers, and specify each of them as an option like so:" NL 1241"# first-server = 127.0.0.1:11211" NL 1242"# remote-memcached = mymemcached.corp.example.com:11212" NL 1243"### The option name is ignored; the value is of the form HOST:PORT." NL 1244"### memcached servers can be shared between multiple repositories;" NL 1245"### however, if you do this, you *must* ensure that repositories have" NL 1246"### distinct UUIDs and paths, or else cached data from one repository" NL 1247"### might be used by another accidentally. Note also that memcached has" NL 1248"### no authentication for reads or writes, so you must ensure that your" NL 1249"### memcached servers are only accessible by trusted users." NL 1250"" NL 1251"[" CONFIG_SECTION_CACHES "]" NL 1252"### When a cache-related error occurs, normally Subversion ignores it" NL 1253"### and continues, logging an error if the server is appropriately" NL 1254"### configured (and ignoring it with file:// access). To make" NL 1255"### Subversion never ignore cache errors, uncomment this line." NL 1256"# " CONFIG_OPTION_FAIL_STOP " = true" NL 1257"" NL 1258"[" CONFIG_SECTION_REP_SHARING "]" NL 1259"### To conserve space, the filesystem can optionally avoid storing" NL 1260"### duplicate representations. This comes at a slight cost in" NL 1261"### performance, as maintaining a database of shared representations can" NL 1262"### increase commit times. The space savings are dependent upon the size" NL 1263"### of the repository, the number of objects it contains and the amount of" NL 1264"### duplication between them, usually a function of the branching and" NL 1265"### merging process." NL 1266"###" NL 1267"### The following parameter enables rep-sharing in the repository. It can" NL 1268"### be switched on and off at will, but for best space-saving results" NL 1269"### should be enabled consistently over the life of the repository." NL 1270"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL 1271"### rep-sharing is enabled by default." NL 1272"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL 1273"" NL 1274"[" CONFIG_SECTION_DELTIFICATION "]" NL 1275"### To conserve space, the filesystem stores data as differences against" NL 1276"### existing representations. This comes at a slight cost in performance," NL 1277"### as calculating differences can increase commit times. Reading data" NL 1278"### will also create higher CPU load and the data will be fragmented." NL 1279"### Since deltification tends to save significant amounts of disk space," NL 1280"### the overall I/O load can actually be lower." NL 1281"###" NL 1282"### The options in this section allow for tuning the deltification" NL 1283"### strategy. Their effects on data size and server performance may vary" NL 1284"### from one repository to another. Versions prior to 1.8 will ignore" NL 1285"### this section." NL 1286"###" NL 1287"### The following parameter enables deltification for directories. It can" NL 1288"### be switched on and off at will, but for best space-saving results" NL 1289"### should be enabled consistently over the life of the repository." NL 1290"### Repositories containing large directories will benefit greatly." NL 1291"### In rarely read repositories, the I/O overhead may be significant as" NL 1292"### cache hit rates will most likely be low" NL 1293"### directory deltification is disabled by default." NL 1294"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false" NL 1295"###" NL 1296"### The following parameter enables deltification for properties on files" NL 1297"### and directories. Overall, this is a minor tuning option but can save" NL 1298"### some disk space if you merge frequently or frequently change node" NL 1299"### properties. You should not activate this if rep-sharing has been" NL 1300"### disabled because this may result in a net increase in repository size." NL 1301"### property deltification is disabled by default." NL 1302"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false" NL 1303"###" NL 1304"### During commit, the server may need to walk the whole change history of" NL 1305"### of a given node to find a suitable deltification base. This linear" NL 1306"### process can impact commit times, svnadmin load and similar operations." NL 1307"### This setting limits the depth of the deltification history. If the" NL 1308"### threshold has been reached, the node will be stored as fulltext and a" NL 1309"### new deltification history begins." NL 1310"### Note, this is unrelated to svn log." NL 1311"### Very large values rarely provide significant additional savings but" NL 1312"### can impact performance greatly - in particular if directory" NL 1313"### deltification has been activated. Very small values may be useful in" NL 1314"### repositories that are dominated by large, changing binaries." NL 1315"### Should be a power of two minus 1. A value of 0 will effectively" NL 1316"### disable deltification." NL 1317"### For 1.8, the default value is 1023; earlier versions have no limit." NL 1318"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL 1319"###" NL 1320"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL 1321"### delta information where a simple delta against the latest version is" NL 1322"### often smaller. By default, 1.8+ will therefore use skip deltas only" NL 1323"### after the linear chain of deltas has grown beyond the threshold" NL 1324"### specified by this setting." NL 1325"### Values up to 64 can result in some reduction in repository size for" NL 1326"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL 1327"### numbers can reduce those costs at the cost of more disk space. For" NL 1328"### rarely read repositories or those containing larger binaries, this may" NL 1329"### present a better trade-off." NL 1330"### Should be a power of two. A value of 1 or smaller will cause the" NL 1331"### exclusive use of skip-deltas (as in pre-1.8)." NL 1332"### For 1.8, the default value is 16; earlier versions use 1." NL 1333"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL 1334"" NL 1335"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL 1336"### This parameter controls the size (in kBytes) of packed revprop files." NL 1337"### Revprops of consecutive revisions will be concatenated into a single" NL 1338"### file up to but not exceeding the threshold given here. However, each" NL 1339"### pack file may be much smaller and revprops of a single revision may be" NL 1340"### much larger than the limit set here. The threshold will be applied" NL 1341"### before optional compression takes place." NL 1342"### Large values will reduce disk space usage at the expense of increased" NL 1343"### latency and CPU usage reading and changing individual revprops. They" NL 1344"### become an advantage when revprop caching has been enabled because a" NL 1345"### lot of data can be read in one go. Values smaller than 4 kByte will" NL 1346"### not improve latency any further and quickly render revprop packing" NL 1347"### ineffective." NL 1348"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL 1349"### pack files and 256 kBytes when compression has been enabled." NL 1350"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL 1351"###" NL 1352"### To save disk space, packed revprop files may be compressed. Standard" NL 1353"### revprops tend to allow for very effective compression. Reading and" NL 1354"### even more so writing, become significantly more CPU intensive. With" NL 1355"### revprop caching enabled, the overhead can be offset by reduced I/O" NL 1356"### unless you often modify revprops after packing." NL 1357"### Compressing packed revprops is disabled by default." NL 1358"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL 1359; 1360#undef NL 1361 return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1362 fsfs_conf_contents, pool); 1363} 1364 1365static svn_error_t * 1366read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev, 1367 const char *path, 1368 apr_pool_t *pool) 1369{ 1370 char buf[80]; 1371 apr_file_t *file; 1372 apr_size_t len; 1373 1374 SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED, 1375 APR_OS_DEFAULT, pool)); 1376 len = sizeof(buf); 1377 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 1378 SVN_ERR(svn_io_file_close(file, pool)); 1379 1380 *min_unpacked_rev = SVN_STR_TO_REV(buf); 1381 return SVN_NO_ERROR; 1382} 1383 1384static svn_error_t * 1385update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool) 1386{ 1387 fs_fs_data_t *ffd = fs->fsap_data; 1388 1389 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT); 1390 1391 return read_min_unpacked_rev(&ffd->min_unpacked_rev, 1392 path_min_unpacked_rev(fs, pool), 1393 pool); 1394} 1395 1396svn_error_t * 1397svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool) 1398{ 1399 fs_fs_data_t *ffd = fs->fsap_data; 1400 apr_file_t *uuid_file; 1401 int format, max_files_per_dir; 1402 char buf[APR_UUID_FORMATTED_LENGTH + 2]; 1403 apr_size_t limit; 1404 1405 fs->path = apr_pstrdup(fs->pool, path); 1406 1407 /* Read the FS format number. */ 1408 SVN_ERR(read_format(&format, &max_files_per_dir, 1409 path_format(fs, pool), pool)); 1410 1411 /* Now we've got a format number no matter what. */ 1412 ffd->format = format; 1413 ffd->max_files_per_dir = max_files_per_dir; 1414 1415 /* Read in and cache the repository uuid. */ 1416 SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool), 1417 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 1418 1419 limit = sizeof(buf); 1420 SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool)); 1421 fs->uuid = apr_pstrdup(fs->pool, buf); 1422 1423 SVN_ERR(svn_io_file_close(uuid_file, pool)); 1424 1425 /* Read the min unpacked revision. */ 1426 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1427 SVN_ERR(update_min_unpacked_rev(fs, pool)); 1428 1429 /* Read the configuration file. */ 1430 SVN_ERR(read_config(ffd, fs->path, pool)); 1431 1432 return get_youngest(&(ffd->youngest_rev_cache), path, pool); 1433} 1434 1435/* Wrapper around svn_io_file_create which ignores EEXIST. */ 1436static svn_error_t * 1437create_file_ignore_eexist(const char *file, 1438 const char *contents, 1439 apr_pool_t *pool) 1440{ 1441 svn_error_t *err = svn_io_file_create(file, contents, pool); 1442 if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 1443 { 1444 svn_error_clear(err); 1445 err = SVN_NO_ERROR; 1446 } 1447 return svn_error_trace(err); 1448} 1449 1450/* forward declarations */ 1451 1452static svn_error_t * 1453pack_revprops_shard(const char *pack_file_dir, 1454 const char *shard_path, 1455 apr_int64_t shard, 1456 int max_files_per_dir, 1457 apr_off_t max_pack_size, 1458 int compression_level, 1459 svn_cancel_func_t cancel_func, 1460 void *cancel_baton, 1461 apr_pool_t *scratch_pool); 1462 1463static svn_error_t * 1464delete_revprops_shard(const char *shard_path, 1465 apr_int64_t shard, 1466 int max_files_per_dir, 1467 svn_cancel_func_t cancel_func, 1468 void *cancel_baton, 1469 apr_pool_t *scratch_pool); 1470 1471/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev. 1472 * 1473 * NOTE: Keep the old non-packed shards around until after the format bump. 1474 * Otherwise, re-running upgrade will drop the packed revprop shard but 1475 * have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after 1476 * the bump. 1477 * 1478 * Use SCRATCH_POOL for temporary allocations. 1479 */ 1480static svn_error_t * 1481upgrade_pack_revprops(svn_fs_t *fs, 1482 apr_pool_t *scratch_pool) 1483{ 1484 fs_fs_data_t *ffd = fs->fsap_data; 1485 const char *revprops_shard_path; 1486 const char *revprops_pack_file_dir; 1487 apr_int64_t shard; 1488 apr_int64_t first_unpacked_shard 1489 = ffd->min_unpacked_rev / ffd->max_files_per_dir; 1490 1491 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1492 const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, 1493 scratch_pool); 1494 int compression_level = ffd->compress_packed_revprops 1495 ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 1496 : SVN_DELTA_COMPRESSION_LEVEL_NONE; 1497 1498 /* first, pack all revprops shards to match the packed revision shards */ 1499 for (shard = 0; shard < first_unpacked_shard; ++shard) 1500 { 1501 revprops_pack_file_dir = svn_dirent_join(revsprops_dir, 1502 apr_psprintf(iterpool, 1503 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 1504 shard), 1505 iterpool); 1506 revprops_shard_path = svn_dirent_join(revsprops_dir, 1507 apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), 1508 iterpool); 1509 1510 SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path, 1511 shard, ffd->max_files_per_dir, 1512 (int)(0.9 * ffd->revprop_pack_size), 1513 compression_level, 1514 NULL, NULL, iterpool)); 1515 svn_pool_clear(iterpool); 1516 } 1517 1518 svn_pool_destroy(iterpool); 1519 1520 return SVN_NO_ERROR; 1521} 1522 1523/* In the filesystem FS, remove all non-packed revprop shards up to 1524 * min_unpacked_rev. Use SCRATCH_POOL for temporary allocations. 1525 * See upgrade_pack_revprops for more info. 1526 */ 1527static svn_error_t * 1528upgrade_cleanup_pack_revprops(svn_fs_t *fs, 1529 apr_pool_t *scratch_pool) 1530{ 1531 fs_fs_data_t *ffd = fs->fsap_data; 1532 const char *revprops_shard_path; 1533 apr_int64_t shard; 1534 apr_int64_t first_unpacked_shard 1535 = ffd->min_unpacked_rev / ffd->max_files_per_dir; 1536 1537 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1538 const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, 1539 scratch_pool); 1540 1541 /* delete the non-packed revprops shards afterwards */ 1542 for (shard = 0; shard < first_unpacked_shard; ++shard) 1543 { 1544 revprops_shard_path = svn_dirent_join(revsprops_dir, 1545 apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), 1546 iterpool); 1547 SVN_ERR(delete_revprops_shard(revprops_shard_path, 1548 shard, ffd->max_files_per_dir, 1549 NULL, NULL, iterpool)); 1550 svn_pool_clear(iterpool); 1551 } 1552 1553 svn_pool_destroy(iterpool); 1554 1555 return SVN_NO_ERROR; 1556} 1557 1558static svn_error_t * 1559upgrade_body(void *baton, apr_pool_t *pool) 1560{ 1561 svn_fs_t *fs = baton; 1562 int format, max_files_per_dir; 1563 const char *format_path = path_format(fs, pool); 1564 svn_node_kind_t kind; 1565 svn_boolean_t needs_revprop_shard_cleanup = FALSE; 1566 1567 /* Read the FS format number and max-files-per-dir setting. */ 1568 SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool)); 1569 1570 /* If the config file does not exist, create one. */ 1571 SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1572 &kind, pool)); 1573 switch (kind) 1574 { 1575 case svn_node_none: 1576 SVN_ERR(write_config(fs, pool)); 1577 break; 1578 case svn_node_file: 1579 break; 1580 default: 1581 return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, 1582 _("'%s' is not a regular file." 1583 " Please move it out of " 1584 "the way and try again"), 1585 svn_dirent_join(fs->path, PATH_CONFIG, pool)); 1586 } 1587 1588 /* If we're already up-to-date, there's nothing else to be done here. */ 1589 if (format == SVN_FS_FS__FORMAT_NUMBER) 1590 return SVN_NO_ERROR; 1591 1592 /* If our filesystem predates the existance of the 'txn-current 1593 file', make that file and its corresponding lock file. */ 1594 if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 1595 { 1596 SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n", 1597 pool)); 1598 SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "", 1599 pool)); 1600 } 1601 1602 /* If our filesystem predates the existance of the 'txn-protorevs' 1603 dir, make that directory. */ 1604 if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 1605 { 1606 /* We don't use path_txn_proto_rev() here because it expects 1607 we've already bumped our format. */ 1608 SVN_ERR(svn_io_make_dir_recursively( 1609 svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool)); 1610 } 1611 1612 /* If our filesystem is new enough, write the min unpacked rev file. */ 1613 if (format < SVN_FS_FS__MIN_PACKED_FORMAT) 1614 SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool)); 1615 1616 /* If the file system supports revision packing but not revprop packing 1617 *and* the FS has been sharded, pack the revprops up to the point that 1618 revision data has been packed. However, keep the non-packed revprop 1619 files around until after the format bump */ 1620 if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT 1621 && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 1622 && max_files_per_dir > 0) 1623 { 1624 needs_revprop_shard_cleanup = TRUE; 1625 SVN_ERR(upgrade_pack_revprops(fs, pool)); 1626 } 1627 1628 /* Bump the format file. */ 1629 SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, 1630 max_files_per_dir, TRUE, pool)); 1631 1632 /* Now, it is safe to remove the redundant revprop files. */ 1633 if (needs_revprop_shard_cleanup) 1634 SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool)); 1635 1636 /* Done */ 1637 return SVN_NO_ERROR; 1638} 1639 1640 1641svn_error_t * 1642svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool) 1643{ 1644 return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool); 1645} 1646 1647 1648/* Functions for dealing with recoverable errors on mutable files 1649 * 1650 * Revprops, current, and txn-current files are mutable; that is, they 1651 * change as part of normal fsfs operation, in constrat to revs files, or 1652 * the format file, which are written once at create (or upgrade) time. 1653 * When more than one host writes to the same repository, we will 1654 * sometimes see these recoverable errors when accesssing these files. 1655 * 1656 * These errors all relate to NFS, and thus we only use this retry code if 1657 * ESTALE is defined. 1658 * 1659 ** ESTALE 1660 * 1661 * In NFS v3 and under, the server doesn't track opened files. If you 1662 * unlink(2) or rename(2) a file held open by another process *on the 1663 * same host*, that host's kernel typically renames the file to 1664 * .nfsXXXX and automatically deletes that when it's no longer open, 1665 * but this behavior is not required. 1666 * 1667 * For obvious reasons, this does not work *across hosts*. No one 1668 * knows about the opened file; not the server, and not the deleting 1669 * client. So the file vanishes, and the reader gets stale NFS file 1670 * handle. 1671 * 1672 ** EIO, ENOENT 1673 * 1674 * Some client implementations (at least the 2.6.18.5 kernel that ships 1675 * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or 1676 * even EIO errors when trying to read these files that have been renamed 1677 * over on some other host. 1678 * 1679 ** Solution 1680 * 1681 * Try open and read of such files in try_stringbuf_from_file(). Call 1682 * this function within a loop of RECOVERABLE_RETRY_COUNT iterations 1683 * (though, realistically, the second try will succeed). 1684 */ 1685 1686#define RECOVERABLE_RETRY_COUNT 10 1687 1688/* Read the file at PATH and return its content in *CONTENT. *CONTENT will 1689 * not be modified unless the whole file was read successfully. 1690 * 1691 * ESTALE, EIO and ENOENT will not cause this function to return an error 1692 * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate 1693 * missing files (ENOENT) there. 1694 * 1695 * Use POOL for allocations. 1696 */ 1697static svn_error_t * 1698try_stringbuf_from_file(svn_stringbuf_t **content, 1699 svn_boolean_t *missing, 1700 const char *path, 1701 svn_boolean_t last_attempt, 1702 apr_pool_t *pool) 1703{ 1704 svn_error_t *err = svn_stringbuf_from_file2(content, path, pool); 1705 if (missing) 1706 *missing = FALSE; 1707 1708 if (err) 1709 { 1710 *content = NULL; 1711 1712 if (APR_STATUS_IS_ENOENT(err->apr_err)) 1713 { 1714 if (!last_attempt) 1715 { 1716 svn_error_clear(err); 1717 if (missing) 1718 *missing = TRUE; 1719 return SVN_NO_ERROR; 1720 } 1721 } 1722#ifdef ESTALE 1723 else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE 1724 || APR_TO_OS_ERROR(err->apr_err) == EIO) 1725 { 1726 if (!last_attempt) 1727 { 1728 svn_error_clear(err); 1729 return SVN_NO_ERROR; 1730 } 1731 } 1732#endif 1733 } 1734 1735 return svn_error_trace(err); 1736} 1737 1738/* Read the 'current' file FNAME and store the contents in *BUF. 1739 Allocations are performed in POOL. */ 1740static svn_error_t * 1741read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool) 1742{ 1743 int i; 1744 *content = NULL; 1745 1746 for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i) 1747 SVN_ERR(try_stringbuf_from_file(content, NULL, 1748 fname, i + 1 < RECOVERABLE_RETRY_COUNT, 1749 pool)); 1750 1751 if (!*content) 1752 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1753 _("Can't read '%s'"), 1754 svn_dirent_local_style(fname, pool)); 1755 1756 return SVN_NO_ERROR; 1757} 1758 1759/* Find the youngest revision in a repository at path FS_PATH and 1760 return it in *YOUNGEST_P. Perform temporary allocations in 1761 POOL. */ 1762static svn_error_t * 1763get_youngest(svn_revnum_t *youngest_p, 1764 const char *fs_path, 1765 apr_pool_t *pool) 1766{ 1767 svn_stringbuf_t *buf; 1768 SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool), 1769 pool)); 1770 1771 *youngest_p = SVN_STR_TO_REV(buf->data); 1772 1773 return SVN_NO_ERROR; 1774} 1775 1776 1777svn_error_t * 1778svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p, 1779 svn_fs_t *fs, 1780 apr_pool_t *pool) 1781{ 1782 fs_fs_data_t *ffd = fs->fsap_data; 1783 1784 SVN_ERR(get_youngest(youngest_p, fs->path, pool)); 1785 ffd->youngest_rev_cache = *youngest_p; 1786 1787 return SVN_NO_ERROR; 1788} 1789 1790/* Given a revision file FILE that has been pre-positioned at the 1791 beginning of a Node-Rev header block, read in that header block and 1792 store it in the apr_hash_t HEADERS. All allocations will be from 1793 POOL. */ 1794static svn_error_t * read_header_block(apr_hash_t **headers, 1795 svn_stream_t *stream, 1796 apr_pool_t *pool) 1797{ 1798 *headers = apr_hash_make(pool); 1799 1800 while (1) 1801 { 1802 svn_stringbuf_t *header_str; 1803 const char *name, *value; 1804 apr_size_t i = 0; 1805 svn_boolean_t eof; 1806 1807 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool)); 1808 1809 if (eof || header_str->len == 0) 1810 break; /* end of header block */ 1811 1812 while (header_str->data[i] != ':') 1813 { 1814 if (header_str->data[i] == '\0') 1815 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1816 _("Found malformed header '%s' in " 1817 "revision file"), 1818 header_str->data); 1819 i++; 1820 } 1821 1822 /* Create a 'name' string and point to it. */ 1823 header_str->data[i] = '\0'; 1824 name = header_str->data; 1825 1826 /* Skip over the NULL byte and the space following it. */ 1827 i += 2; 1828 1829 if (i > header_str->len) 1830 { 1831 /* Restore the original line for the error. */ 1832 i -= 2; 1833 header_str->data[i] = ':'; 1834 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1835 _("Found malformed header '%s' in " 1836 "revision file"), 1837 header_str->data); 1838 } 1839 1840 value = header_str->data + i; 1841 1842 /* header_str is safely in our pool, so we can use bits of it as 1843 key and value. */ 1844 svn_hash_sets(*headers, name, value); 1845 } 1846 1847 return SVN_NO_ERROR; 1848} 1849 1850/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer 1851 than the current youngest revision or is simply not a valid 1852 revision number, else return success. 1853 1854 FSFS is based around the concept that commits only take effect when 1855 the number in "current" is bumped. Thus if there happens to be a rev 1856 or revprops file installed for a revision higher than the one recorded 1857 in "current" (because a commit failed between installing the rev file 1858 and bumping "current", or because an administrator rolled back the 1859 repository by resetting "current" without deleting rev files, etc), it 1860 ought to be completely ignored. This function provides the check 1861 by which callers can make that decision. */ 1862static svn_error_t * 1863ensure_revision_exists(svn_fs_t *fs, 1864 svn_revnum_t rev, 1865 apr_pool_t *pool) 1866{ 1867 fs_fs_data_t *ffd = fs->fsap_data; 1868 1869 if (! SVN_IS_VALID_REVNUM(rev)) 1870 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1871 _("Invalid revision number '%ld'"), rev); 1872 1873 1874 /* Did the revision exist the last time we checked the current 1875 file? */ 1876 if (rev <= ffd->youngest_rev_cache) 1877 return SVN_NO_ERROR; 1878 1879 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool)); 1880 1881 /* Check again. */ 1882 if (rev <= ffd->youngest_rev_cache) 1883 return SVN_NO_ERROR; 1884 1885 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1886 _("No such revision %ld"), rev); 1887} 1888 1889svn_error_t * 1890svn_fs_fs__revision_exists(svn_revnum_t rev, 1891 svn_fs_t *fs, 1892 apr_pool_t *pool) 1893{ 1894 /* Different order of parameters. */ 1895 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 1896 return SVN_NO_ERROR; 1897} 1898 1899/* Open the correct revision file for REV. If the filesystem FS has 1900 been packed, *FILE will be set to the packed file; otherwise, set *FILE 1901 to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the 1902 file doesn't exist. 1903 1904 TODO: Consider returning an indication of whether this is a packed rev 1905 file, so the caller need not rely on is_packed_rev() which in turn 1906 relies on the cached FFD->min_unpacked_rev value not having changed 1907 since the rev file was opened. 1908 1909 Use POOL for allocations. */ 1910static svn_error_t * 1911open_pack_or_rev_file(apr_file_t **file, 1912 svn_fs_t *fs, 1913 svn_revnum_t rev, 1914 apr_pool_t *pool) 1915{ 1916 fs_fs_data_t *ffd = fs->fsap_data; 1917 svn_error_t *err; 1918 const char *path; 1919 svn_boolean_t retry = FALSE; 1920 1921 do 1922 { 1923 err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool); 1924 1925 /* open the revision file in buffered r/o mode */ 1926 if (! err) 1927 err = svn_io_file_open(file, path, 1928 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool); 1929 1930 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1931 { 1932 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1933 { 1934 /* Could not open the file. This may happen if the 1935 * file once existed but got packed later. */ 1936 svn_error_clear(err); 1937 1938 /* if that was our 2nd attempt, leave it at that. */ 1939 if (retry) 1940 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1941 _("No such revision %ld"), rev); 1942 1943 /* We failed for the first time. Refresh cache & retry. */ 1944 SVN_ERR(update_min_unpacked_rev(fs, pool)); 1945 1946 retry = TRUE; 1947 } 1948 else 1949 { 1950 svn_error_clear(err); 1951 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1952 _("No such revision %ld"), rev); 1953 } 1954 } 1955 else 1956 { 1957 retry = FALSE; 1958 } 1959 } 1960 while (retry); 1961 1962 return svn_error_trace(err); 1963} 1964 1965/* Reads a line from STREAM and converts it to a 64 bit integer to be 1966 * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave 1967 * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS" 1968 * error return. 1969 * SCRATCH_POOL is used for temporary allocations. 1970 */ 1971static svn_error_t * 1972read_number_from_stream(apr_int64_t *result, 1973 svn_boolean_t *hit_eof, 1974 svn_stream_t *stream, 1975 apr_pool_t *scratch_pool) 1976{ 1977 svn_stringbuf_t *sb; 1978 svn_boolean_t eof; 1979 svn_error_t *err; 1980 1981 SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool)); 1982 if (hit_eof) 1983 *hit_eof = eof; 1984 else 1985 if (eof) 1986 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF")); 1987 1988 if (!eof) 1989 { 1990 err = svn_cstring_atoi64(result, sb->data); 1991 if (err) 1992 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 1993 _("Number '%s' invalid or too large"), 1994 sb->data); 1995 } 1996 1997 return SVN_NO_ERROR; 1998} 1999 2000/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file. 2001 Use POOL for temporary allocations. */ 2002static svn_error_t * 2003get_packed_offset(apr_off_t *rev_offset, 2004 svn_fs_t *fs, 2005 svn_revnum_t rev, 2006 apr_pool_t *pool) 2007{ 2008 fs_fs_data_t *ffd = fs->fsap_data; 2009 svn_stream_t *manifest_stream; 2010 svn_boolean_t is_cached; 2011 svn_revnum_t shard; 2012 apr_int64_t shard_pos; 2013 apr_array_header_t *manifest; 2014 apr_pool_t *iterpool; 2015 2016 shard = rev / ffd->max_files_per_dir; 2017 2018 /* position of the shard within the manifest */ 2019 shard_pos = rev % ffd->max_files_per_dir; 2020 2021 /* fetch exactly that element into *rev_offset, if the manifest is found 2022 in the cache */ 2023 SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached, 2024 ffd->packed_offset_cache, &shard, 2025 svn_fs_fs__get_sharded_offset, &shard_pos, 2026 pool)); 2027 2028 if (is_cached) 2029 return SVN_NO_ERROR; 2030 2031 /* Open the manifest file. */ 2032 SVN_ERR(svn_stream_open_readonly(&manifest_stream, 2033 path_rev_packed(fs, rev, PATH_MANIFEST, 2034 pool), 2035 pool, pool)); 2036 2037 /* While we're here, let's just read the entire manifest file into an array, 2038 so we can cache the entire thing. */ 2039 iterpool = svn_pool_create(pool); 2040 manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t)); 2041 while (1) 2042 { 2043 svn_boolean_t eof; 2044 apr_int64_t val; 2045 2046 svn_pool_clear(iterpool); 2047 SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool)); 2048 if (eof) 2049 break; 2050 2051 APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val; 2052 } 2053 svn_pool_destroy(iterpool); 2054 2055 *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir, 2056 apr_off_t); 2057 2058 /* Close up shop and cache the array. */ 2059 SVN_ERR(svn_stream_close(manifest_stream)); 2060 return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool); 2061} 2062 2063/* Open the revision file for revision REV in filesystem FS and store 2064 the newly opened file in FILE. Seek to location OFFSET before 2065 returning. Perform temporary allocations in POOL. */ 2066static svn_error_t * 2067open_and_seek_revision(apr_file_t **file, 2068 svn_fs_t *fs, 2069 svn_revnum_t rev, 2070 apr_off_t offset, 2071 apr_pool_t *pool) 2072{ 2073 apr_file_t *rev_file; 2074 2075 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 2076 2077 SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool)); 2078 2079 if (is_packed_rev(fs, rev)) 2080 { 2081 apr_off_t rev_offset; 2082 2083 SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool)); 2084 offset += rev_offset; 2085 } 2086 2087 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2088 2089 *file = rev_file; 2090 2091 return SVN_NO_ERROR; 2092} 2093 2094/* Open the representation for a node-revision in transaction TXN_ID 2095 in filesystem FS and store the newly opened file in FILE. Seek to 2096 location OFFSET before returning. Perform temporary allocations in 2097 POOL. Only appropriate for file contents, nor props or directory 2098 contents. */ 2099static svn_error_t * 2100open_and_seek_transaction(apr_file_t **file, 2101 svn_fs_t *fs, 2102 const char *txn_id, 2103 representation_t *rep, 2104 apr_pool_t *pool) 2105{ 2106 apr_file_t *rev_file; 2107 apr_off_t offset; 2108 2109 SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool), 2110 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2111 2112 offset = rep->offset; 2113 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2114 2115 *file = rev_file; 2116 2117 return SVN_NO_ERROR; 2118} 2119 2120/* Given a node-id ID, and a representation REP in filesystem FS, open 2121 the correct file and seek to the correction location. Store this 2122 file in *FILE_P. Perform any allocations in POOL. */ 2123static svn_error_t * 2124open_and_seek_representation(apr_file_t **file_p, 2125 svn_fs_t *fs, 2126 representation_t *rep, 2127 apr_pool_t *pool) 2128{ 2129 if (! rep->txn_id) 2130 return open_and_seek_revision(file_p, fs, rep->revision, rep->offset, 2131 pool); 2132 else 2133 return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool); 2134} 2135 2136/* Parse the description of a representation from STRING and store it 2137 into *REP_P. If the representation is mutable (the revision is 2138 given as -1), then use TXN_ID for the representation's txn_id 2139 field. If MUTABLE_REP_TRUNCATED is true, then this representation 2140 is for property or directory contents, and no information will be 2141 expected except the "-1" revision number for a mutable 2142 representation. Allocate *REP_P in POOL. */ 2143static svn_error_t * 2144read_rep_offsets_body(representation_t **rep_p, 2145 char *string, 2146 const char *txn_id, 2147 svn_boolean_t mutable_rep_truncated, 2148 apr_pool_t *pool) 2149{ 2150 representation_t *rep; 2151 char *str; 2152 apr_int64_t val; 2153 2154 rep = apr_pcalloc(pool, sizeof(*rep)); 2155 *rep_p = rep; 2156 2157 str = svn_cstring_tokenize(" ", &string); 2158 if (str == NULL) 2159 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2160 _("Malformed text representation offset line in node-rev")); 2161 2162 2163 rep->revision = SVN_STR_TO_REV(str); 2164 if (rep->revision == SVN_INVALID_REVNUM) 2165 { 2166 rep->txn_id = txn_id; 2167 if (mutable_rep_truncated) 2168 return SVN_NO_ERROR; 2169 } 2170 2171 str = svn_cstring_tokenize(" ", &string); 2172 if (str == NULL) 2173 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2174 _("Malformed text representation offset line in node-rev")); 2175 2176 SVN_ERR(svn_cstring_atoi64(&val, str)); 2177 rep->offset = (apr_off_t)val; 2178 2179 str = svn_cstring_tokenize(" ", &string); 2180 if (str == NULL) 2181 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2182 _("Malformed text representation offset line in node-rev")); 2183 2184 SVN_ERR(svn_cstring_atoi64(&val, str)); 2185 rep->size = (svn_filesize_t)val; 2186 2187 str = svn_cstring_tokenize(" ", &string); 2188 if (str == NULL) 2189 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2190 _("Malformed text representation offset line in node-rev")); 2191 2192 SVN_ERR(svn_cstring_atoi64(&val, str)); 2193 rep->expanded_size = (svn_filesize_t)val; 2194 2195 /* Read in the MD5 hash. */ 2196 str = svn_cstring_tokenize(" ", &string); 2197 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2))) 2198 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2199 _("Malformed text representation offset line in node-rev")); 2200 2201 SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str, 2202 pool)); 2203 2204 /* The remaining fields are only used for formats >= 4, so check that. */ 2205 str = svn_cstring_tokenize(" ", &string); 2206 if (str == NULL) 2207 return SVN_NO_ERROR; 2208 2209 /* Read the SHA1 hash. */ 2210 if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2)) 2211 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2212 _("Malformed text representation offset line in node-rev")); 2213 2214 SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str, 2215 pool)); 2216 2217 /* Read the uniquifier. */ 2218 str = svn_cstring_tokenize(" ", &string); 2219 if (str == NULL) 2220 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2221 _("Malformed text representation offset line in node-rev")); 2222 2223 rep->uniquifier = apr_pstrdup(pool, str); 2224 2225 return SVN_NO_ERROR; 2226} 2227 2228/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID, 2229 and adding an error message. */ 2230static svn_error_t * 2231read_rep_offsets(representation_t **rep_p, 2232 char *string, 2233 const svn_fs_id_t *noderev_id, 2234 svn_boolean_t mutable_rep_truncated, 2235 apr_pool_t *pool) 2236{ 2237 svn_error_t *err; 2238 const char *txn_id; 2239 2240 if (noderev_id) 2241 txn_id = svn_fs_fs__id_txn_id(noderev_id); 2242 else 2243 txn_id = NULL; 2244 2245 err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated, 2246 pool); 2247 if (err) 2248 { 2249 const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool); 2250 const char *where; 2251 where = apr_psprintf(pool, 2252 _("While reading representation offsets " 2253 "for node-revision '%s':"), 2254 noderev_id ? id_unparsed->data : "(null)"); 2255 2256 return svn_error_quick_wrap(err, where); 2257 } 2258 else 2259 return SVN_NO_ERROR; 2260} 2261 2262static svn_error_t * 2263err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id) 2264{ 2265 svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool); 2266 return svn_error_createf 2267 (SVN_ERR_FS_ID_NOT_FOUND, 0, 2268 _("Reference to non-existent node '%s' in filesystem '%s'"), 2269 id_str->data, fs->path); 2270} 2271 2272/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev 2273 * caching has been enabled and the data can be found, IS_CACHED will 2274 * be set to TRUE. The noderev will be allocated from POOL. 2275 * 2276 * Non-permanent ids (e.g. ids within a TXN) will not be cached. 2277 */ 2278static svn_error_t * 2279get_cached_node_revision_body(node_revision_t **noderev_p, 2280 svn_fs_t *fs, 2281 const svn_fs_id_t *id, 2282 svn_boolean_t *is_cached, 2283 apr_pool_t *pool) 2284{ 2285 fs_fs_data_t *ffd = fs->fsap_data; 2286 if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id)) 2287 { 2288 *is_cached = FALSE; 2289 } 2290 else 2291 { 2292 pair_cache_key_t key = { 0 }; 2293 2294 key.revision = svn_fs_fs__id_rev(id); 2295 key.second = svn_fs_fs__id_offset(id); 2296 SVN_ERR(svn_cache__get((void **) noderev_p, 2297 is_cached, 2298 ffd->node_revision_cache, 2299 &key, 2300 pool)); 2301 } 2302 2303 return SVN_NO_ERROR; 2304} 2305 2306/* If noderev caching has been enabled, store the NODEREV_P for the given ID 2307 * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations. 2308 * 2309 * Non-permanent ids (e.g. ids within a TXN) will not be cached. 2310 */ 2311static svn_error_t * 2312set_cached_node_revision_body(node_revision_t *noderev_p, 2313 svn_fs_t *fs, 2314 const svn_fs_id_t *id, 2315 apr_pool_t *scratch_pool) 2316{ 2317 fs_fs_data_t *ffd = fs->fsap_data; 2318 2319 if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id)) 2320 { 2321 pair_cache_key_t key = { 0 }; 2322 2323 key.revision = svn_fs_fs__id_rev(id); 2324 key.second = svn_fs_fs__id_offset(id); 2325 return svn_cache__set(ffd->node_revision_cache, 2326 &key, 2327 noderev_p, 2328 scratch_pool); 2329 } 2330 2331 return SVN_NO_ERROR; 2332} 2333 2334/* Get the node-revision for the node ID in FS. 2335 Set *NODEREV_P to the new node-revision structure, allocated in POOL. 2336 See svn_fs_fs__get_node_revision, which wraps this and adds another 2337 error. */ 2338static svn_error_t * 2339get_node_revision_body(node_revision_t **noderev_p, 2340 svn_fs_t *fs, 2341 const svn_fs_id_t *id, 2342 apr_pool_t *pool) 2343{ 2344 apr_file_t *revision_file; 2345 svn_error_t *err; 2346 svn_boolean_t is_cached = FALSE; 2347 2348 /* First, try a cache lookup. If that succeeds, we are done here. */ 2349 SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool)); 2350 if (is_cached) 2351 return SVN_NO_ERROR; 2352 2353 if (svn_fs_fs__id_txn_id(id)) 2354 { 2355 /* This is a transaction node-rev. */ 2356 err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool), 2357 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool); 2358 } 2359 else 2360 { 2361 /* This is a revision node-rev. */ 2362 err = open_and_seek_revision(&revision_file, fs, 2363 svn_fs_fs__id_rev(id), 2364 svn_fs_fs__id_offset(id), 2365 pool); 2366 } 2367 2368 if (err) 2369 { 2370 if (APR_STATUS_IS_ENOENT(err->apr_err)) 2371 { 2372 svn_error_clear(err); 2373 return svn_error_trace(err_dangling_id(fs, id)); 2374 } 2375 2376 return svn_error_trace(err); 2377 } 2378 2379 SVN_ERR(svn_fs_fs__read_noderev(noderev_p, 2380 svn_stream_from_aprfile2(revision_file, FALSE, 2381 pool), 2382 pool)); 2383 2384 /* The noderev is not in cache, yet. Add it, if caching has been enabled. */ 2385 return set_cached_node_revision_body(*noderev_p, fs, id, pool); 2386} 2387 2388svn_error_t * 2389svn_fs_fs__read_noderev(node_revision_t **noderev_p, 2390 svn_stream_t *stream, 2391 apr_pool_t *pool) 2392{ 2393 apr_hash_t *headers; 2394 node_revision_t *noderev; 2395 char *value; 2396 const char *noderev_id; 2397 2398 SVN_ERR(read_header_block(&headers, stream, pool)); 2399 2400 noderev = apr_pcalloc(pool, sizeof(*noderev)); 2401 2402 /* Read the node-rev id. */ 2403 value = svn_hash_gets(headers, HEADER_ID); 2404 if (value == NULL) 2405 /* ### More information: filename/offset coordinates */ 2406 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2407 _("Missing id field in node-rev")); 2408 2409 SVN_ERR(svn_stream_close(stream)); 2410 2411 noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool); 2412 noderev_id = value; /* for error messages later */ 2413 2414 /* Read the type. */ 2415 value = svn_hash_gets(headers, HEADER_TYPE); 2416 2417 if ((value == NULL) || 2418 (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR))) 2419 /* ### s/kind/type/ */ 2420 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2421 _("Missing kind field in node-rev '%s'"), 2422 noderev_id); 2423 2424 noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file 2425 : svn_node_dir; 2426 2427 /* Read the 'count' field. */ 2428 value = svn_hash_gets(headers, HEADER_COUNT); 2429 if (value) 2430 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value)); 2431 else 2432 noderev->predecessor_count = 0; 2433 2434 /* Get the properties location. */ 2435 value = svn_hash_gets(headers, HEADER_PROPS); 2436 if (value) 2437 { 2438 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value, 2439 noderev->id, TRUE, pool)); 2440 } 2441 2442 /* Get the data location. */ 2443 value = svn_hash_gets(headers, HEADER_TEXT); 2444 if (value) 2445 { 2446 SVN_ERR(read_rep_offsets(&noderev->data_rep, value, 2447 noderev->id, 2448 (noderev->kind == svn_node_dir), pool)); 2449 } 2450 2451 /* Get the created path. */ 2452 value = svn_hash_gets(headers, HEADER_CPATH); 2453 if (value == NULL) 2454 { 2455 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2456 _("Missing cpath field in node-rev '%s'"), 2457 noderev_id); 2458 } 2459 else 2460 { 2461 noderev->created_path = apr_pstrdup(pool, value); 2462 } 2463 2464 /* Get the predecessor ID. */ 2465 value = svn_hash_gets(headers, HEADER_PRED); 2466 if (value) 2467 noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value), 2468 pool); 2469 2470 /* Get the copyroot. */ 2471 value = svn_hash_gets(headers, HEADER_COPYROOT); 2472 if (value == NULL) 2473 { 2474 noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path); 2475 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id); 2476 } 2477 else 2478 { 2479 char *str; 2480 2481 str = svn_cstring_tokenize(" ", &value); 2482 if (str == NULL) 2483 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2484 _("Malformed copyroot line in node-rev '%s'"), 2485 noderev_id); 2486 2487 noderev->copyroot_rev = SVN_STR_TO_REV(str); 2488 2489 if (*value == '\0') 2490 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2491 _("Malformed copyroot line in node-rev '%s'"), 2492 noderev_id); 2493 noderev->copyroot_path = apr_pstrdup(pool, value); 2494 } 2495 2496 /* Get the copyfrom. */ 2497 value = svn_hash_gets(headers, HEADER_COPYFROM); 2498 if (value == NULL) 2499 { 2500 noderev->copyfrom_path = NULL; 2501 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 2502 } 2503 else 2504 { 2505 char *str = svn_cstring_tokenize(" ", &value); 2506 if (str == NULL) 2507 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2508 _("Malformed copyfrom line in node-rev '%s'"), 2509 noderev_id); 2510 2511 noderev->copyfrom_rev = SVN_STR_TO_REV(str); 2512 2513 if (*value == 0) 2514 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2515 _("Malformed copyfrom line in node-rev '%s'"), 2516 noderev_id); 2517 noderev->copyfrom_path = apr_pstrdup(pool, value); 2518 } 2519 2520 /* Get whether this is a fresh txn root. */ 2521 value = svn_hash_gets(headers, HEADER_FRESHTXNRT); 2522 noderev->is_fresh_txn_root = (value != NULL); 2523 2524 /* Get the mergeinfo count. */ 2525 value = svn_hash_gets(headers, HEADER_MINFO_CNT); 2526 if (value) 2527 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value)); 2528 else 2529 noderev->mergeinfo_count = 0; 2530 2531 /* Get whether *this* node has mergeinfo. */ 2532 value = svn_hash_gets(headers, HEADER_MINFO_HERE); 2533 noderev->has_mergeinfo = (value != NULL); 2534 2535 *noderev_p = noderev; 2536 2537 return SVN_NO_ERROR; 2538} 2539 2540svn_error_t * 2541svn_fs_fs__get_node_revision(node_revision_t **noderev_p, 2542 svn_fs_t *fs, 2543 const svn_fs_id_t *id, 2544 apr_pool_t *pool) 2545{ 2546 svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool); 2547 if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 2548 { 2549 svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool); 2550 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 2551 "Corrupt node-revision '%s'", 2552 id_string->data); 2553 } 2554 return svn_error_trace(err); 2555} 2556 2557 2558/* Return a formatted string, compatible with filesystem format FORMAT, 2559 that represents the location of representation REP. If 2560 MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents, 2561 and only a "-1" revision number will be given for a mutable rep. 2562 If MAY_BE_CORRUPT is true, guard for NULL when constructing the string. 2563 Perform the allocation from POOL. */ 2564static const char * 2565representation_string(representation_t *rep, 2566 int format, 2567 svn_boolean_t mutable_rep_truncated, 2568 svn_boolean_t may_be_corrupt, 2569 apr_pool_t *pool) 2570{ 2571 if (rep->txn_id && mutable_rep_truncated) 2572 return "-1"; 2573 2574#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum) \ 2575 ((!may_be_corrupt || (checksum) != NULL) \ 2576 ? svn_checksum_to_cstring_display((checksum), pool) \ 2577 : "(null)") 2578 2579 if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL) 2580 return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT 2581 " %" SVN_FILESIZE_T_FMT " %s", 2582 rep->revision, rep->offset, rep->size, 2583 rep->expanded_size, 2584 DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum)); 2585 2586 return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT 2587 " %" SVN_FILESIZE_T_FMT " %s %s %s", 2588 rep->revision, rep->offset, rep->size, 2589 rep->expanded_size, 2590 DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum), 2591 DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum), 2592 rep->uniquifier); 2593 2594#undef DISPLAY_MAYBE_NULL_CHECKSUM 2595 2596} 2597 2598 2599svn_error_t * 2600svn_fs_fs__write_noderev(svn_stream_t *outfile, 2601 node_revision_t *noderev, 2602 int format, 2603 svn_boolean_t include_mergeinfo, 2604 apr_pool_t *pool) 2605{ 2606 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n", 2607 svn_fs_fs__id_unparse(noderev->id, 2608 pool)->data)); 2609 2610 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n", 2611 (noderev->kind == svn_node_file) ? 2612 KIND_FILE : KIND_DIR)); 2613 2614 if (noderev->predecessor_id) 2615 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n", 2616 svn_fs_fs__id_unparse(noderev->predecessor_id, 2617 pool)->data)); 2618 2619 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n", 2620 noderev->predecessor_count)); 2621 2622 if (noderev->data_rep) 2623 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n", 2624 representation_string(noderev->data_rep, 2625 format, 2626 (noderev->kind 2627 == svn_node_dir), 2628 FALSE, 2629 pool))); 2630 2631 if (noderev->prop_rep) 2632 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n", 2633 representation_string(noderev->prop_rep, format, 2634 TRUE, FALSE, pool))); 2635 2636 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n", 2637 noderev->created_path)); 2638 2639 if (noderev->copyfrom_path) 2640 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld" 2641 " %s\n", 2642 noderev->copyfrom_rev, 2643 noderev->copyfrom_path)); 2644 2645 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) || 2646 (strcmp(noderev->copyroot_path, noderev->created_path) != 0)) 2647 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld" 2648 " %s\n", 2649 noderev->copyroot_rev, 2650 noderev->copyroot_path)); 2651 2652 if (noderev->is_fresh_txn_root) 2653 SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n")); 2654 2655 if (include_mergeinfo) 2656 { 2657 if (noderev->mergeinfo_count > 0) 2658 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %" 2659 APR_INT64_T_FMT "\n", 2660 noderev->mergeinfo_count)); 2661 2662 if (noderev->has_mergeinfo) 2663 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n")); 2664 } 2665 2666 return svn_stream_puts(outfile, "\n"); 2667} 2668 2669svn_error_t * 2670svn_fs_fs__put_node_revision(svn_fs_t *fs, 2671 const svn_fs_id_t *id, 2672 node_revision_t *noderev, 2673 svn_boolean_t fresh_txn_root, 2674 apr_pool_t *pool) 2675{ 2676 fs_fs_data_t *ffd = fs->fsap_data; 2677 apr_file_t *noderev_file; 2678 const char *txn_id = svn_fs_fs__id_txn_id(id); 2679 2680 noderev->is_fresh_txn_root = fresh_txn_root; 2681 2682 if (! txn_id) 2683 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2684 _("Attempted to write to non-transaction '%s'"), 2685 svn_fs_fs__id_unparse(id, pool)->data); 2686 2687 SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool), 2688 APR_WRITE | APR_CREATE | APR_TRUNCATE 2689 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2690 2691 SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE, 2692 pool), 2693 noderev, ffd->format, 2694 svn_fs_fs__fs_supports_mergeinfo(fs), 2695 pool)); 2696 2697 SVN_ERR(svn_io_file_close(noderev_file, pool)); 2698 2699 return SVN_NO_ERROR; 2700} 2701 2702/* For the in-transaction NODEREV within FS, write the sha1->rep mapping 2703 * file in the respective transaction, if rep sharing has been enabled etc. 2704 * Use POOL for temporary allocations. 2705 */ 2706static svn_error_t * 2707store_sha1_rep_mapping(svn_fs_t *fs, 2708 node_revision_t *noderev, 2709 apr_pool_t *pool) 2710{ 2711 fs_fs_data_t *ffd = fs->fsap_data; 2712 2713 /* if rep sharing has been enabled and the noderev has a data rep and 2714 * its SHA-1 is known, store the rep struct under its SHA1. */ 2715 if ( ffd->rep_sharing_allowed 2716 && noderev->data_rep 2717 && noderev->data_rep->sha1_checksum) 2718 { 2719 apr_file_t *rep_file; 2720 const char *file_name = path_txn_sha1(fs, 2721 svn_fs_fs__id_txn_id(noderev->id), 2722 noderev->data_rep->sha1_checksum, 2723 pool); 2724 const char *rep_string = representation_string(noderev->data_rep, 2725 ffd->format, 2726 (noderev->kind 2727 == svn_node_dir), 2728 FALSE, 2729 pool); 2730 SVN_ERR(svn_io_file_open(&rep_file, file_name, 2731 APR_WRITE | APR_CREATE | APR_TRUNCATE 2732 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2733 2734 SVN_ERR(svn_io_file_write_full(rep_file, rep_string, 2735 strlen(rep_string), NULL, pool)); 2736 2737 SVN_ERR(svn_io_file_close(rep_file, pool)); 2738 } 2739 2740 return SVN_NO_ERROR; 2741} 2742 2743 2744/* This structure is used to hold the information associated with a 2745 REP line. */ 2746struct rep_args 2747{ 2748 svn_boolean_t is_delta; 2749 svn_boolean_t is_delta_vs_empty; 2750 2751 svn_revnum_t base_revision; 2752 apr_off_t base_offset; 2753 svn_filesize_t base_length; 2754}; 2755 2756/* Read the next line from file FILE and parse it as a text 2757 representation entry. Return the parsed entry in *REP_ARGS_P. 2758 Perform all allocations in POOL. */ 2759static svn_error_t * 2760read_rep_line(struct rep_args **rep_args_p, 2761 apr_file_t *file, 2762 apr_pool_t *pool) 2763{ 2764 char buffer[160]; 2765 apr_size_t limit; 2766 struct rep_args *rep_args; 2767 char *str, *last_str = buffer; 2768 apr_int64_t val; 2769 2770 limit = sizeof(buffer); 2771 SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool)); 2772 2773 rep_args = apr_pcalloc(pool, sizeof(*rep_args)); 2774 rep_args->is_delta = FALSE; 2775 2776 if (strcmp(buffer, REP_PLAIN) == 0) 2777 { 2778 *rep_args_p = rep_args; 2779 return SVN_NO_ERROR; 2780 } 2781 2782 if (strcmp(buffer, REP_DELTA) == 0) 2783 { 2784 /* This is a delta against the empty stream. */ 2785 rep_args->is_delta = TRUE; 2786 rep_args->is_delta_vs_empty = TRUE; 2787 *rep_args_p = rep_args; 2788 return SVN_NO_ERROR; 2789 } 2790 2791 rep_args->is_delta = TRUE; 2792 rep_args->is_delta_vs_empty = FALSE; 2793 2794 /* We have hopefully a DELTA vs. a non-empty base revision. */ 2795 str = svn_cstring_tokenize(" ", &last_str); 2796 if (! str || (strcmp(str, REP_DELTA) != 0)) 2797 goto error; 2798 2799 str = svn_cstring_tokenize(" ", &last_str); 2800 if (! str) 2801 goto error; 2802 rep_args->base_revision = SVN_STR_TO_REV(str); 2803 2804 str = svn_cstring_tokenize(" ", &last_str); 2805 if (! str) 2806 goto error; 2807 SVN_ERR(svn_cstring_atoi64(&val, str)); 2808 rep_args->base_offset = (apr_off_t)val; 2809 2810 str = svn_cstring_tokenize(" ", &last_str); 2811 if (! str) 2812 goto error; 2813 SVN_ERR(svn_cstring_atoi64(&val, str)); 2814 rep_args->base_length = (svn_filesize_t)val; 2815 2816 *rep_args_p = rep_args; 2817 return SVN_NO_ERROR; 2818 2819 error: 2820 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2821 _("Malformed representation header at %s"), 2822 path_and_offset_of(file, pool)); 2823} 2824 2825/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID 2826 of the header located at OFFSET and store it in *ID_P. Allocate 2827 temporary variables from POOL. */ 2828static svn_error_t * 2829get_fs_id_at_offset(svn_fs_id_t **id_p, 2830 apr_file_t *rev_file, 2831 svn_fs_t *fs, 2832 svn_revnum_t rev, 2833 apr_off_t offset, 2834 apr_pool_t *pool) 2835{ 2836 svn_fs_id_t *id; 2837 apr_hash_t *headers; 2838 const char *node_id_str; 2839 2840 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2841 2842 SVN_ERR(read_header_block(&headers, 2843 svn_stream_from_aprfile2(rev_file, TRUE, pool), 2844 pool)); 2845 2846 /* In error messages, the offset is relative to the pack file, 2847 not to the rev file. */ 2848 2849 node_id_str = svn_hash_gets(headers, HEADER_ID); 2850 2851 if (node_id_str == NULL) 2852 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2853 _("Missing node-id in node-rev at r%ld " 2854 "(offset %s)"), 2855 rev, 2856 apr_psprintf(pool, "%" APR_OFF_T_FMT, offset)); 2857 2858 id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool); 2859 2860 if (id == NULL) 2861 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2862 _("Corrupt node-id '%s' in node-rev at r%ld " 2863 "(offset %s)"), 2864 node_id_str, rev, 2865 apr_psprintf(pool, "%" APR_OFF_T_FMT, offset)); 2866 2867 *id_p = id; 2868 2869 /* ### assert that the txn_id is REV/OFFSET ? */ 2870 2871 return SVN_NO_ERROR; 2872} 2873 2874 2875/* Given an open revision file REV_FILE in FS for REV, locate the trailer that 2876 specifies the offset to the root node-id and to the changed path 2877 information. Store the root node offset in *ROOT_OFFSET and the 2878 changed path offset in *CHANGES_OFFSET. If either of these 2879 pointers is NULL, do nothing with it. 2880 2881 If PACKED is true, REV_FILE should be a packed shard file. 2882 ### There is currently no such parameter. This function assumes that 2883 is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed 2884 file. Therefore FS->fsap_data->min_unpacked_rev must not have been 2885 refreshed since REV_FILE was opened if there is a possibility that 2886 revision REV may have become packed since then. 2887 TODO: Take an IS_PACKED parameter instead, in order to remove this 2888 requirement. 2889 2890 Allocate temporary variables from POOL. */ 2891static svn_error_t * 2892get_root_changes_offset(apr_off_t *root_offset, 2893 apr_off_t *changes_offset, 2894 apr_file_t *rev_file, 2895 svn_fs_t *fs, 2896 svn_revnum_t rev, 2897 apr_pool_t *pool) 2898{ 2899 fs_fs_data_t *ffd = fs->fsap_data; 2900 apr_off_t offset; 2901 apr_off_t rev_offset; 2902 char buf[64]; 2903 int i, num_bytes; 2904 const char *str; 2905 apr_size_t len; 2906 apr_seek_where_t seek_relative; 2907 2908 /* Determine where to seek to in the file. 2909 2910 If we've got a pack file, we want to seek to the end of the desired 2911 revision. But we don't track that, so we seek to the beginning of the 2912 next revision. 2913 2914 Unless the next revision is in a different file, in which case, we can 2915 just seek to the end of the pack file -- just like we do in the 2916 non-packed case. */ 2917 if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0)) 2918 { 2919 SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool)); 2920 seek_relative = APR_SET; 2921 } 2922 else 2923 { 2924 seek_relative = APR_END; 2925 offset = 0; 2926 } 2927 2928 /* Offset of the revision from the start of the pack file, if applicable. */ 2929 if (is_packed_rev(fs, rev)) 2930 SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool)); 2931 else 2932 rev_offset = 0; 2933 2934 /* We will assume that the last line containing the two offsets 2935 will never be longer than 64 characters. */ 2936 SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool)); 2937 2938 offset -= sizeof(buf); 2939 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2940 2941 /* Read in this last block, from which we will identify the last line. */ 2942 len = sizeof(buf); 2943 SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool)); 2944 2945 /* This cast should be safe since the maximum amount read, 64, will 2946 never be bigger than the size of an int. */ 2947 num_bytes = (int) len; 2948 2949 /* The last byte should be a newline. */ 2950 if (buf[num_bytes - 1] != '\n') 2951 { 2952 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2953 _("Revision file (r%ld) lacks trailing newline"), 2954 rev); 2955 } 2956 2957 /* Look for the next previous newline. */ 2958 for (i = num_bytes - 2; i >= 0; i--) 2959 { 2960 if (buf[i] == '\n') 2961 break; 2962 } 2963 2964 if (i < 0) 2965 { 2966 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2967 _("Final line in revision file (r%ld) longer " 2968 "than 64 characters"), 2969 rev); 2970 } 2971 2972 i++; 2973 str = &buf[i]; 2974 2975 /* find the next space */ 2976 for ( ; i < (num_bytes - 2) ; i++) 2977 if (buf[i] == ' ') 2978 break; 2979 2980 if (i == (num_bytes - 2)) 2981 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2982 _("Final line in revision file r%ld missing space"), 2983 rev); 2984 2985 if (root_offset) 2986 { 2987 apr_int64_t val; 2988 2989 buf[i] = '\0'; 2990 SVN_ERR(svn_cstring_atoi64(&val, str)); 2991 *root_offset = rev_offset + (apr_off_t)val; 2992 } 2993 2994 i++; 2995 str = &buf[i]; 2996 2997 /* find the next newline */ 2998 for ( ; i < num_bytes; i++) 2999 if (buf[i] == '\n') 3000 break; 3001 3002 if (changes_offset) 3003 { 3004 apr_int64_t val; 3005 3006 buf[i] = '\0'; 3007 SVN_ERR(svn_cstring_atoi64(&val, str)); 3008 *changes_offset = rev_offset + (apr_off_t)val; 3009 } 3010 3011 return SVN_NO_ERROR; 3012} 3013 3014/* Move a file into place from OLD_FILENAME in the transactions 3015 directory to its final location NEW_FILENAME in the repository. On 3016 Unix, match the permissions of the new file to the permissions of 3017 PERMS_REFERENCE. Temporary allocations are from POOL. 3018 3019 This function almost duplicates svn_io_file_move(), but it tries to 3020 guarantee a flush. */ 3021static svn_error_t * 3022move_into_place(const char *old_filename, 3023 const char *new_filename, 3024 const char *perms_reference, 3025 apr_pool_t *pool) 3026{ 3027 svn_error_t *err; 3028 3029 SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool)); 3030 3031 /* Move the file into place. */ 3032 err = svn_io_file_rename(old_filename, new_filename, pool); 3033 if (err && APR_STATUS_IS_EXDEV(err->apr_err)) 3034 { 3035 apr_file_t *file; 3036 3037 /* Can't rename across devices; fall back to copying. */ 3038 svn_error_clear(err); 3039 err = SVN_NO_ERROR; 3040 SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool)); 3041 3042 /* Flush the target of the copy to disk. */ 3043 SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ, 3044 APR_OS_DEFAULT, pool)); 3045 /* ### BH: Does this really guarantee a flush of the data written 3046 ### via a completely different handle on all operating systems? 3047 ### 3048 ### Maybe we should perform the copy ourselves instead of making 3049 ### apr do that and flush the real handle? */ 3050 SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 3051 SVN_ERR(svn_io_file_close(file, pool)); 3052 } 3053 if (err) 3054 return svn_error_trace(err); 3055 3056#ifdef __linux__ 3057 { 3058 /* Linux has the unusual feature that fsync() on a file is not 3059 enough to ensure that a file's directory entries have been 3060 flushed to disk; you have to fsync the directory as well. 3061 On other operating systems, we'd only be asking for trouble 3062 by trying to open and fsync a directory. */ 3063 const char *dirname; 3064 apr_file_t *file; 3065 3066 dirname = svn_dirent_dirname(new_filename, pool); 3067 SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, 3068 pool)); 3069 SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 3070 SVN_ERR(svn_io_file_close(file, pool)); 3071 } 3072#endif 3073 3074 return SVN_NO_ERROR; 3075} 3076 3077svn_error_t * 3078svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p, 3079 svn_fs_t *fs, 3080 svn_revnum_t rev, 3081 apr_pool_t *pool) 3082{ 3083 fs_fs_data_t *ffd = fs->fsap_data; 3084 apr_file_t *revision_file; 3085 apr_off_t root_offset; 3086 svn_fs_id_t *root_id = NULL; 3087 svn_boolean_t is_cached; 3088 3089 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 3090 3091 SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached, 3092 ffd->rev_root_id_cache, &rev, pool)); 3093 if (is_cached) 3094 return SVN_NO_ERROR; 3095 3096 SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool)); 3097 SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev, 3098 pool)); 3099 3100 SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev, 3101 root_offset, pool)); 3102 3103 SVN_ERR(svn_io_file_close(revision_file, pool)); 3104 3105 SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool)); 3106 3107 *root_id_p = root_id; 3108 3109 return SVN_NO_ERROR; 3110} 3111 3112/* Revprop caching management. 3113 * 3114 * Mechanism: 3115 * ---------- 3116 * 3117 * Revprop caching needs to be activated and will be deactivated for the 3118 * respective FS instance if the necessary infrastructure could not be 3119 * initialized. In deactivated mode, there is almost no runtime overhead 3120 * associated with revprop caching. As long as no revprops are being read 3121 * or changed, revprop caching imposes no overhead. 3122 * 3123 * When activated, we cache revprops using (revision, generation) pairs 3124 * as keys with the generation being incremented upon every revprop change. 3125 * Since the cache is process-local, the generation needs to be tracked 3126 * for at least as long as the process lives but may be reset afterwards. 3127 * 3128 * To track the revprop generation, we use two-layer approach. On the lower 3129 * level, we use named atomics to have a system-wide consistent value for 3130 * the current revprop generation. However, those named atomics will only 3131 * remain valid for as long as at least one process / thread in the system 3132 * accesses revprops in the respective repository. The underlying shared 3133 * memory gets cleaned up afterwards. 3134 * 3135 * On the second level, we will use a persistent file to track the latest 3136 * revprop generation. It will be written upon each revprop change but 3137 * only be read if we are the first process to initialize the named atomics 3138 * with that value. 3139 * 3140 * The overhead for the second and following accesses to revprops is 3141 * almost zero on most systems. 3142 * 3143 * 3144 * Tech aspects: 3145 * ------------- 3146 * 3147 * A problem is that we need to provide a globally available file name to 3148 * back the SHM implementation on OSes that need it. We can only assume 3149 * write access to some file within the respective repositories. Because 3150 * a given server process may access thousands of repositories during its 3151 * lifetime, keeping the SHM data alive for all of them is also not an 3152 * option. 3153 * 3154 * So, we store the new revprop generation on disk as part of each 3155 * setrevprop call, i.e. this write will be serialized and the write order 3156 * be guaranteed by the repository write lock. 3157 * 3158 * The only racy situation occurs when the data is being read again by two 3159 * processes concurrently but in that situation, the first process to 3160 * finish that procedure is guaranteed to be the only one that initializes 3161 * the SHM data. Since even writers will first go through that 3162 * initialization phase, they will never operate on stale data. 3163 */ 3164 3165/* Read revprop generation as stored on disk for repository FS. The result 3166 * is returned in *CURRENT. Default to 2 if no such file is available. 3167 */ 3168static svn_error_t * 3169read_revprop_generation_file(apr_int64_t *current, 3170 svn_fs_t *fs, 3171 apr_pool_t *pool) 3172{ 3173 svn_error_t *err; 3174 apr_file_t *file; 3175 char buf[80]; 3176 apr_size_t len; 3177 const char *path = path_revprop_generation(fs, pool); 3178 3179 err = svn_io_file_open(&file, path, 3180 APR_READ | APR_BUFFERED, 3181 APR_OS_DEFAULT, pool); 3182 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 3183 { 3184 svn_error_clear(err); 3185 *current = 2; 3186 3187 return SVN_NO_ERROR; 3188 } 3189 SVN_ERR(err); 3190 3191 len = sizeof(buf); 3192 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 3193 3194 /* Check that the first line contains only digits. */ 3195 SVN_ERR(check_file_buffer_numeric(buf, 0, path, 3196 "Revprop Generation", pool)); 3197 SVN_ERR(svn_cstring_atoi64(current, buf)); 3198 3199 return svn_io_file_close(file, pool); 3200} 3201 3202/* Write the CURRENT revprop generation to disk for repository FS. 3203 */ 3204static svn_error_t * 3205write_revprop_generation_file(svn_fs_t *fs, 3206 apr_int64_t current, 3207 apr_pool_t *pool) 3208{ 3209 apr_file_t *file; 3210 const char *tmp_path; 3211 3212 char buf[SVN_INT64_BUFFER_SIZE]; 3213 apr_size_t len = svn__i64toa(buf, current); 3214 buf[len] = '\n'; 3215 3216 SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path, 3217 svn_io_file_del_none, pool, pool)); 3218 SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool)); 3219 SVN_ERR(svn_io_file_close(file, pool)); 3220 3221 return move_into_place(tmp_path, path_revprop_generation(fs, pool), 3222 tmp_path, pool); 3223} 3224 3225/* Make sure the revprop_namespace member in FS is set. */ 3226static svn_error_t * 3227ensure_revprop_namespace(svn_fs_t *fs) 3228{ 3229 fs_fs_data_t *ffd = fs->fsap_data; 3230 3231 return ffd->revprop_namespace == NULL 3232 ? svn_atomic_namespace__create(&ffd->revprop_namespace, 3233 svn_dirent_join(fs->path, 3234 ATOMIC_REVPROP_NAMESPACE, 3235 fs->pool), 3236 fs->pool) 3237 : SVN_NO_ERROR; 3238} 3239 3240/* Make sure the revprop_namespace member in FS is set. */ 3241static svn_error_t * 3242cleanup_revprop_namespace(svn_fs_t *fs) 3243{ 3244 const char *name = svn_dirent_join(fs->path, 3245 ATOMIC_REVPROP_NAMESPACE, 3246 fs->pool); 3247 return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool)); 3248} 3249 3250/* Make sure the revprop_generation member in FS is set and, if necessary, 3251 * initialized with the latest value stored on disk. 3252 */ 3253static svn_error_t * 3254ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool) 3255{ 3256 fs_fs_data_t *ffd = fs->fsap_data; 3257 3258 SVN_ERR(ensure_revprop_namespace(fs)); 3259 if (ffd->revprop_generation == NULL) 3260 { 3261 apr_int64_t current = 0; 3262 3263 SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation, 3264 ffd->revprop_namespace, 3265 ATOMIC_REVPROP_GENERATION, 3266 TRUE)); 3267 3268 /* If the generation is at 0, we just created a new namespace 3269 * (it would be at least 2 otherwise). Read the latest generation 3270 * from disk and if we are the first one to initialize the atomic 3271 * (i.e. is still 0), set it to the value just gotten. 3272 */ 3273 SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation)); 3274 if (current == 0) 3275 { 3276 SVN_ERR(read_revprop_generation_file(¤t, fs, pool)); 3277 SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0, 3278 ffd->revprop_generation)); 3279 } 3280 } 3281 3282 return SVN_NO_ERROR; 3283} 3284 3285/* Make sure the revprop_timeout member in FS is set. */ 3286static svn_error_t * 3287ensure_revprop_timeout(svn_fs_t *fs) 3288{ 3289 fs_fs_data_t *ffd = fs->fsap_data; 3290 3291 SVN_ERR(ensure_revprop_namespace(fs)); 3292 return ffd->revprop_timeout == NULL 3293 ? svn_named_atomic__get(&ffd->revprop_timeout, 3294 ffd->revprop_namespace, 3295 ATOMIC_REVPROP_TIMEOUT, 3296 TRUE) 3297 : SVN_NO_ERROR; 3298} 3299 3300/* Create an error object with the given MESSAGE and pass it to the 3301 WARNING member of FS. */ 3302static void 3303log_revprop_cache_init_warning(svn_fs_t *fs, 3304 svn_error_t *underlying_err, 3305 const char *message) 3306{ 3307 svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE, 3308 underlying_err, 3309 message, fs->path); 3310 3311 if (fs->warning) 3312 (fs->warning)(fs->warning_baton, err); 3313 3314 svn_error_clear(err); 3315} 3316 3317/* Test whether revprop cache and necessary infrastructure are 3318 available in FS. */ 3319static svn_boolean_t 3320has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool) 3321{ 3322 fs_fs_data_t *ffd = fs->fsap_data; 3323 svn_error_t *error; 3324 3325 /* is the cache (still) enabled? */ 3326 if (ffd->revprop_cache == NULL) 3327 return FALSE; 3328 3329 /* is it efficient? */ 3330 if (!svn_named_atomic__is_efficient()) 3331 { 3332 /* access to it would be quite slow 3333 * -> disable the revprop cache for good 3334 */ 3335 ffd->revprop_cache = NULL; 3336 log_revprop_cache_init_warning(fs, NULL, 3337 "Revprop caching for '%s' disabled" 3338 " because it would be inefficient."); 3339 3340 return FALSE; 3341 } 3342 3343 /* try to access our SHM-backed infrastructure */ 3344 error = ensure_revprop_generation(fs, pool); 3345 if (error) 3346 { 3347 /* failure -> disable revprop cache for good */ 3348 3349 ffd->revprop_cache = NULL; 3350 log_revprop_cache_init_warning(fs, error, 3351 "Revprop caching for '%s' disabled " 3352 "because SHM infrastructure for revprop " 3353 "caching failed to initialize."); 3354 3355 return FALSE; 3356 } 3357 3358 return TRUE; 3359} 3360 3361/* Baton structure for revprop_generation_fixup. */ 3362typedef struct revprop_generation_fixup_t 3363{ 3364 /* revprop generation to read */ 3365 apr_int64_t *generation; 3366 3367 /* containing the revprop_generation member to query */ 3368 fs_fs_data_t *ffd; 3369} revprop_generation_upgrade_t; 3370 3371/* If the revprop generation has an odd value, it means the original writer 3372 of the revprop got killed. We don't know whether that process as able 3373 to change the revprop data but we assume that it was. Therefore, we 3374 increase the generation in that case to basically invalidate everyones 3375 cache content. 3376 Execute this onlx while holding the write lock to the repo in baton->FFD. 3377 */ 3378static svn_error_t * 3379revprop_generation_fixup(void *void_baton, 3380 apr_pool_t *pool) 3381{ 3382 revprop_generation_upgrade_t *baton = void_baton; 3383 assert(baton->ffd->has_write_lock); 3384 3385 /* Maybe, either the original revprop writer or some other reader has 3386 already corrected / bumped the revprop generation. Thus, we need 3387 to read it again. */ 3388 SVN_ERR(svn_named_atomic__read(baton->generation, 3389 baton->ffd->revprop_generation)); 3390 3391 /* Cause everyone to re-read revprops upon their next access, if the 3392 last revprop write did not complete properly. */ 3393 while (*baton->generation % 2) 3394 SVN_ERR(svn_named_atomic__add(baton->generation, 3395 1, 3396 baton->ffd->revprop_generation)); 3397 3398 return SVN_NO_ERROR; 3399} 3400 3401/* Read the current revprop generation and return it in *GENERATION. 3402 Also, detect aborted / crashed writers and recover from that. 3403 Use the access object in FS to set the shared mem values. */ 3404static svn_error_t * 3405read_revprop_generation(apr_int64_t *generation, 3406 svn_fs_t *fs, 3407 apr_pool_t *pool) 3408{ 3409 apr_int64_t current = 0; 3410 fs_fs_data_t *ffd = fs->fsap_data; 3411 3412 /* read the current revprop generation number */ 3413 SVN_ERR(ensure_revprop_generation(fs, pool)); 3414 SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation)); 3415 3416 /* is an unfinished revprop write under the way? */ 3417 if (current % 2) 3418 { 3419 apr_int64_t timeout = 0; 3420 3421 /* read timeout for the write operation */ 3422 SVN_ERR(ensure_revprop_timeout(fs)); 3423 SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout)); 3424 3425 /* has the writer process been aborted, 3426 * i.e. has the timeout been reached? 3427 */ 3428 if (apr_time_now() > timeout) 3429 { 3430 revprop_generation_upgrade_t baton; 3431 baton.generation = ¤t; 3432 baton.ffd = ffd; 3433 3434 /* Ensure that the original writer process no longer exists by 3435 * acquiring the write lock to this repository. Then, fix up 3436 * the revprop generation. 3437 */ 3438 if (ffd->has_write_lock) 3439 SVN_ERR(revprop_generation_fixup(&baton, pool)); 3440 else 3441 SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup, 3442 &baton, pool)); 3443 } 3444 } 3445 3446 /* return the value we just got */ 3447 *generation = current; 3448 return SVN_NO_ERROR; 3449} 3450 3451/* Set the revprop generation to the next odd number to indicate that 3452 there is a revprop write process under way. If that times out, 3453 readers shall recover from that state & re-read revprops. 3454 Use the access object in FS to set the shared mem value. */ 3455static svn_error_t * 3456begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool) 3457{ 3458 apr_int64_t current; 3459 fs_fs_data_t *ffd = fs->fsap_data; 3460 3461 /* set the timeout for the write operation */ 3462 SVN_ERR(ensure_revprop_timeout(fs)); 3463 SVN_ERR(svn_named_atomic__write(NULL, 3464 apr_time_now() + REVPROP_CHANGE_TIMEOUT, 3465 ffd->revprop_timeout)); 3466 3467 /* set the revprop generation to an odd value to indicate 3468 * that a write is in progress 3469 */ 3470 SVN_ERR(ensure_revprop_generation(fs, pool)); 3471 do 3472 { 3473 SVN_ERR(svn_named_atomic__add(¤t, 3474 1, 3475 ffd->revprop_generation)); 3476 } 3477 while (current % 2 == 0); 3478 3479 return SVN_NO_ERROR; 3480} 3481 3482/* Set the revprop generation to the next even number to indicate that 3483 a) readers shall re-read revprops, and 3484 b) the write process has been completed (no recovery required) 3485 Use the access object in FS to set the shared mem value. */ 3486static svn_error_t * 3487end_revprop_change(svn_fs_t *fs, apr_pool_t *pool) 3488{ 3489 apr_int64_t current = 1; 3490 fs_fs_data_t *ffd = fs->fsap_data; 3491 3492 /* set the revprop generation to an even value to indicate 3493 * that a write has been completed 3494 */ 3495 SVN_ERR(ensure_revprop_generation(fs, pool)); 3496 do 3497 { 3498 SVN_ERR(svn_named_atomic__add(¤t, 3499 1, 3500 ffd->revprop_generation)); 3501 } 3502 while (current % 2); 3503 3504 /* Save the latest generation to disk. FS is currently in a "locked" 3505 * state such that we can be sure the be the only ones to write that 3506 * file. 3507 */ 3508 return write_revprop_generation_file(fs, current, pool); 3509} 3510 3511/* Container for all data required to access the packed revprop file 3512 * for a given REVISION. This structure will be filled incrementally 3513 * by read_pack_revprops() its sub-routines. 3514 */ 3515typedef struct packed_revprops_t 3516{ 3517 /* revision number to read (not necessarily the first in the pack) */ 3518 svn_revnum_t revision; 3519 3520 /* current revprop generation. Used when populating the revprop cache */ 3521 apr_int64_t generation; 3522 3523 /* the actual revision properties */ 3524 apr_hash_t *properties; 3525 3526 /* their size when serialized to a single string 3527 * (as found in PACKED_REVPROPS) */ 3528 apr_size_t serialized_size; 3529 3530 3531 /* name of the pack file (without folder path) */ 3532 const char *filename; 3533 3534 /* packed shard folder path */ 3535 const char *folder; 3536 3537 /* sum of values in SIZES */ 3538 apr_size_t total_size; 3539 3540 /* first revision in the pack (>= MANIFEST_START) */ 3541 svn_revnum_t start_revision; 3542 3543 /* size of the revprops in PACKED_REVPROPS */ 3544 apr_array_header_t *sizes; 3545 3546 /* offset of the revprops in PACKED_REVPROPS */ 3547 apr_array_header_t *offsets; 3548 3549 3550 /* concatenation of the serialized representation of all revprops 3551 * in the pack, i.e. the pack content without header and compression */ 3552 svn_stringbuf_t *packed_revprops; 3553 3554 /* First revision covered by MANIFEST. 3555 * Will equal the shard start revision or 1, for the 1st shard. */ 3556 svn_revnum_t manifest_start; 3557 3558 /* content of the manifest. 3559 * Maps long(rev - MANIFEST_START) to const char* pack file name */ 3560 apr_array_header_t *manifest; 3561} packed_revprops_t; 3562 3563/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES. 3564 * Also, put them into the revprop cache, if activated, for future use. 3565 * Three more parameters are being used to update the revprop cache: FS is 3566 * our file system, the revprops belong to REVISION and the global revprop 3567 * GENERATION is used as well. 3568 * 3569 * The returned hash will be allocated in POOL, SCRATCH_POOL is being used 3570 * for temporary allocations. 3571 */ 3572static svn_error_t * 3573parse_revprop(apr_hash_t **properties, 3574 svn_fs_t *fs, 3575 svn_revnum_t revision, 3576 apr_int64_t generation, 3577 svn_string_t *content, 3578 apr_pool_t *pool, 3579 apr_pool_t *scratch_pool) 3580{ 3581 svn_stream_t *stream = svn_stream_from_string(content, scratch_pool); 3582 *properties = apr_hash_make(pool); 3583 3584 SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool)); 3585 if (has_revprop_cache(fs, pool)) 3586 { 3587 fs_fs_data_t *ffd = fs->fsap_data; 3588 pair_cache_key_t key = { 0 }; 3589 3590 key.revision = revision; 3591 key.second = generation; 3592 SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties, 3593 scratch_pool)); 3594 } 3595 3596 return SVN_NO_ERROR; 3597} 3598 3599/* Read the non-packed revprops for revision REV in FS, put them into the 3600 * revprop cache if activated and return them in *PROPERTIES. GENERATION 3601 * is the current revprop generation. 3602 * 3603 * If the data could not be read due to an otherwise recoverable error, 3604 * leave *PROPERTIES unchanged. No error will be returned in that case. 3605 * 3606 * Allocations will be done in POOL. 3607 */ 3608static svn_error_t * 3609read_non_packed_revprop(apr_hash_t **properties, 3610 svn_fs_t *fs, 3611 svn_revnum_t rev, 3612 apr_int64_t generation, 3613 apr_pool_t *pool) 3614{ 3615 svn_stringbuf_t *content = NULL; 3616 apr_pool_t *iterpool = svn_pool_create(pool); 3617 svn_boolean_t missing = FALSE; 3618 int i; 3619 3620 for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i) 3621 { 3622 svn_pool_clear(iterpool); 3623 SVN_ERR(try_stringbuf_from_file(&content, 3624 &missing, 3625 path_revprops(fs, rev, iterpool), 3626 i + 1 < RECOVERABLE_RETRY_COUNT, 3627 iterpool)); 3628 } 3629 3630 if (content) 3631 SVN_ERR(parse_revprop(properties, fs, rev, generation, 3632 svn_stringbuf__morph_into_string(content), 3633 pool, iterpool)); 3634 3635 svn_pool_clear(iterpool); 3636 3637 return SVN_NO_ERROR; 3638} 3639 3640/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST 3641 * members. Use POOL for allocating results and SCRATCH_POOL for temporaries. 3642 */ 3643static svn_error_t * 3644get_revprop_packname(svn_fs_t *fs, 3645 packed_revprops_t *revprops, 3646 apr_pool_t *pool, 3647 apr_pool_t *scratch_pool) 3648{ 3649 fs_fs_data_t *ffd = fs->fsap_data; 3650 svn_stringbuf_t *content = NULL; 3651 const char *manifest_file_path; 3652 int idx; 3653 3654 /* read content of the manifest file */ 3655 revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool); 3656 manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); 3657 3658 SVN_ERR(read_content(&content, manifest_file_path, pool)); 3659 3660 /* parse the manifest. Every line is a file name */ 3661 revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir, 3662 sizeof(const char*)); 3663 3664 /* Read all lines. Since the last line ends with a newline, we will 3665 end up with a valid but empty string after the last entry. */ 3666 while (content->data && *content->data) 3667 { 3668 APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data; 3669 content->data = strchr(content->data, '\n'); 3670 if (content->data) 3671 { 3672 *content->data = 0; 3673 content->data++; 3674 } 3675 } 3676 3677 /* Index for our revision. Rev 0 is excluded from the first shard. */ 3678 revprops->manifest_start = revprops->revision 3679 - (revprops->revision % ffd->max_files_per_dir); 3680 if (revprops->manifest_start == 0) 3681 ++revprops->manifest_start; 3682 idx = (int)(revprops->revision - revprops->manifest_start); 3683 3684 if (revprops->manifest->nelts <= idx) 3685 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3686 _("Packed revprop manifest for r%ld too " 3687 "small"), revprops->revision); 3688 3689 /* Now get the file name */ 3690 revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*); 3691 3692 return SVN_NO_ERROR; 3693} 3694 3695/* Return TRUE, if revision R1 and R2 refer to the same shard in FS. 3696 */ 3697static svn_boolean_t 3698same_shard(svn_fs_t *fs, 3699 svn_revnum_t r1, 3700 svn_revnum_t r2) 3701{ 3702 fs_fs_data_t *ffd = fs->fsap_data; 3703 return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir); 3704} 3705 3706/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS, 3707 * fill the START_REVISION, SIZES, OFFSETS members. Also, make 3708 * PACKED_REVPROPS point to the first serialized revprop. 3709 * 3710 * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as 3711 * well as the SERIALIZED_SIZE member. If revprop caching has been 3712 * enabled, parse all revprops in the pack and cache them. 3713 */ 3714static svn_error_t * 3715parse_packed_revprops(svn_fs_t *fs, 3716 packed_revprops_t *revprops, 3717 apr_pool_t *pool, 3718 apr_pool_t *scratch_pool) 3719{ 3720 svn_stream_t *stream; 3721 apr_int64_t first_rev, count, i; 3722 apr_off_t offset; 3723 const char *header_end; 3724 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3725 3726 /* decompress (even if the data is only "stored", there is still a 3727 * length header to remove) */ 3728 svn_string_t *compressed 3729 = svn_stringbuf__morph_into_string(revprops->packed_revprops); 3730 svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool); 3731 SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX)); 3732 3733 /* read first revision number and number of revisions in the pack */ 3734 stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); 3735 SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool)); 3736 SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool)); 3737 3738 /* Check revision range for validity. */ 3739 if ( !same_shard(fs, revprops->revision, first_rev) 3740 || !same_shard(fs, revprops->revision, first_rev + count - 1) 3741 || count < 1) 3742 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3743 _("Revprop pack for revision r%ld" 3744 " contains revprops for r%ld .. r%ld"), 3745 revprops->revision, 3746 (svn_revnum_t)first_rev, 3747 (svn_revnum_t)(first_rev + count -1)); 3748 3749 /* Since start & end are in the same shard, it is enough to just test 3750 * the FIRST_REV for being actually packed. That will also cover the 3751 * special case of rev 0 never being packed. */ 3752 if (!is_packed_revprop(fs, first_rev)) 3753 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3754 _("Revprop pack for revision r%ld" 3755 " starts at non-packed revisions r%ld"), 3756 revprops->revision, (svn_revnum_t)first_rev); 3757 3758 /* make PACKED_REVPROPS point to the first char after the header. 3759 * This is where the serialized revprops are. */ 3760 header_end = strstr(uncompressed->data, "\n\n"); 3761 if (header_end == NULL) 3762 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 3763 _("Header end not found")); 3764 3765 offset = header_end - uncompressed->data + 2; 3766 3767 revprops->packed_revprops = svn_stringbuf_create_empty(pool); 3768 revprops->packed_revprops->data = uncompressed->data + offset; 3769 revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset); 3770 revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset); 3771 3772 /* STREAM still points to the first entry in the sizes list. 3773 * Init / construct REVPROPS members. */ 3774 revprops->start_revision = (svn_revnum_t)first_rev; 3775 revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset)); 3776 revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset)); 3777 3778 /* Now parse, revision by revision, the size and content of each 3779 * revisions' revprops. */ 3780 for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i) 3781 { 3782 apr_int64_t size; 3783 svn_string_t serialized; 3784 apr_hash_t *properties; 3785 svn_revnum_t revision = (svn_revnum_t)(first_rev + i); 3786 3787 /* read & check the serialized size */ 3788 SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool)); 3789 if (size + offset > (apr_int64_t)revprops->packed_revprops->len) 3790 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 3791 _("Packed revprop size exceeds pack file size")); 3792 3793 /* Parse this revprops list, if necessary */ 3794 serialized.data = revprops->packed_revprops->data + offset; 3795 serialized.len = (apr_size_t)size; 3796 3797 if (revision == revprops->revision) 3798 { 3799 SVN_ERR(parse_revprop(&revprops->properties, fs, revision, 3800 revprops->generation, &serialized, 3801 pool, iterpool)); 3802 revprops->serialized_size = serialized.len; 3803 } 3804 else 3805 { 3806 /* If revprop caching is enabled, parse any revprops. 3807 * They will get cached as a side-effect of this. */ 3808 if (has_revprop_cache(fs, pool)) 3809 SVN_ERR(parse_revprop(&properties, fs, revision, 3810 revprops->generation, &serialized, 3811 iterpool, iterpool)); 3812 } 3813 3814 /* fill REVPROPS data structures */ 3815 APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len; 3816 APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset; 3817 revprops->total_size += serialized.len; 3818 3819 offset += serialized.len; 3820 3821 svn_pool_clear(iterpool); 3822 } 3823 3824 return SVN_NO_ERROR; 3825} 3826 3827/* In filesystem FS, read the packed revprops for revision REV into 3828 * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled. 3829 * Allocate data in POOL. 3830 */ 3831static svn_error_t * 3832read_pack_revprop(packed_revprops_t **revprops, 3833 svn_fs_t *fs, 3834 svn_revnum_t rev, 3835 apr_int64_t generation, 3836 apr_pool_t *pool) 3837{ 3838 apr_pool_t *iterpool = svn_pool_create(pool); 3839 svn_boolean_t missing = FALSE; 3840 svn_error_t *err; 3841 packed_revprops_t *result; 3842 int i; 3843 3844 /* someone insisted that REV is packed. Double-check if necessary */ 3845 if (!is_packed_revprop(fs, rev)) 3846 SVN_ERR(update_min_unpacked_rev(fs, iterpool)); 3847 3848 if (!is_packed_revprop(fs, rev)) 3849 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 3850 _("No such packed revision %ld"), rev); 3851 3852 /* initialize the result data structure */ 3853 result = apr_pcalloc(pool, sizeof(*result)); 3854 result->revision = rev; 3855 result->generation = generation; 3856 3857 /* try to read the packed revprops. This may require retries if we have 3858 * concurrent writers. */ 3859 for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i) 3860 { 3861 const char *file_path; 3862 3863 /* there might have been concurrent writes. 3864 * Re-read the manifest and the pack file. 3865 */ 3866 SVN_ERR(get_revprop_packname(fs, result, pool, iterpool)); 3867 file_path = svn_dirent_join(result->folder, 3868 result->filename, 3869 iterpool); 3870 SVN_ERR(try_stringbuf_from_file(&result->packed_revprops, 3871 &missing, 3872 file_path, 3873 i + 1 < RECOVERABLE_RETRY_COUNT, 3874 pool)); 3875 3876 /* If we could not find the file, there was a write. 3877 * So, we should refresh our revprop generation info as well such 3878 * that others may find data we will put into the cache. They would 3879 * consider it outdated, otherwise. 3880 */ 3881 if (missing && has_revprop_cache(fs, pool)) 3882 SVN_ERR(read_revprop_generation(&result->generation, fs, pool)); 3883 3884 svn_pool_clear(iterpool); 3885 } 3886 3887 /* the file content should be available now */ 3888 if (!result->packed_revprops) 3889 return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL, 3890 _("Failed to read revprop pack file for r%ld"), rev); 3891 3892 /* parse it. RESULT will be complete afterwards. */ 3893 err = parse_packed_revprops(fs, result, pool, iterpool); 3894 svn_pool_destroy(iterpool); 3895 if (err) 3896 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 3897 _("Revprop pack file for r%ld is corrupt"), rev); 3898 3899 *revprops = result; 3900 3901 return SVN_NO_ERROR; 3902} 3903 3904/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P. 3905 * 3906 * Allocations will be done in POOL. 3907 */ 3908static svn_error_t * 3909get_revision_proplist(apr_hash_t **proplist_p, 3910 svn_fs_t *fs, 3911 svn_revnum_t rev, 3912 apr_pool_t *pool) 3913{ 3914 fs_fs_data_t *ffd = fs->fsap_data; 3915 apr_int64_t generation = 0; 3916 3917 /* not found, yet */ 3918 *proplist_p = NULL; 3919 3920 /* should they be available at all? */ 3921 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 3922 3923 /* Try cache lookup first. */ 3924 if (has_revprop_cache(fs, pool)) 3925 { 3926 svn_boolean_t is_cached; 3927 pair_cache_key_t key = { 0 }; 3928 3929 SVN_ERR(read_revprop_generation(&generation, fs, pool)); 3930 3931 key.revision = rev; 3932 key.second = generation; 3933 SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 3934 ffd->revprop_cache, &key, pool)); 3935 if (is_cached) 3936 return SVN_NO_ERROR; 3937 } 3938 3939 /* if REV had not been packed when we began, try reading it from the 3940 * non-packed shard. If that fails, we will fall through to packed 3941 * shard reads. */ 3942 if (!is_packed_revprop(fs, rev)) 3943 { 3944 svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev, 3945 generation, pool); 3946 if (err) 3947 { 3948 if (!APR_STATUS_IS_ENOENT(err->apr_err) 3949 || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 3950 return svn_error_trace(err); 3951 3952 svn_error_clear(err); 3953 *proplist_p = NULL; /* in case read_non_packed_revprop changed it */ 3954 } 3955 } 3956 3957 /* if revprop packing is available and we have not read the revprops, yet, 3958 * try reading them from a packed shard. If that fails, REV is most 3959 * likely invalid (or its revprops highly contested). */ 3960 if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p) 3961 { 3962 packed_revprops_t *packed_revprops; 3963 SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool)); 3964 *proplist_p = packed_revprops->properties; 3965 } 3966 3967 /* The revprops should have been there. Did we get them? */ 3968 if (!*proplist_p) 3969 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 3970 _("Could not read revprops for revision %ld"), 3971 rev); 3972 3973 return SVN_NO_ERROR; 3974} 3975 3976/* Serialize the revision property list PROPLIST of revision REV in 3977 * filesystem FS to a non-packed file. Return the name of that temporary 3978 * file in *TMP_PATH and the file path that it must be moved to in 3979 * *FINAL_PATH. 3980 * 3981 * Use POOL for allocations. 3982 */ 3983static svn_error_t * 3984write_non_packed_revprop(const char **final_path, 3985 const char **tmp_path, 3986 svn_fs_t *fs, 3987 svn_revnum_t rev, 3988 apr_hash_t *proplist, 3989 apr_pool_t *pool) 3990{ 3991 apr_file_t *file; 3992 svn_stream_t *stream; 3993 *final_path = path_revprops(fs, rev, pool); 3994 3995 /* ### do we have a directory sitting around already? we really shouldn't 3996 ### have to get the dirname here. */ 3997 SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, 3998 svn_dirent_dirname(*final_path, pool), 3999 svn_io_file_del_none, pool, pool)); 4000 stream = svn_stream_from_aprfile2(file, TRUE, pool); 4001 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4002 SVN_ERR(svn_stream_close(stream)); 4003 4004 /* Flush temporary file to disk and close it. */ 4005 SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4006 SVN_ERR(svn_io_file_close(file, pool)); 4007 4008 return SVN_NO_ERROR; 4009} 4010 4011/* After writing the new revprop file(s), call this function to move the 4012 * file at TMP_PATH to FINAL_PATH and give it the permissions from 4013 * PERMS_REFERENCE. 4014 * 4015 * If indicated in BUMP_GENERATION, increase FS' revprop generation. 4016 * Finally, delete all the temporary files given in FILES_TO_DELETE. 4017 * The latter may be NULL. 4018 * 4019 * Use POOL for temporary allocations. 4020 */ 4021static svn_error_t * 4022switch_to_new_revprop(svn_fs_t *fs, 4023 const char *final_path, 4024 const char *tmp_path, 4025 const char *perms_reference, 4026 apr_array_header_t *files_to_delete, 4027 svn_boolean_t bump_generation, 4028 apr_pool_t *pool) 4029{ 4030 /* Now, we may actually be replacing revprops. Make sure that all other 4031 threads and processes will know about this. */ 4032 if (bump_generation) 4033 SVN_ERR(begin_revprop_change(fs, pool)); 4034 4035 SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool)); 4036 4037 /* Indicate that the update (if relevant) has been completed. */ 4038 if (bump_generation) 4039 SVN_ERR(end_revprop_change(fs, pool)); 4040 4041 /* Clean up temporary files, if necessary. */ 4042 if (files_to_delete) 4043 { 4044 apr_pool_t *iterpool = svn_pool_create(pool); 4045 int i; 4046 4047 for (i = 0; i < files_to_delete->nelts; ++i) 4048 { 4049 const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*); 4050 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 4051 svn_pool_clear(iterpool); 4052 } 4053 4054 svn_pool_destroy(iterpool); 4055 } 4056 return SVN_NO_ERROR; 4057} 4058 4059/* Write a pack file header to STREAM that starts at revision START_REVISION 4060 * and contains the indexes [START,END) of SIZES. 4061 */ 4062static svn_error_t * 4063serialize_revprops_header(svn_stream_t *stream, 4064 svn_revnum_t start_revision, 4065 apr_array_header_t *sizes, 4066 int start, 4067 int end, 4068 apr_pool_t *pool) 4069{ 4070 apr_pool_t *iterpool = svn_pool_create(pool); 4071 int i; 4072 4073 SVN_ERR_ASSERT(start < end); 4074 4075 /* start revision and entry count */ 4076 SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision)); 4077 SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start)); 4078 4079 /* the sizes array */ 4080 for (i = start; i < end; ++i) 4081 { 4082 apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t); 4083 SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n", 4084 size)); 4085 } 4086 4087 /* the double newline char indicates the end of the header */ 4088 SVN_ERR(svn_stream_printf(stream, iterpool, "\n")); 4089 4090 svn_pool_clear(iterpool); 4091 return SVN_NO_ERROR; 4092} 4093 4094/* Writes the a pack file to FILE. It copies the serialized data 4095 * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX. 4096 * 4097 * The data for the latter is taken from NEW_SERIALIZED. Note, that 4098 * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is 4099 * taken in that case but only a subset of the old data will be copied. 4100 * 4101 * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size. 4102 * POOL is used for temporary allocations. 4103 */ 4104static svn_error_t * 4105repack_revprops(svn_fs_t *fs, 4106 packed_revprops_t *revprops, 4107 int start, 4108 int end, 4109 int changed_index, 4110 svn_stringbuf_t *new_serialized, 4111 apr_off_t new_total_size, 4112 apr_file_t *file, 4113 apr_pool_t *pool) 4114{ 4115 fs_fs_data_t *ffd = fs->fsap_data; 4116 svn_stream_t *stream; 4117 int i; 4118 4119 /* create data empty buffers and the stream object */ 4120 svn_stringbuf_t *uncompressed 4121 = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool); 4122 svn_stringbuf_t *compressed 4123 = svn_stringbuf_create_empty(pool); 4124 stream = svn_stream_from_stringbuf(uncompressed, pool); 4125 4126 /* write the header*/ 4127 SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start, 4128 revprops->sizes, start, end, pool)); 4129 4130 /* append the serialized revprops */ 4131 for (i = start; i < end; ++i) 4132 if (i == changed_index) 4133 { 4134 SVN_ERR(svn_stream_write(stream, 4135 new_serialized->data, 4136 &new_serialized->len)); 4137 } 4138 else 4139 { 4140 apr_size_t size 4141 = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t); 4142 apr_size_t offset 4143 = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t); 4144 4145 SVN_ERR(svn_stream_write(stream, 4146 revprops->packed_revprops->data + offset, 4147 &size)); 4148 } 4149 4150 /* flush the stream buffer (if any) to our underlying data buffer */ 4151 SVN_ERR(svn_stream_close(stream)); 4152 4153 /* compress / store the data */ 4154 SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 4155 compressed, 4156 ffd->compress_packed_revprops 4157 ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 4158 : SVN_DELTA_COMPRESSION_LEVEL_NONE)); 4159 4160 /* finally, write the content to the target file, flush and close it */ 4161 SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len, 4162 NULL, pool)); 4163 SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4164 SVN_ERR(svn_io_file_close(file, pool)); 4165 4166 return SVN_NO_ERROR; 4167} 4168 4169/* Allocate a new pack file name for revisions 4170 * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1] 4171 * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE, 4172 * auto-create that array if necessary. Return an open file *FILE that is 4173 * allocated in POOL. 4174 */ 4175static svn_error_t * 4176repack_file_open(apr_file_t **file, 4177 svn_fs_t *fs, 4178 packed_revprops_t *revprops, 4179 int start, 4180 int end, 4181 apr_array_header_t **files_to_delete, 4182 apr_pool_t *pool) 4183{ 4184 apr_int64_t tag; 4185 const char *tag_string; 4186 svn_string_t *new_filename; 4187 int i; 4188 int manifest_offset 4189 = (int)(revprops->start_revision - revprops->manifest_start); 4190 4191 /* get the old (= current) file name and enlist it for later deletion */ 4192 const char *old_filename = APR_ARRAY_IDX(revprops->manifest, 4193 start + manifest_offset, 4194 const char*); 4195 4196 if (*files_to_delete == NULL) 4197 *files_to_delete = apr_array_make(pool, 3, sizeof(const char*)); 4198 4199 APR_ARRAY_PUSH(*files_to_delete, const char*) 4200 = svn_dirent_join(revprops->folder, old_filename, pool); 4201 4202 /* increase the tag part, i.e. the counter after the dot */ 4203 tag_string = strchr(old_filename, '.'); 4204 if (tag_string == NULL) 4205 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4206 _("Packed file '%s' misses a tag"), 4207 old_filename); 4208 4209 SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1)); 4210 new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT, 4211 revprops->start_revision + start, 4212 ++tag); 4213 4214 /* update the manifest to point to the new file */ 4215 for (i = start; i < end; ++i) 4216 APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*) 4217 = new_filename->data; 4218 4219 /* open the file */ 4220 SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder, 4221 new_filename->data, 4222 pool), 4223 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 4224 4225 return SVN_NO_ERROR; 4226} 4227 4228/* For revision REV in filesystem FS, set the revision properties to 4229 * PROPLIST. Return a new file in *TMP_PATH that the caller shall move 4230 * to *FINAL_PATH to make the change visible. Files to be deleted will 4231 * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated. 4232 * Use POOL for allocations. 4233 */ 4234static svn_error_t * 4235write_packed_revprop(const char **final_path, 4236 const char **tmp_path, 4237 apr_array_header_t **files_to_delete, 4238 svn_fs_t *fs, 4239 svn_revnum_t rev, 4240 apr_hash_t *proplist, 4241 apr_pool_t *pool) 4242{ 4243 fs_fs_data_t *ffd = fs->fsap_data; 4244 packed_revprops_t *revprops; 4245 apr_int64_t generation = 0; 4246 svn_stream_t *stream; 4247 apr_file_t *file; 4248 svn_stringbuf_t *serialized; 4249 apr_off_t new_total_size; 4250 int changed_index; 4251 4252 /* read the current revprop generation. This value will not change 4253 * while we hold the global write lock to this FS. */ 4254 if (has_revprop_cache(fs, pool)) 4255 SVN_ERR(read_revprop_generation(&generation, fs, pool)); 4256 4257 /* read contents of the current pack file */ 4258 SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool)); 4259 4260 /* serialize the new revprops */ 4261 serialized = svn_stringbuf_create_empty(pool); 4262 stream = svn_stream_from_stringbuf(serialized, pool); 4263 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4264 SVN_ERR(svn_stream_close(stream)); 4265 4266 /* calculate the size of the new data */ 4267 changed_index = (int)(rev - revprops->start_revision); 4268 new_total_size = revprops->total_size - revprops->serialized_size 4269 + serialized->len 4270 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE; 4271 4272 APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len; 4273 4274 /* can we put the new data into the same pack as the before? */ 4275 if ( new_total_size < ffd->revprop_pack_size 4276 || revprops->sizes->nelts == 1) 4277 { 4278 /* simply replace the old pack file with new content as we do it 4279 * in the non-packed case */ 4280 4281 *final_path = svn_dirent_join(revprops->folder, revprops->filename, 4282 pool); 4283 SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, 4284 svn_io_file_del_none, pool, pool)); 4285 SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts, 4286 changed_index, serialized, new_total_size, 4287 file, pool)); 4288 } 4289 else 4290 { 4291 /* split the pack file into two of roughly equal size */ 4292 int right_count, left_count, i; 4293 4294 int left = 0; 4295 int right = revprops->sizes->nelts - 1; 4296 apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE; 4297 apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE; 4298 4299 /* let left and right side grow such that their size difference 4300 * is minimal after each step. */ 4301 while (left <= right) 4302 if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4303 < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)) 4304 { 4305 left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4306 + SVN_INT64_BUFFER_SIZE; 4307 ++left; 4308 } 4309 else 4310 { 4311 right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t) 4312 + SVN_INT64_BUFFER_SIZE; 4313 --right; 4314 } 4315 4316 /* since the items need much less than SVN_INT64_BUFFER_SIZE 4317 * bytes to represent their length, the split may not be optimal */ 4318 left_count = left; 4319 right_count = revprops->sizes->nelts - left; 4320 4321 /* if new_size is large, one side may exceed the pack size limit. 4322 * In that case, split before and after the modified revprop.*/ 4323 if ( left_size > ffd->revprop_pack_size 4324 || right_size > ffd->revprop_pack_size) 4325 { 4326 left_count = changed_index; 4327 right_count = revprops->sizes->nelts - left_count - 1; 4328 } 4329 4330 /* write the new, split files */ 4331 if (left_count) 4332 { 4333 SVN_ERR(repack_file_open(&file, fs, revprops, 0, 4334 left_count, files_to_delete, pool)); 4335 SVN_ERR(repack_revprops(fs, revprops, 0, left_count, 4336 changed_index, serialized, new_total_size, 4337 file, pool)); 4338 } 4339 4340 if (left_count + right_count < revprops->sizes->nelts) 4341 { 4342 SVN_ERR(repack_file_open(&file, fs, revprops, changed_index, 4343 changed_index + 1, files_to_delete, 4344 pool)); 4345 SVN_ERR(repack_revprops(fs, revprops, changed_index, 4346 changed_index + 1, 4347 changed_index, serialized, new_total_size, 4348 file, pool)); 4349 } 4350 4351 if (right_count) 4352 { 4353 SVN_ERR(repack_file_open(&file, fs, revprops, 4354 revprops->sizes->nelts - right_count, 4355 revprops->sizes->nelts, 4356 files_to_delete, pool)); 4357 SVN_ERR(repack_revprops(fs, revprops, 4358 revprops->sizes->nelts - right_count, 4359 revprops->sizes->nelts, changed_index, 4360 serialized, new_total_size, file, 4361 pool)); 4362 } 4363 4364 /* write the new manifest */ 4365 *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); 4366 SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, 4367 svn_io_file_del_none, pool, pool)); 4368 4369 for (i = 0; i < revprops->manifest->nelts; ++i) 4370 { 4371 const char *filename = APR_ARRAY_IDX(revprops->manifest, i, 4372 const char*); 4373 SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename), 4374 NULL, pool)); 4375 SVN_ERR(svn_io_file_putc('\n', file, pool)); 4376 } 4377 4378 SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4379 SVN_ERR(svn_io_file_close(file, pool)); 4380 } 4381 4382 return SVN_NO_ERROR; 4383} 4384 4385/* Set the revision property list of revision REV in filesystem FS to 4386 PROPLIST. Use POOL for temporary allocations. */ 4387static svn_error_t * 4388set_revision_proplist(svn_fs_t *fs, 4389 svn_revnum_t rev, 4390 apr_hash_t *proplist, 4391 apr_pool_t *pool) 4392{ 4393 svn_boolean_t is_packed; 4394 svn_boolean_t bump_generation = FALSE; 4395 const char *final_path; 4396 const char *tmp_path; 4397 const char *perms_reference; 4398 apr_array_header_t *files_to_delete = NULL; 4399 4400 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 4401 4402 /* this info will not change while we hold the global FS write lock */ 4403 is_packed = is_packed_revprop(fs, rev); 4404 4405 /* Test whether revprops already exist for this revision. 4406 * Only then will we need to bump the revprop generation. */ 4407 if (has_revprop_cache(fs, pool)) 4408 { 4409 if (is_packed) 4410 { 4411 bump_generation = TRUE; 4412 } 4413 else 4414 { 4415 svn_node_kind_t kind; 4416 SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind, 4417 pool)); 4418 bump_generation = kind != svn_node_none; 4419 } 4420 } 4421 4422 /* Serialize the new revprop data */ 4423 if (is_packed) 4424 SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete, 4425 fs, rev, proplist, pool)); 4426 else 4427 SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path, 4428 fs, rev, proplist, pool)); 4429 4430 /* We use the rev file of this revision as the perms reference, 4431 * because when setting revprops for the first time, the revprop 4432 * file won't exist and therefore can't serve as its own reference. 4433 * (Whereas the rev file should already exist at this point.) 4434 */ 4435 SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool)); 4436 4437 /* Now, switch to the new revprop data. */ 4438 SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference, 4439 files_to_delete, bump_generation, pool)); 4440 4441 return SVN_NO_ERROR; 4442} 4443 4444svn_error_t * 4445svn_fs_fs__revision_proplist(apr_hash_t **proplist_p, 4446 svn_fs_t *fs, 4447 svn_revnum_t rev, 4448 apr_pool_t *pool) 4449{ 4450 SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool)); 4451 4452 return SVN_NO_ERROR; 4453} 4454 4455/* Represents where in the current svndiff data block each 4456 representation is. */ 4457struct rep_state 4458{ 4459 apr_file_t *file; 4460 /* The txdelta window cache to use or NULL. */ 4461 svn_cache__t *window_cache; 4462 /* Caches un-deltified windows. May be NULL. */ 4463 svn_cache__t *combined_cache; 4464 apr_off_t start; /* The starting offset for the raw 4465 svndiff/plaintext data minus header. */ 4466 apr_off_t off; /* The current offset into the file. */ 4467 apr_off_t end; /* The end offset of the raw data. */ 4468 int ver; /* If a delta, what svndiff version? */ 4469 int chunk_index; 4470}; 4471 4472/* See create_rep_state, which wraps this and adds another error. */ 4473static svn_error_t * 4474create_rep_state_body(struct rep_state **rep_state, 4475 struct rep_args **rep_args, 4476 apr_file_t **file_hint, 4477 svn_revnum_t *rev_hint, 4478 representation_t *rep, 4479 svn_fs_t *fs, 4480 apr_pool_t *pool) 4481{ 4482 fs_fs_data_t *ffd = fs->fsap_data; 4483 struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs)); 4484 struct rep_args *ra; 4485 unsigned char buf[4]; 4486 4487 /* If the hint is 4488 * - given, 4489 * - refers to a valid revision, 4490 * - refers to a packed revision, 4491 * - as does the rep we want to read, and 4492 * - refers to the same pack file as the rep 4493 * ... 4494 */ 4495 if ( file_hint && rev_hint && *file_hint 4496 && SVN_IS_VALID_REVNUM(*rev_hint) 4497 && *rev_hint < ffd->min_unpacked_rev 4498 && rep->revision < ffd->min_unpacked_rev 4499 && ( (*rev_hint / ffd->max_files_per_dir) 4500 == (rep->revision / ffd->max_files_per_dir))) 4501 { 4502 /* ... we can re-use the same, already open file object 4503 */ 4504 apr_off_t offset; 4505 SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool)); 4506 4507 offset += rep->offset; 4508 SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool)); 4509 4510 rs->file = *file_hint; 4511 } 4512 else 4513 { 4514 /* otherwise, create a new file object 4515 */ 4516 SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool)); 4517 } 4518 4519 /* remember the current file, if suggested by the caller */ 4520 if (file_hint) 4521 *file_hint = rs->file; 4522 if (rev_hint) 4523 *rev_hint = rep->revision; 4524 4525 /* continue constructing RS and RA */ 4526 rs->window_cache = ffd->txdelta_window_cache; 4527 rs->combined_cache = ffd->combined_window_cache; 4528 4529 SVN_ERR(read_rep_line(&ra, rs->file, pool)); 4530 SVN_ERR(get_file_offset(&rs->start, rs->file, pool)); 4531 rs->off = rs->start; 4532 rs->end = rs->start + rep->size; 4533 *rep_state = rs; 4534 *rep_args = ra; 4535 4536 if (!ra->is_delta) 4537 /* This is a plaintext, so just return the current rep_state. */ 4538 return SVN_NO_ERROR; 4539 4540 /* We are dealing with a delta, find out what version. */ 4541 SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf), 4542 NULL, NULL, pool)); 4543 /* ### Layering violation */ 4544 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N'))) 4545 return svn_error_create 4546 (SVN_ERR_FS_CORRUPT, NULL, 4547 _("Malformed svndiff data in representation")); 4548 rs->ver = buf[3]; 4549 rs->chunk_index = 0; 4550 rs->off += 4; 4551 4552 return SVN_NO_ERROR; 4553} 4554 4555/* Read the rep args for REP in filesystem FS and create a rep_state 4556 for reading the representation. Return the rep_state in *REP_STATE 4557 and the rep args in *REP_ARGS, both allocated in POOL. 4558 4559 When reading multiple reps, i.e. a skip delta chain, you may provide 4560 non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first 4561 call it should be a pointer to NULL.) The function will use these variables 4562 to store the previous call results and tries to re-use them. This may 4563 result in significant savings in I/O for packed files. 4564 */ 4565static svn_error_t * 4566create_rep_state(struct rep_state **rep_state, 4567 struct rep_args **rep_args, 4568 apr_file_t **file_hint, 4569 svn_revnum_t *rev_hint, 4570 representation_t *rep, 4571 svn_fs_t *fs, 4572 apr_pool_t *pool) 4573{ 4574 svn_error_t *err = create_rep_state_body(rep_state, rep_args, 4575 file_hint, rev_hint, 4576 rep, fs, pool); 4577 if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 4578 { 4579 fs_fs_data_t *ffd = fs->fsap_data; 4580 4581 /* ### This always returns "-1" for transaction reps, because 4582 ### this particular bit of code doesn't know if the rep is 4583 ### stored in the protorev or in the mutable area (for props 4584 ### or dir contents). It is pretty rare for FSFS to *read* 4585 ### from the protorev file, though, so this is probably OK. 4586 ### And anyone going to debug corruption errors is probably 4587 ### going to jump straight to this comment anyway! */ 4588 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 4589 "Corrupt representation '%s'", 4590 rep 4591 ? representation_string(rep, ffd->format, TRUE, 4592 TRUE, pool) 4593 : "(null)"); 4594 } 4595 /* ### Call representation_string() ? */ 4596 return svn_error_trace(err); 4597} 4598 4599struct rep_read_baton 4600{ 4601 /* The FS from which we're reading. */ 4602 svn_fs_t *fs; 4603 4604 /* If not NULL, this is the base for the first delta window in rs_list */ 4605 svn_stringbuf_t *base_window; 4606 4607 /* The state of all prior delta representations. */ 4608 apr_array_header_t *rs_list; 4609 4610 /* The plaintext state, if there is a plaintext. */ 4611 struct rep_state *src_state; 4612 4613 /* The index of the current delta chunk, if we are reading a delta. */ 4614 int chunk_index; 4615 4616 /* The buffer where we store undeltified data. */ 4617 char *buf; 4618 apr_size_t buf_pos; 4619 apr_size_t buf_len; 4620 4621 /* A checksum context for summing the data read in order to verify it. 4622 Note: we don't need to use the sha1 checksum because we're only doing 4623 data verification, for which md5 is perfectly safe. */ 4624 svn_checksum_ctx_t *md5_checksum_ctx; 4625 4626 svn_boolean_t checksum_finalized; 4627 4628 /* The stored checksum of the representation we are reading, its 4629 length, and the amount we've read so far. Some of this 4630 information is redundant with rs_list and src_state, but it's 4631 convenient for the checksumming code to have it here. */ 4632 svn_checksum_t *md5_checksum; 4633 4634 svn_filesize_t len; 4635 svn_filesize_t off; 4636 4637 /* The key for the fulltext cache for this rep, if there is a 4638 fulltext cache. */ 4639 pair_cache_key_t fulltext_cache_key; 4640 /* The text we've been reading, if we're going to cache it. */ 4641 svn_stringbuf_t *current_fulltext; 4642 4643 /* Used for temporary allocations during the read. */ 4644 apr_pool_t *pool; 4645 4646 /* Pool used to store file handles and other data that is persistant 4647 for the entire stream read. */ 4648 apr_pool_t *filehandle_pool; 4649}; 4650 4651/* Combine the name of the rev file in RS with the given OFFSET to form 4652 * a cache lookup key. Allocations will be made from POOL. May return 4653 * NULL if the key cannot be constructed. */ 4654static const char* 4655get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool) 4656{ 4657 const char *name; 4658 const char *last_part; 4659 const char *name_last; 4660 4661 /* the rev file name containing the txdelta window. 4662 * If this fails we are in serious trouble anyways. 4663 * And if nobody else detects the problems, the file content checksum 4664 * comparison _will_ find them. 4665 */ 4666 if (apr_file_name_get(&name, rs->file)) 4667 return NULL; 4668 4669 /* Handle packed files as well by scanning backwards until we find the 4670 * revision or pack number. */ 4671 name_last = name + strlen(name) - 1; 4672 while (! svn_ctype_isdigit(*name_last)) 4673 --name_last; 4674 4675 last_part = name_last; 4676 while (svn_ctype_isdigit(*last_part)) 4677 --last_part; 4678 4679 /* We must differentiate between packed files (as of today, the number 4680 * is being followed by a dot) and non-packed files (followed by \0). 4681 * Otherwise, there might be overlaps in the numbering range if the 4682 * repo gets packed after caching the txdeltas of non-packed revs. 4683 * => add the first non-digit char to the packed number. */ 4684 if (name_last[1] != '\0') 4685 ++name_last; 4686 4687 /* copy one char MORE than the actual number to mark packed files, 4688 * i.e. packed revision file content uses different key space then 4689 * non-packed ones: keys for packed rev file content ends with a dot 4690 * for non-packed rev files they end with a digit. */ 4691 name = apr_pstrndup(pool, last_part + 1, name_last - last_part); 4692 return svn_fs_fs__combine_number_and_string(offset, name, pool); 4693} 4694 4695/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4696 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4697 * cache has been given. If a cache is available IS_CACHED will inform 4698 * the caller about the success of the lookup. Allocations (of the window 4699 * in particualar) will be made from POOL. 4700 * 4701 * If the information could be found, put RS and the position within the 4702 * rev file into the same state as if the data had just been read from it. 4703 */ 4704static svn_error_t * 4705get_cached_window(svn_txdelta_window_t **window_p, 4706 struct rep_state *rs, 4707 svn_boolean_t *is_cached, 4708 apr_pool_t *pool) 4709{ 4710 if (! rs->window_cache) 4711 { 4712 /* txdelta window has not been enabled */ 4713 *is_cached = FALSE; 4714 } 4715 else 4716 { 4717 /* ask the cache for the desired txdelta window */ 4718 svn_fs_fs__txdelta_cached_window_t *cached_window; 4719 SVN_ERR(svn_cache__get((void **) &cached_window, 4720 is_cached, 4721 rs->window_cache, 4722 get_window_key(rs, rs->off, pool), 4723 pool)); 4724 4725 if (*is_cached) 4726 { 4727 /* found it. Pass it back to the caller. */ 4728 *window_p = cached_window->window; 4729 4730 /* manipulate the RS as if we just read the data */ 4731 rs->chunk_index++; 4732 rs->off = cached_window->end_offset; 4733 4734 /* manipulate the rev file as if we just read from it */ 4735 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4736 } 4737 } 4738 4739 return SVN_NO_ERROR; 4740} 4741 4742/* Store the WINDOW read at OFFSET for the rep state RS in the current 4743 * FSFS session's cache. This will be a no-op if no cache has been given. 4744 * Temporary allocations will be made from SCRATCH_POOL. */ 4745static svn_error_t * 4746set_cached_window(svn_txdelta_window_t *window, 4747 struct rep_state *rs, 4748 apr_off_t offset, 4749 apr_pool_t *scratch_pool) 4750{ 4751 if (rs->window_cache) 4752 { 4753 /* store the window and the first offset _past_ it */ 4754 svn_fs_fs__txdelta_cached_window_t cached_window; 4755 4756 cached_window.window = window; 4757 cached_window.end_offset = rs->off; 4758 4759 /* but key it with the start offset because that is the known state 4760 * when we will look it up */ 4761 return svn_cache__set(rs->window_cache, 4762 get_window_key(rs, offset, scratch_pool), 4763 &cached_window, 4764 scratch_pool); 4765 } 4766 4767 return SVN_NO_ERROR; 4768} 4769 4770/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4771 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4772 * cache has been given. If a cache is available IS_CACHED will inform 4773 * the caller about the success of the lookup. Allocations (of the window 4774 * in particualar) will be made from POOL. 4775 */ 4776static svn_error_t * 4777get_cached_combined_window(svn_stringbuf_t **window_p, 4778 struct rep_state *rs, 4779 svn_boolean_t *is_cached, 4780 apr_pool_t *pool) 4781{ 4782 if (! rs->combined_cache) 4783 { 4784 /* txdelta window has not been enabled */ 4785 *is_cached = FALSE; 4786 } 4787 else 4788 { 4789 /* ask the cache for the desired txdelta window */ 4790 return svn_cache__get((void **)window_p, 4791 is_cached, 4792 rs->combined_cache, 4793 get_window_key(rs, rs->start, pool), 4794 pool); 4795 } 4796 4797 return SVN_NO_ERROR; 4798} 4799 4800/* Store the WINDOW read at OFFSET for the rep state RS in the current 4801 * FSFS session's cache. This will be a no-op if no cache has been given. 4802 * Temporary allocations will be made from SCRATCH_POOL. */ 4803static svn_error_t * 4804set_cached_combined_window(svn_stringbuf_t *window, 4805 struct rep_state *rs, 4806 apr_off_t offset, 4807 apr_pool_t *scratch_pool) 4808{ 4809 if (rs->combined_cache) 4810 { 4811 /* but key it with the start offset because that is the known state 4812 * when we will look it up */ 4813 return svn_cache__set(rs->combined_cache, 4814 get_window_key(rs, offset, scratch_pool), 4815 window, 4816 scratch_pool); 4817 } 4818 4819 return SVN_NO_ERROR; 4820} 4821 4822/* Build an array of rep_state structures in *LIST giving the delta 4823 reps from first_rep to a plain-text or self-compressed rep. Set 4824 *SRC_STATE to the plain-text rep we find at the end of the chain, 4825 or to NULL if the final delta representation is self-compressed. 4826 The representation to start from is designated by filesystem FS, id 4827 ID, and representation REP. 4828 Also, set *WINDOW_P to the base window content for *LIST, if it 4829 could be found in cache. Otherwise, *LIST will contain the base 4830 representation for the whole delta chain. 4831 Finally, return the expanded size of the representation in 4832 *EXPANDED_SIZE. It will take care of cases where only the on-disk 4833 size is known. */ 4834static svn_error_t * 4835build_rep_list(apr_array_header_t **list, 4836 svn_stringbuf_t **window_p, 4837 struct rep_state **src_state, 4838 svn_filesize_t *expanded_size, 4839 svn_fs_t *fs, 4840 representation_t *first_rep, 4841 apr_pool_t *pool) 4842{ 4843 representation_t rep; 4844 struct rep_state *rs = NULL; 4845 struct rep_args *rep_args; 4846 svn_boolean_t is_cached = FALSE; 4847 apr_file_t *last_file = NULL; 4848 svn_revnum_t last_revision; 4849 4850 *list = apr_array_make(pool, 1, sizeof(struct rep_state *)); 4851 rep = *first_rep; 4852 4853 /* The value as stored in the data struct. 4854 0 is either for unknown length or actually zero length. */ 4855 *expanded_size = first_rep->expanded_size; 4856 4857 /* for the top-level rep, we need the rep_args */ 4858 SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4859 &last_revision, &rep, fs, pool)); 4860 4861 /* Unknown size or empty representation? 4862 That implies the this being the first iteration. 4863 Usually size equals on-disk size, except for empty, 4864 compressed representations (delta, size = 4). 4865 Please note that for all non-empty deltas have 4866 a 4-byte header _plus_ some data. */ 4867 if (*expanded_size == 0) 4868 if (! rep_args->is_delta || first_rep->size != 4) 4869 *expanded_size = first_rep->size; 4870 4871 while (1) 4872 { 4873 /* fetch state, if that has not been done already */ 4874 if (!rs) 4875 SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4876 &last_revision, &rep, fs, pool)); 4877 4878 SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool)); 4879 if (is_cached) 4880 { 4881 /* We already have a reconstructed window in our cache. 4882 Write a pseudo rep_state with the full length. */ 4883 rs->off = rs->start; 4884 rs->end = rs->start + (*window_p)->len; 4885 *src_state = rs; 4886 return SVN_NO_ERROR; 4887 } 4888 4889 if (!rep_args->is_delta) 4890 { 4891 /* This is a plaintext, so just return the current rep_state. */ 4892 *src_state = rs; 4893 return SVN_NO_ERROR; 4894 } 4895 4896 /* Push this rep onto the list. If it's self-compressed, we're done. */ 4897 APR_ARRAY_PUSH(*list, struct rep_state *) = rs; 4898 if (rep_args->is_delta_vs_empty) 4899 { 4900 *src_state = NULL; 4901 return SVN_NO_ERROR; 4902 } 4903 4904 rep.revision = rep_args->base_revision; 4905 rep.offset = rep_args->base_offset; 4906 rep.size = rep_args->base_length; 4907 rep.txn_id = NULL; 4908 4909 rs = NULL; 4910 } 4911} 4912 4913 4914/* Create a rep_read_baton structure for node revision NODEREV in 4915 filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not 4916 NULL, it is the rep's key in the fulltext cache, and a stringbuf 4917 must be allocated to store the text. Perform all allocations in 4918 POOL. If rep is mutable, it must be for file contents. */ 4919static svn_error_t * 4920rep_read_get_baton(struct rep_read_baton **rb_p, 4921 svn_fs_t *fs, 4922 representation_t *rep, 4923 pair_cache_key_t fulltext_cache_key, 4924 apr_pool_t *pool) 4925{ 4926 struct rep_read_baton *b; 4927 4928 b = apr_pcalloc(pool, sizeof(*b)); 4929 b->fs = fs; 4930 b->base_window = NULL; 4931 b->chunk_index = 0; 4932 b->buf = NULL; 4933 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 4934 b->checksum_finalized = FALSE; 4935 b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 4936 b->len = rep->expanded_size; 4937 b->off = 0; 4938 b->fulltext_cache_key = fulltext_cache_key; 4939 b->pool = svn_pool_create(pool); 4940 b->filehandle_pool = svn_pool_create(pool); 4941 4942 SVN_ERR(build_rep_list(&b->rs_list, &b->base_window, 4943 &b->src_state, &b->len, fs, rep, 4944 b->filehandle_pool)); 4945 4946 if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision)) 4947 b->current_fulltext = svn_stringbuf_create_ensure 4948 ((apr_size_t)b->len, 4949 b->filehandle_pool); 4950 else 4951 b->current_fulltext = NULL; 4952 4953 /* Save our output baton. */ 4954 *rb_p = b; 4955 4956 return SVN_NO_ERROR; 4957} 4958 4959/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta 4960 window into *NWIN. */ 4961static svn_error_t * 4962read_delta_window(svn_txdelta_window_t **nwin, int this_chunk, 4963 struct rep_state *rs, apr_pool_t *pool) 4964{ 4965 svn_stream_t *stream; 4966 svn_boolean_t is_cached; 4967 apr_off_t old_offset; 4968 4969 SVN_ERR_ASSERT(rs->chunk_index <= this_chunk); 4970 4971 /* RS->FILE may be shared between RS instances -> make sure we point 4972 * to the right data. */ 4973 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4974 4975 /* Skip windows to reach the current chunk if we aren't there yet. */ 4976 while (rs->chunk_index < this_chunk) 4977 { 4978 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool)); 4979 rs->chunk_index++; 4980 SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4981 if (rs->off >= rs->end) 4982 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 4983 _("Reading one svndiff window read " 4984 "beyond the end of the " 4985 "representation")); 4986 } 4987 4988 /* Read the next window. But first, try to find it in the cache. */ 4989 SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool)); 4990 if (is_cached) 4991 return SVN_NO_ERROR; 4992 4993 /* Actually read the next window. */ 4994 old_offset = rs->off; 4995 stream = svn_stream_from_aprfile2(rs->file, TRUE, pool); 4996 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool)); 4997 rs->chunk_index++; 4998 SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4999 5000 if (rs->off > rs->end) 5001 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 5002 _("Reading one svndiff window read beyond " 5003 "the end of the representation")); 5004 5005 /* the window has not been cached before, thus cache it now 5006 * (if caching is used for them at all) */ 5007 return set_cached_window(*nwin, rs, old_offset, pool); 5008} 5009 5010/* Read SIZE bytes from the representation RS and return it in *NWIN. */ 5011static svn_error_t * 5012read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs, 5013 apr_size_t size, apr_pool_t *pool) 5014{ 5015 /* RS->FILE may be shared between RS instances -> make sure we point 5016 * to the right data. */ 5017 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 5018 5019 /* Read the plain data. */ 5020 *nwin = svn_stringbuf_create_ensure(size, pool); 5021 SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL, 5022 pool)); 5023 (*nwin)->data[size] = 0; 5024 5025 /* Update RS. */ 5026 rs->off += (apr_off_t)size; 5027 5028 return SVN_NO_ERROR; 5029} 5030 5031/* Get the undeltified window that is a result of combining all deltas 5032 from the current desired representation identified in *RB with its 5033 base representation. Store the window in *RESULT. */ 5034static svn_error_t * 5035get_combined_window(svn_stringbuf_t **result, 5036 struct rep_read_baton *rb) 5037{ 5038 apr_pool_t *pool, *new_pool, *window_pool; 5039 int i; 5040 svn_txdelta_window_t *window; 5041 apr_array_header_t *windows; 5042 svn_stringbuf_t *source, *buf = rb->base_window; 5043 struct rep_state *rs; 5044 5045 /* Read all windows that we need to combine. This is fine because 5046 the size of each window is relatively small (100kB) and skip- 5047 delta limits the number of deltas in a chain to well under 100. 5048 Stop early if one of them does not depend on its predecessors. */ 5049 window_pool = svn_pool_create(rb->pool); 5050 windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *)); 5051 for (i = 0; i < rb->rs_list->nelts; ++i) 5052 { 5053 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5054 SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool)); 5055 5056 APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window; 5057 if (window->src_ops == 0) 5058 { 5059 ++i; 5060 break; 5061 } 5062 } 5063 5064 /* Combine in the windows from the other delta reps. */ 5065 pool = svn_pool_create(rb->pool); 5066 for (--i; i >= 0; --i) 5067 { 5068 5069 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5070 window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *); 5071 5072 /* Maybe, we've got a PLAIN start representation. If we do, read 5073 as much data from it as the needed for the txdelta window's source 5074 view. 5075 Note that BUF / SOURCE may only be NULL in the first iteration. 5076 Also note that we may have short-cut reading the delta chain -- 5077 in which case SRC_OPS is 0 and it might not be a PLAIN rep. */ 5078 source = buf; 5079 if (source == NULL && rb->src_state != NULL && window->src_ops) 5080 SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len, 5081 pool)); 5082 5083 /* Combine this window with the current one. */ 5084 new_pool = svn_pool_create(rb->pool); 5085 buf = svn_stringbuf_create_ensure(window->tview_len, new_pool); 5086 buf->len = window->tview_len; 5087 5088 svn_txdelta_apply_instructions(window, source ? source->data : NULL, 5089 buf->data, &buf->len); 5090 if (buf->len != window->tview_len) 5091 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 5092 _("svndiff window length is " 5093 "corrupt")); 5094 5095 /* Cache windows only if the whole rep content could be read as a 5096 single chunk. Only then will no other chunk need a deeper RS 5097 list than the cached chunk. */ 5098 if ((rb->chunk_index == 0) && (rs->off == rs->end)) 5099 SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool)); 5100 5101 /* Cycle pools so that we only need to hold three windows at a time. */ 5102 svn_pool_destroy(pool); 5103 pool = new_pool; 5104 } 5105 5106 svn_pool_destroy(window_pool); 5107 5108 *result = buf; 5109 return SVN_NO_ERROR; 5110} 5111 5112/* Returns whether or not the expanded fulltext of the file is cachable 5113 * based on its size SIZE. The decision depends on the cache used by RB. 5114 */ 5115static svn_boolean_t 5116fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size) 5117{ 5118 return (size < APR_SIZE_MAX) 5119 && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size); 5120} 5121 5122/* Close method used on streams returned by read_representation(). 5123 */ 5124static svn_error_t * 5125rep_read_contents_close(void *baton) 5126{ 5127 struct rep_read_baton *rb = baton; 5128 5129 svn_pool_destroy(rb->pool); 5130 svn_pool_destroy(rb->filehandle_pool); 5131 5132 return SVN_NO_ERROR; 5133} 5134 5135/* Return the next *LEN bytes of the rep and store them in *BUF. */ 5136static svn_error_t * 5137get_contents(struct rep_read_baton *rb, 5138 char *buf, 5139 apr_size_t *len) 5140{ 5141 apr_size_t copy_len, remaining = *len; 5142 char *cur = buf; 5143 struct rep_state *rs; 5144 5145 /* Special case for when there are no delta reps, only a plain 5146 text. */ 5147 if (rb->rs_list->nelts == 0) 5148 { 5149 copy_len = remaining; 5150 rs = rb->src_state; 5151 5152 if (rb->base_window != NULL) 5153 { 5154 /* We got the desired rep directly from the cache. 5155 This is where we need the pseudo rep_state created 5156 by build_rep_list(). */ 5157 apr_size_t offset = (apr_size_t)(rs->off - rs->start); 5158 if (copy_len + offset > rb->base_window->len) 5159 copy_len = offset < rb->base_window->len 5160 ? rb->base_window->len - offset 5161 : 0ul; 5162 5163 memcpy (cur, rb->base_window->data + offset, copy_len); 5164 } 5165 else 5166 { 5167 if (((apr_off_t) copy_len) > rs->end - rs->off) 5168 copy_len = (apr_size_t) (rs->end - rs->off); 5169 SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL, 5170 NULL, rb->pool)); 5171 } 5172 5173 rs->off += copy_len; 5174 *len = copy_len; 5175 return SVN_NO_ERROR; 5176 } 5177 5178 while (remaining > 0) 5179 { 5180 /* If we have buffered data from a previous chunk, use that. */ 5181 if (rb->buf) 5182 { 5183 /* Determine how much to copy from the buffer. */ 5184 copy_len = rb->buf_len - rb->buf_pos; 5185 if (copy_len > remaining) 5186 copy_len = remaining; 5187 5188 /* Actually copy the data. */ 5189 memcpy(cur, rb->buf + rb->buf_pos, copy_len); 5190 rb->buf_pos += copy_len; 5191 cur += copy_len; 5192 remaining -= copy_len; 5193 5194 /* If the buffer is all used up, clear it and empty the 5195 local pool. */ 5196 if (rb->buf_pos == rb->buf_len) 5197 { 5198 svn_pool_clear(rb->pool); 5199 rb->buf = NULL; 5200 } 5201 } 5202 else 5203 { 5204 svn_stringbuf_t *sbuf = NULL; 5205 5206 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *); 5207 if (rs->off == rs->end) 5208 break; 5209 5210 /* Get more buffered data by evaluating a chunk. */ 5211 SVN_ERR(get_combined_window(&sbuf, rb)); 5212 5213 rb->chunk_index++; 5214 rb->buf_len = sbuf->len; 5215 rb->buf = sbuf->data; 5216 rb->buf_pos = 0; 5217 } 5218 } 5219 5220 *len = cur - buf; 5221 5222 return SVN_NO_ERROR; 5223} 5224 5225/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the 5226 representation and store them in *BUF. Sum as we read and verify 5227 the MD5 sum at the end. */ 5228static svn_error_t * 5229rep_read_contents(void *baton, 5230 char *buf, 5231 apr_size_t *len) 5232{ 5233 struct rep_read_baton *rb = baton; 5234 5235 /* Get the next block of data. */ 5236 SVN_ERR(get_contents(rb, buf, len)); 5237 5238 if (rb->current_fulltext) 5239 svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); 5240 5241 /* Perform checksumming. We want to check the checksum as soon as 5242 the last byte of data is read, in case the caller never performs 5243 a short read, but we don't want to finalize the MD5 context 5244 twice. */ 5245 if (!rb->checksum_finalized) 5246 { 5247 SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len)); 5248 rb->off += *len; 5249 if (rb->off == rb->len) 5250 { 5251 svn_checksum_t *md5_checksum; 5252 5253 rb->checksum_finalized = TRUE; 5254 SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx, 5255 rb->pool)); 5256 if (!svn_checksum_match(md5_checksum, rb->md5_checksum)) 5257 return svn_error_create(SVN_ERR_FS_CORRUPT, 5258 svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum, 5259 rb->pool, 5260 _("Checksum mismatch while reading representation")), 5261 NULL); 5262 } 5263 } 5264 5265 if (rb->off == rb->len && rb->current_fulltext) 5266 { 5267 fs_fs_data_t *ffd = rb->fs->fsap_data; 5268 SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key, 5269 rb->current_fulltext, rb->pool)); 5270 rb->current_fulltext = NULL; 5271 } 5272 5273 return SVN_NO_ERROR; 5274} 5275 5276 5277/* Return a stream in *CONTENTS_P that will read the contents of a 5278 representation stored at the location given by REP. Appropriate 5279 for any kind of immutable representation, but only for file 5280 contents (not props or directory contents) in mutable 5281 representations. 5282 5283 If REP is NULL, the representation is assumed to be empty, and the 5284 empty stream is returned. 5285*/ 5286static svn_error_t * 5287read_representation(svn_stream_t **contents_p, 5288 svn_fs_t *fs, 5289 representation_t *rep, 5290 apr_pool_t *pool) 5291{ 5292 if (! rep) 5293 { 5294 *contents_p = svn_stream_empty(pool); 5295 } 5296 else 5297 { 5298 fs_fs_data_t *ffd = fs->fsap_data; 5299 pair_cache_key_t fulltext_cache_key = { 0 }; 5300 svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size; 5301 struct rep_read_baton *rb; 5302 5303 fulltext_cache_key.revision = rep->revision; 5304 fulltext_cache_key.second = rep->offset; 5305 if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5306 && fulltext_size_is_cachable(ffd, len)) 5307 { 5308 svn_stringbuf_t *fulltext; 5309 svn_boolean_t is_cached; 5310 SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached, 5311 ffd->fulltext_cache, &fulltext_cache_key, 5312 pool)); 5313 if (is_cached) 5314 { 5315 *contents_p = svn_stream_from_stringbuf(fulltext, pool); 5316 return SVN_NO_ERROR; 5317 } 5318 } 5319 else 5320 fulltext_cache_key.revision = SVN_INVALID_REVNUM; 5321 5322 SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool)); 5323 5324 *contents_p = svn_stream_create(rb, pool); 5325 svn_stream_set_read(*contents_p, rep_read_contents); 5326 svn_stream_set_close(*contents_p, rep_read_contents_close); 5327 } 5328 5329 return SVN_NO_ERROR; 5330} 5331 5332svn_error_t * 5333svn_fs_fs__get_contents(svn_stream_t **contents_p, 5334 svn_fs_t *fs, 5335 node_revision_t *noderev, 5336 apr_pool_t *pool) 5337{ 5338 return read_representation(contents_p, fs, noderev->data_rep, pool); 5339} 5340 5341/* Baton used when reading delta windows. */ 5342struct delta_read_baton 5343{ 5344 struct rep_state *rs; 5345 svn_checksum_t *checksum; 5346}; 5347 5348/* This implements the svn_txdelta_next_window_fn_t interface. */ 5349static svn_error_t * 5350delta_read_next_window(svn_txdelta_window_t **window, void *baton, 5351 apr_pool_t *pool) 5352{ 5353 struct delta_read_baton *drb = baton; 5354 5355 if (drb->rs->off == drb->rs->end) 5356 { 5357 *window = NULL; 5358 return SVN_NO_ERROR; 5359 } 5360 5361 return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool); 5362} 5363 5364/* This implements the svn_txdelta_md5_digest_fn_t interface. */ 5365static const unsigned char * 5366delta_read_md5_digest(void *baton) 5367{ 5368 struct delta_read_baton *drb = baton; 5369 5370 if (drb->checksum->kind == svn_checksum_md5) 5371 return drb->checksum->digest; 5372 else 5373 return NULL; 5374} 5375 5376svn_error_t * 5377svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p, 5378 svn_fs_t *fs, 5379 node_revision_t *source, 5380 node_revision_t *target, 5381 apr_pool_t *pool) 5382{ 5383 svn_stream_t *source_stream, *target_stream; 5384 5385 /* Try a shortcut: if the target is stored as a delta against the source, 5386 then just use that delta. */ 5387 if (source && source->data_rep && target->data_rep) 5388 { 5389 struct rep_state *rep_state; 5390 struct rep_args *rep_args; 5391 5392 /* Read target's base rep if any. */ 5393 SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL, 5394 target->data_rep, fs, pool)); 5395 5396 /* If that matches source, then use this delta as is. 5397 Note that we want an actual delta here. E.g. a self-delta would 5398 not be good enough. */ 5399 if (rep_args->is_delta 5400 && rep_args->base_revision == source->data_rep->revision 5401 && rep_args->base_offset == source->data_rep->offset) 5402 { 5403 /* Create the delta read baton. */ 5404 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb)); 5405 drb->rs = rep_state; 5406 drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum, 5407 pool); 5408 *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window, 5409 delta_read_md5_digest, pool); 5410 return SVN_NO_ERROR; 5411 } 5412 else 5413 SVN_ERR(svn_io_file_close(rep_state->file, pool)); 5414 } 5415 5416 /* Read both fulltexts and construct a delta. */ 5417 if (source) 5418 SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool)); 5419 else 5420 source_stream = svn_stream_empty(pool); 5421 SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool)); 5422 5423 /* Because source and target stream will already verify their content, 5424 * there is no need to do this once more. In particular if the stream 5425 * content is being fetched from cache. */ 5426 svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool); 5427 5428 return SVN_NO_ERROR; 5429} 5430 5431/* Baton for cache_access_wrapper. Wraps the original parameters of 5432 * svn_fs_fs__try_process_file_content(). 5433 */ 5434typedef struct cache_access_wrapper_baton_t 5435{ 5436 svn_fs_process_contents_func_t func; 5437 void* baton; 5438} cache_access_wrapper_baton_t; 5439 5440/* Wrapper to translate between svn_fs_process_contents_func_t and 5441 * svn_cache__partial_getter_func_t. 5442 */ 5443static svn_error_t * 5444cache_access_wrapper(void **out, 5445 const void *data, 5446 apr_size_t data_len, 5447 void *baton, 5448 apr_pool_t *pool) 5449{ 5450 cache_access_wrapper_baton_t *wrapper_baton = baton; 5451 5452 SVN_ERR(wrapper_baton->func((const unsigned char *)data, 5453 data_len - 1, /* cache adds terminating 0 */ 5454 wrapper_baton->baton, 5455 pool)); 5456 5457 /* non-NULL value to signal the calling cache that all went well */ 5458 *out = baton; 5459 5460 return SVN_NO_ERROR; 5461} 5462 5463svn_error_t * 5464svn_fs_fs__try_process_file_contents(svn_boolean_t *success, 5465 svn_fs_t *fs, 5466 node_revision_t *noderev, 5467 svn_fs_process_contents_func_t processor, 5468 void* baton, 5469 apr_pool_t *pool) 5470{ 5471 representation_t *rep = noderev->data_rep; 5472 if (rep) 5473 { 5474 fs_fs_data_t *ffd = fs->fsap_data; 5475 pair_cache_key_t fulltext_cache_key = { 0 }; 5476 5477 fulltext_cache_key.revision = rep->revision; 5478 fulltext_cache_key.second = rep->offset; 5479 if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5480 && fulltext_size_is_cachable(ffd, rep->expanded_size)) 5481 { 5482 cache_access_wrapper_baton_t wrapper_baton; 5483 void *dummy = NULL; 5484 5485 wrapper_baton.func = processor; 5486 wrapper_baton.baton = baton; 5487 return svn_cache__get_partial(&dummy, success, 5488 ffd->fulltext_cache, 5489 &fulltext_cache_key, 5490 cache_access_wrapper, 5491 &wrapper_baton, 5492 pool); 5493 } 5494 } 5495 5496 *success = FALSE; 5497 return SVN_NO_ERROR; 5498} 5499 5500/* Fetch the contents of a directory into ENTRIES. Values are stored 5501 as filename to string mappings; further conversion is necessary to 5502 convert them into svn_fs_dirent_t values. */ 5503static svn_error_t * 5504get_dir_contents(apr_hash_t *entries, 5505 svn_fs_t *fs, 5506 node_revision_t *noderev, 5507 apr_pool_t *pool) 5508{ 5509 svn_stream_t *contents; 5510 5511 if (noderev->data_rep && noderev->data_rep->txn_id) 5512 { 5513 const char *filename = path_txn_node_children(fs, noderev->id, pool); 5514 5515 /* The representation is mutable. Read the old directory 5516 contents from the mutable children file, followed by the 5517 changes we've made in this transaction. */ 5518 SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool)); 5519 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5520 SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool)); 5521 SVN_ERR(svn_stream_close(contents)); 5522 } 5523 else if (noderev->data_rep) 5524 { 5525 /* use a temporary pool for temp objects. 5526 * Also undeltify content before parsing it. Otherwise, we could only 5527 * parse it byte-by-byte. 5528 */ 5529 apr_pool_t *text_pool = svn_pool_create(pool); 5530 apr_size_t len = noderev->data_rep->expanded_size 5531 ? (apr_size_t)noderev->data_rep->expanded_size 5532 : (apr_size_t)noderev->data_rep->size; 5533 svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool); 5534 text->len = len; 5535 5536 /* The representation is immutable. Read it normally. */ 5537 SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool)); 5538 SVN_ERR(svn_stream_read(contents, text->data, &text->len)); 5539 SVN_ERR(svn_stream_close(contents)); 5540 5541 /* de-serialize hash */ 5542 contents = svn_stream_from_stringbuf(text, text_pool); 5543 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5544 5545 svn_pool_destroy(text_pool); 5546 } 5547 5548 return SVN_NO_ERROR; 5549} 5550 5551 5552static const char * 5553unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id, 5554 apr_pool_t *pool) 5555{ 5556 return apr_psprintf(pool, "%s %s", 5557 (kind == svn_node_file) ? KIND_FILE : KIND_DIR, 5558 svn_fs_fs__id_unparse(id, pool)->data); 5559} 5560 5561/* Given a hash ENTRIES of dirent structions, return a hash in 5562 *STR_ENTRIES_P, that has svn_string_t as the values in the format 5563 specified by the fs_fs directory contents file. Perform 5564 allocations in POOL. */ 5565static svn_error_t * 5566unparse_dir_entries(apr_hash_t **str_entries_p, 5567 apr_hash_t *entries, 5568 apr_pool_t *pool) 5569{ 5570 apr_hash_index_t *hi; 5571 5572 /* For now, we use a our own hash function to ensure that we get a 5573 * (largely) stable order when serializing the data. It also gives 5574 * us some performance improvement. 5575 * 5576 * ### TODO ### 5577 * Use some sorted or other fixed order data container. 5578 */ 5579 *str_entries_p = svn_hash__make(pool); 5580 5581 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 5582 { 5583 const void *key; 5584 apr_ssize_t klen; 5585 svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi); 5586 const char *new_val; 5587 5588 apr_hash_this(hi, &key, &klen, NULL); 5589 new_val = unparse_dir_entry(dirent->kind, dirent->id, pool); 5590 apr_hash_set(*str_entries_p, key, klen, 5591 svn_string_create(new_val, pool)); 5592 } 5593 5594 return SVN_NO_ERROR; 5595} 5596 5597 5598/* Given a hash STR_ENTRIES with values as svn_string_t as specified 5599 in an FSFS directory contents listing, return a hash of dirents in 5600 *ENTRIES_P. Perform allocations in POOL. */ 5601static svn_error_t * 5602parse_dir_entries(apr_hash_t **entries_p, 5603 apr_hash_t *str_entries, 5604 const char *unparsed_id, 5605 apr_pool_t *pool) 5606{ 5607 apr_hash_index_t *hi; 5608 5609 *entries_p = apr_hash_make(pool); 5610 5611 /* Translate the string dir entries into real entries. */ 5612 for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi)) 5613 { 5614 const char *name = svn__apr_hash_index_key(hi); 5615 svn_string_t *str_val = svn__apr_hash_index_val(hi); 5616 char *str, *last_str; 5617 svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent)); 5618 5619 last_str = apr_pstrdup(pool, str_val->data); 5620 dirent->name = apr_pstrdup(pool, name); 5621 5622 str = svn_cstring_tokenize(" ", &last_str); 5623 if (str == NULL) 5624 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5625 _("Directory entry corrupt in '%s'"), 5626 unparsed_id); 5627 5628 if (strcmp(str, KIND_FILE) == 0) 5629 { 5630 dirent->kind = svn_node_file; 5631 } 5632 else if (strcmp(str, KIND_DIR) == 0) 5633 { 5634 dirent->kind = svn_node_dir; 5635 } 5636 else 5637 { 5638 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5639 _("Directory entry corrupt in '%s'"), 5640 unparsed_id); 5641 } 5642 5643 str = svn_cstring_tokenize(" ", &last_str); 5644 if (str == NULL) 5645 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5646 _("Directory entry corrupt in '%s'"), 5647 unparsed_id); 5648 5649 dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool); 5650 5651 svn_hash_sets(*entries_p, dirent->name, dirent); 5652 } 5653 5654 return SVN_NO_ERROR; 5655} 5656 5657/* Return the cache object in FS responsible to storing the directory 5658 * the NODEREV. If none exists, return NULL. */ 5659static svn_cache__t * 5660locate_dir_cache(svn_fs_t *fs, 5661 node_revision_t *noderev) 5662{ 5663 fs_fs_data_t *ffd = fs->fsap_data; 5664 return svn_fs_fs__id_txn_id(noderev->id) 5665 ? ffd->txn_dir_cache 5666 : ffd->dir_cache; 5667} 5668 5669svn_error_t * 5670svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p, 5671 svn_fs_t *fs, 5672 node_revision_t *noderev, 5673 apr_pool_t *pool) 5674{ 5675 const char *unparsed_id = NULL; 5676 apr_hash_t *unparsed_entries, *parsed_entries; 5677 5678 /* find the cache we may use */ 5679 svn_cache__t *cache = locate_dir_cache(fs, noderev); 5680 if (cache) 5681 { 5682 svn_boolean_t found; 5683 5684 unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data; 5685 SVN_ERR(svn_cache__get((void **) entries_p, &found, cache, 5686 unparsed_id, pool)); 5687 if (found) 5688 return SVN_NO_ERROR; 5689 } 5690 5691 /* Read in the directory hash. */ 5692 unparsed_entries = apr_hash_make(pool); 5693 SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool)); 5694 SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries, 5695 unparsed_id, pool)); 5696 5697 /* Update the cache, if we are to use one. */ 5698 if (cache) 5699 SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool)); 5700 5701 *entries_p = parsed_entries; 5702 return SVN_NO_ERROR; 5703} 5704 5705svn_error_t * 5706svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent, 5707 svn_fs_t *fs, 5708 node_revision_t *noderev, 5709 const char *name, 5710 apr_pool_t *result_pool, 5711 apr_pool_t *scratch_pool) 5712{ 5713 svn_boolean_t found = FALSE; 5714 5715 /* find the cache we may use */ 5716 svn_cache__t *cache = locate_dir_cache(fs, noderev); 5717 if (cache) 5718 { 5719 const char *unparsed_id = 5720 svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data; 5721 5722 /* Cache lookup. */ 5723 SVN_ERR(svn_cache__get_partial((void **)dirent, 5724 &found, 5725 cache, 5726 unparsed_id, 5727 svn_fs_fs__extract_dir_entry, 5728 (void*)name, 5729 result_pool)); 5730 } 5731 5732 /* fetch data from disk if we did not find it in the cache */ 5733 if (! found) 5734 { 5735 apr_hash_t *entries; 5736 svn_fs_dirent_t *entry; 5737 svn_fs_dirent_t *entry_copy = NULL; 5738 5739 /* read the dir from the file system. It will probably be put it 5740 into the cache for faster lookup in future calls. */ 5741 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, 5742 scratch_pool)); 5743 5744 /* find desired entry and return a copy in POOL, if found */ 5745 entry = svn_hash_gets(entries, name); 5746 if (entry != NULL) 5747 { 5748 entry_copy = apr_palloc(result_pool, sizeof(*entry_copy)); 5749 entry_copy->name = apr_pstrdup(result_pool, entry->name); 5750 entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool); 5751 entry_copy->kind = entry->kind; 5752 } 5753 5754 *dirent = entry_copy; 5755 } 5756 5757 return SVN_NO_ERROR; 5758} 5759 5760svn_error_t * 5761svn_fs_fs__get_proplist(apr_hash_t **proplist_p, 5762 svn_fs_t *fs, 5763 node_revision_t *noderev, 5764 apr_pool_t *pool) 5765{ 5766 apr_hash_t *proplist; 5767 svn_stream_t *stream; 5768 5769 if (noderev->prop_rep && noderev->prop_rep->txn_id) 5770 { 5771 const char *filename = path_txn_node_props(fs, noderev->id, pool); 5772 proplist = apr_hash_make(pool); 5773 5774 SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool)); 5775 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5776 SVN_ERR(svn_stream_close(stream)); 5777 } 5778 else if (noderev->prop_rep) 5779 { 5780 fs_fs_data_t *ffd = fs->fsap_data; 5781 representation_t *rep = noderev->prop_rep; 5782 pair_cache_key_t key = { 0 }; 5783 5784 key.revision = rep->revision; 5785 key.second = rep->offset; 5786 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5787 { 5788 svn_boolean_t is_cached; 5789 SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 5790 ffd->properties_cache, &key, pool)); 5791 if (is_cached) 5792 return SVN_NO_ERROR; 5793 } 5794 5795 proplist = apr_hash_make(pool); 5796 SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool)); 5797 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5798 SVN_ERR(svn_stream_close(stream)); 5799 5800 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5801 SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool)); 5802 } 5803 else 5804 { 5805 /* return an empty prop list if the node doesn't have any props */ 5806 proplist = apr_hash_make(pool); 5807 } 5808 5809 *proplist_p = proplist; 5810 5811 return SVN_NO_ERROR; 5812} 5813 5814svn_error_t * 5815svn_fs_fs__file_length(svn_filesize_t *length, 5816 node_revision_t *noderev, 5817 apr_pool_t *pool) 5818{ 5819 if (noderev->data_rep) 5820 *length = noderev->data_rep->expanded_size; 5821 else 5822 *length = 0; 5823 5824 return SVN_NO_ERROR; 5825} 5826 5827svn_boolean_t 5828svn_fs_fs__noderev_same_rep_key(representation_t *a, 5829 representation_t *b) 5830{ 5831 if (a == b) 5832 return TRUE; 5833 5834 if (a == NULL || b == NULL) 5835 return FALSE; 5836 5837 if (a->offset != b->offset) 5838 return FALSE; 5839 5840 if (a->revision != b->revision) 5841 return FALSE; 5842 5843 if (a->uniquifier == b->uniquifier) 5844 return TRUE; 5845 5846 if (a->uniquifier == NULL || b->uniquifier == NULL) 5847 return FALSE; 5848 5849 return strcmp(a->uniquifier, b->uniquifier) == 0; 5850} 5851 5852svn_error_t * 5853svn_fs_fs__file_checksum(svn_checksum_t **checksum, 5854 node_revision_t *noderev, 5855 svn_checksum_kind_t kind, 5856 apr_pool_t *pool) 5857{ 5858 if (noderev->data_rep) 5859 { 5860 switch(kind) 5861 { 5862 case svn_checksum_md5: 5863 *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum, 5864 pool); 5865 break; 5866 case svn_checksum_sha1: 5867 *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum, 5868 pool); 5869 break; 5870 default: 5871 *checksum = NULL; 5872 } 5873 } 5874 else 5875 *checksum = NULL; 5876 5877 return SVN_NO_ERROR; 5878} 5879 5880representation_t * 5881svn_fs_fs__rep_copy(representation_t *rep, 5882 apr_pool_t *pool) 5883{ 5884 representation_t *rep_new; 5885 5886 if (rep == NULL) 5887 return NULL; 5888 5889 rep_new = apr_pcalloc(pool, sizeof(*rep_new)); 5890 5891 memcpy(rep_new, rep, sizeof(*rep_new)); 5892 rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 5893 rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool); 5894 rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier); 5895 5896 return rep_new; 5897} 5898 5899/* Merge the internal-use-only CHANGE into a hash of public-FS 5900 svn_fs_path_change2_t CHANGES, collapsing multiple changes into a 5901 single summarical (is that real word?) change per path. Also keep 5902 the COPYFROM_CACHE up to date with new adds and replaces. */ 5903static svn_error_t * 5904fold_change(apr_hash_t *changes, 5905 const change_t *change, 5906 apr_hash_t *copyfrom_cache) 5907{ 5908 apr_pool_t *pool = apr_hash_pool_get(changes); 5909 svn_fs_path_change2_t *old_change, *new_change; 5910 const char *path; 5911 apr_size_t path_len = strlen(change->path); 5912 5913 if ((old_change = apr_hash_get(changes, change->path, path_len))) 5914 { 5915 /* This path already exists in the hash, so we have to merge 5916 this change into the already existing one. */ 5917 5918 /* Sanity check: only allow NULL node revision ID in the 5919 `reset' case. */ 5920 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset)) 5921 return svn_error_create 5922 (SVN_ERR_FS_CORRUPT, NULL, 5923 _("Missing required node revision ID")); 5924 5925 /* Sanity check: we should be talking about the same node 5926 revision ID as our last change except where the last change 5927 was a deletion. */ 5928 if (change->noderev_id 5929 && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id)) 5930 && (old_change->change_kind != svn_fs_path_change_delete)) 5931 return svn_error_create 5932 (SVN_ERR_FS_CORRUPT, NULL, 5933 _("Invalid change ordering: new node revision ID " 5934 "without delete")); 5935 5936 /* Sanity check: an add, replacement, or reset must be the first 5937 thing to follow a deletion. */ 5938 if ((old_change->change_kind == svn_fs_path_change_delete) 5939 && (! ((change->kind == svn_fs_path_change_replace) 5940 || (change->kind == svn_fs_path_change_reset) 5941 || (change->kind == svn_fs_path_change_add)))) 5942 return svn_error_create 5943 (SVN_ERR_FS_CORRUPT, NULL, 5944 _("Invalid change ordering: non-add change on deleted path")); 5945 5946 /* Sanity check: an add can't follow anything except 5947 a delete or reset. */ 5948 if ((change->kind == svn_fs_path_change_add) 5949 && (old_change->change_kind != svn_fs_path_change_delete) 5950 && (old_change->change_kind != svn_fs_path_change_reset)) 5951 return svn_error_create 5952 (SVN_ERR_FS_CORRUPT, NULL, 5953 _("Invalid change ordering: add change on preexisting path")); 5954 5955 /* Now, merge that change in. */ 5956 switch (change->kind) 5957 { 5958 case svn_fs_path_change_reset: 5959 /* A reset here will simply remove the path change from the 5960 hash. */ 5961 old_change = NULL; 5962 break; 5963 5964 case svn_fs_path_change_delete: 5965 if (old_change->change_kind == svn_fs_path_change_add) 5966 { 5967 /* If the path was introduced in this transaction via an 5968 add, and we are deleting it, just remove the path 5969 altogether. */ 5970 old_change = NULL; 5971 } 5972 else 5973 { 5974 /* A deletion overrules all previous changes. */ 5975 old_change->change_kind = svn_fs_path_change_delete; 5976 old_change->text_mod = change->text_mod; 5977 old_change->prop_mod = change->prop_mod; 5978 old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5979 old_change->copyfrom_path = NULL; 5980 } 5981 break; 5982 5983 case svn_fs_path_change_add: 5984 case svn_fs_path_change_replace: 5985 /* An add at this point must be following a previous delete, 5986 so treat it just like a replace. */ 5987 old_change->change_kind = svn_fs_path_change_replace; 5988 old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, 5989 pool); 5990 old_change->text_mod = change->text_mod; 5991 old_change->prop_mod = change->prop_mod; 5992 if (change->copyfrom_rev == SVN_INVALID_REVNUM) 5993 { 5994 old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5995 old_change->copyfrom_path = NULL; 5996 } 5997 else 5998 { 5999 old_change->copyfrom_rev = change->copyfrom_rev; 6000 old_change->copyfrom_path = apr_pstrdup(pool, 6001 change->copyfrom_path); 6002 } 6003 break; 6004 6005 case svn_fs_path_change_modify: 6006 default: 6007 if (change->text_mod) 6008 old_change->text_mod = TRUE; 6009 if (change->prop_mod) 6010 old_change->prop_mod = TRUE; 6011 break; 6012 } 6013 6014 /* Point our new_change to our (possibly modified) old_change. */ 6015 new_change = old_change; 6016 } 6017 else 6018 { 6019 /* This change is new to the hash, so make a new public change 6020 structure from the internal one (in the hash's pool), and dup 6021 the path into the hash's pool, too. */ 6022 new_change = apr_pcalloc(pool, sizeof(*new_change)); 6023 new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool); 6024 new_change->change_kind = change->kind; 6025 new_change->text_mod = change->text_mod; 6026 new_change->prop_mod = change->prop_mod; 6027 /* In FSFS, copyfrom_known is *always* true, since we've always 6028 * stored copyfroms in changed paths lists. */ 6029 new_change->copyfrom_known = TRUE; 6030 if (change->copyfrom_rev != SVN_INVALID_REVNUM) 6031 { 6032 new_change->copyfrom_rev = change->copyfrom_rev; 6033 new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path); 6034 } 6035 else 6036 { 6037 new_change->copyfrom_rev = SVN_INVALID_REVNUM; 6038 new_change->copyfrom_path = NULL; 6039 } 6040 } 6041 6042 if (new_change) 6043 new_change->node_kind = change->node_kind; 6044 6045 /* Add (or update) this path. 6046 6047 Note: this key might already be present, and it would be nice to 6048 re-use its value, but there is no way to fetch it. The API makes no 6049 guarantees that this (new) key will not be retained. Thus, we (again) 6050 copy the key into the target pool to ensure a proper lifetime. */ 6051 path = apr_pstrmemdup(pool, change->path, path_len); 6052 apr_hash_set(changes, path, path_len, new_change); 6053 6054 /* Update the copyfrom cache, if any. */ 6055 if (copyfrom_cache) 6056 { 6057 apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache); 6058 const char *copyfrom_string = NULL, *copyfrom_key = path; 6059 if (new_change) 6060 { 6061 if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev)) 6062 copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s", 6063 new_change->copyfrom_rev, 6064 new_change->copyfrom_path); 6065 else 6066 copyfrom_string = ""; 6067 } 6068 /* We need to allocate a copy of the key in the copyfrom_pool if 6069 * we're not doing a deletion and if it isn't already there. */ 6070 if ( copyfrom_string 6071 && ( ! apr_hash_count(copyfrom_cache) 6072 || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len))) 6073 copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len); 6074 6075 apr_hash_set(copyfrom_cache, copyfrom_key, path_len, 6076 copyfrom_string); 6077 } 6078 6079 return SVN_NO_ERROR; 6080} 6081 6082/* The 256 is an arbitrary size large enough to hold the node id and the 6083 * various flags. */ 6084#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256 6085 6086/* Read the next entry in the changes record from file FILE and store 6087 the resulting change in *CHANGE_P. If there is no next record, 6088 store NULL there. Perform all allocations from POOL. */ 6089static svn_error_t * 6090read_change(change_t **change_p, 6091 apr_file_t *file, 6092 apr_pool_t *pool) 6093{ 6094 char buf[MAX_CHANGE_LINE_LEN]; 6095 apr_size_t len = sizeof(buf); 6096 change_t *change; 6097 char *str, *last_str = buf, *kind_str; 6098 svn_error_t *err; 6099 6100 /* Default return value. */ 6101 *change_p = NULL; 6102 6103 err = svn_io_read_length_line(file, buf, &len, pool); 6104 6105 /* Check for a blank line. */ 6106 if (err || (len == 0)) 6107 { 6108 if (err && APR_STATUS_IS_EOF(err->apr_err)) 6109 { 6110 svn_error_clear(err); 6111 return SVN_NO_ERROR; 6112 } 6113 if ((len == 0) && (! err)) 6114 return SVN_NO_ERROR; 6115 return svn_error_trace(err); 6116 } 6117 6118 change = apr_pcalloc(pool, sizeof(*change)); 6119 6120 /* Get the node-id of the change. */ 6121 str = svn_cstring_tokenize(" ", &last_str); 6122 if (str == NULL) 6123 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6124 _("Invalid changes line in rev-file")); 6125 6126 change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool); 6127 if (change->noderev_id == NULL) 6128 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6129 _("Invalid changes line in rev-file")); 6130 6131 /* Get the change type. */ 6132 str = svn_cstring_tokenize(" ", &last_str); 6133 if (str == NULL) 6134 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6135 _("Invalid changes line in rev-file")); 6136 6137 /* Don't bother to check the format number before looking for 6138 * node-kinds: just read them if you find them. */ 6139 change->node_kind = svn_node_unknown; 6140 kind_str = strchr(str, '-'); 6141 if (kind_str) 6142 { 6143 /* Cap off the end of "str" (the action). */ 6144 *kind_str = '\0'; 6145 kind_str++; 6146 if (strcmp(kind_str, KIND_FILE) == 0) 6147 change->node_kind = svn_node_file; 6148 else if (strcmp(kind_str, KIND_DIR) == 0) 6149 change->node_kind = svn_node_dir; 6150 else 6151 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6152 _("Invalid changes line in rev-file")); 6153 } 6154 6155 if (strcmp(str, ACTION_MODIFY) == 0) 6156 { 6157 change->kind = svn_fs_path_change_modify; 6158 } 6159 else if (strcmp(str, ACTION_ADD) == 0) 6160 { 6161 change->kind = svn_fs_path_change_add; 6162 } 6163 else if (strcmp(str, ACTION_DELETE) == 0) 6164 { 6165 change->kind = svn_fs_path_change_delete; 6166 } 6167 else if (strcmp(str, ACTION_REPLACE) == 0) 6168 { 6169 change->kind = svn_fs_path_change_replace; 6170 } 6171 else if (strcmp(str, ACTION_RESET) == 0) 6172 { 6173 change->kind = svn_fs_path_change_reset; 6174 } 6175 else 6176 { 6177 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6178 _("Invalid change kind in rev file")); 6179 } 6180 6181 /* Get the text-mod flag. */ 6182 str = svn_cstring_tokenize(" ", &last_str); 6183 if (str == NULL) 6184 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6185 _("Invalid changes line in rev-file")); 6186 6187 if (strcmp(str, FLAG_TRUE) == 0) 6188 { 6189 change->text_mod = TRUE; 6190 } 6191 else if (strcmp(str, FLAG_FALSE) == 0) 6192 { 6193 change->text_mod = FALSE; 6194 } 6195 else 6196 { 6197 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6198 _("Invalid text-mod flag in rev-file")); 6199 } 6200 6201 /* Get the prop-mod flag. */ 6202 str = svn_cstring_tokenize(" ", &last_str); 6203 if (str == NULL) 6204 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6205 _("Invalid changes line in rev-file")); 6206 6207 if (strcmp(str, FLAG_TRUE) == 0) 6208 { 6209 change->prop_mod = TRUE; 6210 } 6211 else if (strcmp(str, FLAG_FALSE) == 0) 6212 { 6213 change->prop_mod = FALSE; 6214 } 6215 else 6216 { 6217 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6218 _("Invalid prop-mod flag in rev-file")); 6219 } 6220 6221 /* Get the changed path. */ 6222 change->path = apr_pstrdup(pool, last_str); 6223 6224 6225 /* Read the next line, the copyfrom line. */ 6226 len = sizeof(buf); 6227 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 6228 6229 if (len == 0) 6230 { 6231 change->copyfrom_rev = SVN_INVALID_REVNUM; 6232 change->copyfrom_path = NULL; 6233 } 6234 else 6235 { 6236 last_str = buf; 6237 str = svn_cstring_tokenize(" ", &last_str); 6238 if (! str) 6239 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6240 _("Invalid changes line in rev-file")); 6241 change->copyfrom_rev = SVN_STR_TO_REV(str); 6242 6243 if (! last_str) 6244 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6245 _("Invalid changes line in rev-file")); 6246 6247 change->copyfrom_path = apr_pstrdup(pool, last_str); 6248 } 6249 6250 *change_p = change; 6251 6252 return SVN_NO_ERROR; 6253} 6254 6255/* Examine all the changed path entries in CHANGES and store them in 6256 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary 6257 *data. Store a hash of paths to copyfrom "REV PATH" strings in 6258 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that 6259 the changed-path entries have already been folded (by 6260 write_final_changed_path_info) and may be out of order, so we shouldn't 6261 remove children of replaced or deleted directories. Do all 6262 allocations in POOL. */ 6263static svn_error_t * 6264process_changes(apr_hash_t *changed_paths, 6265 apr_hash_t *copyfrom_cache, 6266 apr_array_header_t *changes, 6267 svn_boolean_t prefolded, 6268 apr_pool_t *pool) 6269{ 6270 apr_pool_t *iterpool = svn_pool_create(pool); 6271 int i; 6272 6273 /* Read in the changes one by one, folding them into our local hash 6274 as necessary. */ 6275 6276 for (i = 0; i < changes->nelts; ++i) 6277 { 6278 change_t *change = APR_ARRAY_IDX(changes, i, change_t *); 6279 6280 SVN_ERR(fold_change(changed_paths, change, copyfrom_cache)); 6281 6282 /* Now, if our change was a deletion or replacement, we have to 6283 blow away any changes thus far on paths that are (or, were) 6284 children of this path. 6285 ### i won't bother with another iteration pool here -- at 6286 most we talking about a few extra dups of paths into what 6287 is already a temporary subpool. 6288 */ 6289 6290 if (((change->kind == svn_fs_path_change_delete) 6291 || (change->kind == svn_fs_path_change_replace)) 6292 && ! prefolded) 6293 { 6294 apr_hash_index_t *hi; 6295 6296 /* a potential child path must contain at least 2 more chars 6297 (the path separator plus at least one char for the name). 6298 Also, we should not assume that all paths have been normalized 6299 i.e. some might have trailing path separators. 6300 */ 6301 apr_ssize_t change_path_len = strlen(change->path); 6302 apr_ssize_t min_child_len = change_path_len == 0 6303 ? 1 6304 : change->path[change_path_len-1] == '/' 6305 ? change_path_len + 1 6306 : change_path_len + 2; 6307 6308 /* CAUTION: This is the inner loop of an O(n^2) algorithm. 6309 The number of changes to process may be >> 1000. 6310 Therefore, keep the inner loop as tight as possible. 6311 */ 6312 for (hi = apr_hash_first(iterpool, changed_paths); 6313 hi; 6314 hi = apr_hash_next(hi)) 6315 { 6316 /* KEY is the path. */ 6317 const void *path; 6318 apr_ssize_t klen; 6319 apr_hash_this(hi, &path, &klen, NULL); 6320 6321 /* If we come across a child of our path, remove it. 6322 Call svn_dirent_is_child only if there is a chance that 6323 this is actually a sub-path. 6324 */ 6325 if ( klen >= min_child_len 6326 && svn_dirent_is_child(change->path, path, iterpool)) 6327 apr_hash_set(changed_paths, path, klen, NULL); 6328 } 6329 } 6330 6331 /* Clear the per-iteration subpool. */ 6332 svn_pool_clear(iterpool); 6333 } 6334 6335 /* Destroy the per-iteration subpool. */ 6336 svn_pool_destroy(iterpool); 6337 6338 return SVN_NO_ERROR; 6339} 6340 6341/* Fetch all the changes from FILE and store them in *CHANGES. Do all 6342 allocations in POOL. */ 6343static svn_error_t * 6344read_all_changes(apr_array_header_t **changes, 6345 apr_file_t *file, 6346 apr_pool_t *pool) 6347{ 6348 change_t *change; 6349 6350 /* pre-allocate enough room for most change lists 6351 (will be auto-expanded as necessary) */ 6352 *changes = apr_array_make(pool, 30, sizeof(change_t *)); 6353 6354 SVN_ERR(read_change(&change, file, pool)); 6355 while (change) 6356 { 6357 APR_ARRAY_PUSH(*changes, change_t*) = change; 6358 SVN_ERR(read_change(&change, file, pool)); 6359 } 6360 6361 return SVN_NO_ERROR; 6362} 6363 6364svn_error_t * 6365svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p, 6366 svn_fs_t *fs, 6367 const char *txn_id, 6368 apr_pool_t *pool) 6369{ 6370 apr_file_t *file; 6371 apr_hash_t *changed_paths = apr_hash_make(pool); 6372 apr_array_header_t *changes; 6373 apr_pool_t *scratch_pool = svn_pool_create(pool); 6374 6375 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 6376 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6377 6378 SVN_ERR(read_all_changes(&changes, file, scratch_pool)); 6379 SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool)); 6380 svn_pool_destroy(scratch_pool); 6381 6382 SVN_ERR(svn_io_file_close(file, pool)); 6383 6384 *changed_paths_p = changed_paths; 6385 6386 return SVN_NO_ERROR; 6387} 6388 6389/* Fetch the list of change in revision REV in FS and return it in *CHANGES. 6390 * Allocate the result in POOL. 6391 */ 6392static svn_error_t * 6393get_changes(apr_array_header_t **changes, 6394 svn_fs_t *fs, 6395 svn_revnum_t rev, 6396 apr_pool_t *pool) 6397{ 6398 apr_off_t changes_offset; 6399 apr_file_t *revision_file; 6400 svn_boolean_t found; 6401 fs_fs_data_t *ffd = fs->fsap_data; 6402 6403 /* try cache lookup first */ 6404 6405 if (ffd->changes_cache) 6406 { 6407 SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache, 6408 &rev, pool)); 6409 if (found) 6410 return SVN_NO_ERROR; 6411 } 6412 6413 /* read changes from revision file */ 6414 6415 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 6416 6417 SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool)); 6418 6419 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs, 6420 rev, pool)); 6421 6422 SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool)); 6423 SVN_ERR(read_all_changes(changes, revision_file, pool)); 6424 6425 SVN_ERR(svn_io_file_close(revision_file, pool)); 6426 6427 /* cache for future reference */ 6428 6429 if (ffd->changes_cache) 6430 SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool)); 6431 6432 return SVN_NO_ERROR; 6433} 6434 6435 6436svn_error_t * 6437svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p, 6438 svn_fs_t *fs, 6439 svn_revnum_t rev, 6440 apr_hash_t *copyfrom_cache, 6441 apr_pool_t *pool) 6442{ 6443 apr_hash_t *changed_paths; 6444 apr_array_header_t *changes; 6445 apr_pool_t *scratch_pool = svn_pool_create(pool); 6446 6447 SVN_ERR(get_changes(&changes, fs, rev, scratch_pool)); 6448 6449 changed_paths = svn_hash__make(pool); 6450 6451 SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes, 6452 TRUE, pool)); 6453 svn_pool_destroy(scratch_pool); 6454 6455 *changed_paths_p = changed_paths; 6456 6457 return SVN_NO_ERROR; 6458} 6459 6460/* Copy a revision node-rev SRC into the current transaction TXN_ID in 6461 the filesystem FS. This is only used to create the root of a transaction. 6462 Allocations are from POOL. */ 6463static svn_error_t * 6464create_new_txn_noderev_from_rev(svn_fs_t *fs, 6465 const char *txn_id, 6466 svn_fs_id_t *src, 6467 apr_pool_t *pool) 6468{ 6469 node_revision_t *noderev; 6470 const char *node_id, *copy_id; 6471 6472 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool)); 6473 6474 if (svn_fs_fs__id_txn_id(noderev->id)) 6475 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6476 _("Copying from transactions not allowed")); 6477 6478 noderev->predecessor_id = noderev->id; 6479 noderev->predecessor_count++; 6480 noderev->copyfrom_path = NULL; 6481 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 6482 6483 /* For the transaction root, the copyroot never changes. */ 6484 6485 node_id = svn_fs_fs__id_node_id(noderev->id); 6486 copy_id = svn_fs_fs__id_copy_id(noderev->id); 6487 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6488 6489 return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool); 6490} 6491 6492/* A structure used by get_and_increment_txn_key_body(). */ 6493struct get_and_increment_txn_key_baton { 6494 svn_fs_t *fs; 6495 char *txn_id; 6496 apr_pool_t *pool; 6497}; 6498 6499/* Callback used in the implementation of create_txn_dir(). This gets 6500 the current base 36 value in PATH_TXN_CURRENT and increments it. 6501 It returns the original value by the baton. */ 6502static svn_error_t * 6503get_and_increment_txn_key_body(void *baton, apr_pool_t *pool) 6504{ 6505 struct get_and_increment_txn_key_baton *cb = baton; 6506 const char *txn_current_filename = path_txn_current(cb->fs, pool); 6507 const char *tmp_filename; 6508 char next_txn_id[MAX_KEY_SIZE+3]; 6509 apr_size_t len; 6510 6511 svn_stringbuf_t *buf; 6512 SVN_ERR(read_content(&buf, txn_current_filename, cb->pool)); 6513 6514 /* remove trailing newlines */ 6515 svn_stringbuf_strip_whitespace(buf); 6516 cb->txn_id = buf->data; 6517 len = buf->len; 6518 6519 /* Increment the key and add a trailing \n to the string so the 6520 txn-current file has a newline in it. */ 6521 svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id); 6522 next_txn_id[len] = '\n'; 6523 ++len; 6524 next_txn_id[len] = '\0'; 6525 6526 SVN_ERR(svn_io_write_unique(&tmp_filename, 6527 svn_dirent_dirname(txn_current_filename, pool), 6528 next_txn_id, len, svn_io_file_del_none, pool)); 6529 SVN_ERR(move_into_place(tmp_filename, txn_current_filename, 6530 txn_current_filename, pool)); 6531 6532 return SVN_NO_ERROR; 6533} 6534 6535/* Create a unique directory for a transaction in FS based on revision 6536 REV. Return the ID for this transaction in *ID_P. Use a sequence 6537 value in the transaction ID to prevent reuse of transaction IDs. */ 6538static svn_error_t * 6539create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6540 apr_pool_t *pool) 6541{ 6542 struct get_and_increment_txn_key_baton cb; 6543 const char *txn_dir; 6544 6545 /* Get the current transaction sequence value, which is a base-36 6546 number, from the txn-current file, and write an 6547 incremented value back out to the file. Place the revision 6548 number the transaction is based off into the transaction id. */ 6549 cb.pool = pool; 6550 cb.fs = fs; 6551 SVN_ERR(with_txn_current_lock(fs, 6552 get_and_increment_txn_key_body, 6553 &cb, 6554 pool)); 6555 *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id); 6556 6557 txn_dir = svn_dirent_join_many(pool, 6558 fs->path, 6559 PATH_TXNS_DIR, 6560 apr_pstrcat(pool, *id_p, PATH_EXT_TXN, 6561 (char *)NULL), 6562 NULL); 6563 6564 return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool); 6565} 6566 6567/* Create a unique directory for a transaction in FS based on revision 6568 REV. Return the ID for this transaction in *ID_P. This 6569 implementation is used in svn 1.4 and earlier repositories and is 6570 kept in 1.5 and greater to support the --pre-1.4-compatible and 6571 --pre-1.5-compatible repository creation options. Reused 6572 transaction IDs are possible with this implementation. */ 6573static svn_error_t * 6574create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6575 apr_pool_t *pool) 6576{ 6577 unsigned int i; 6578 apr_pool_t *subpool; 6579 const char *unique_path, *prefix; 6580 6581 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */ 6582 prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR, 6583 apr_psprintf(pool, "%ld", rev), NULL); 6584 6585 subpool = svn_pool_create(pool); 6586 for (i = 1; i <= 99999; i++) 6587 { 6588 svn_error_t *err; 6589 6590 svn_pool_clear(subpool); 6591 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i); 6592 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool); 6593 if (! err) 6594 { 6595 /* We succeeded. Return the basename minus the ".txn" extension. */ 6596 const char *name = svn_dirent_basename(unique_path, subpool); 6597 *id_p = apr_pstrndup(pool, name, 6598 strlen(name) - strlen(PATH_EXT_TXN)); 6599 svn_pool_destroy(subpool); 6600 return SVN_NO_ERROR; 6601 } 6602 if (! APR_STATUS_IS_EEXIST(err->apr_err)) 6603 return svn_error_trace(err); 6604 svn_error_clear(err); 6605 } 6606 6607 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 6608 NULL, 6609 _("Unable to create transaction directory " 6610 "in '%s' for revision %ld"), 6611 svn_dirent_local_style(fs->path, pool), 6612 rev); 6613} 6614 6615svn_error_t * 6616svn_fs_fs__create_txn(svn_fs_txn_t **txn_p, 6617 svn_fs_t *fs, 6618 svn_revnum_t rev, 6619 apr_pool_t *pool) 6620{ 6621 fs_fs_data_t *ffd = fs->fsap_data; 6622 svn_fs_txn_t *txn; 6623 svn_fs_id_t *root_id; 6624 6625 txn = apr_pcalloc(pool, sizeof(*txn)); 6626 6627 /* Get the txn_id. */ 6628 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 6629 SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool)); 6630 else 6631 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool)); 6632 6633 txn->fs = fs; 6634 txn->base_rev = rev; 6635 6636 txn->vtable = &txn_vtable; 6637 *txn_p = txn; 6638 6639 /* Create a new root node for this transaction. */ 6640 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool)); 6641 SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool)); 6642 6643 /* Create an empty rev file. */ 6644 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "", 6645 pool)); 6646 6647 /* Create an empty rev-lock file. */ 6648 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "", 6649 pool)); 6650 6651 /* Create an empty changes file. */ 6652 SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "", 6653 pool)); 6654 6655 /* Create the next-ids file. */ 6656 return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n", 6657 pool); 6658} 6659 6660/* Store the property list for transaction TXN_ID in PROPLIST. 6661 Perform temporary allocations in POOL. */ 6662static svn_error_t * 6663get_txn_proplist(apr_hash_t *proplist, 6664 svn_fs_t *fs, 6665 const char *txn_id, 6666 apr_pool_t *pool) 6667{ 6668 svn_stream_t *stream; 6669 6670 /* Check for issue #3696. (When we find and fix the cause, we can change 6671 * this to an assertion.) */ 6672 if (txn_id == NULL) 6673 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 6674 _("Internal error: a null transaction id was " 6675 "passed to get_txn_proplist()")); 6676 6677 /* Open the transaction properties file. */ 6678 SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool), 6679 pool, pool)); 6680 6681 /* Read in the property list. */ 6682 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 6683 6684 return svn_stream_close(stream); 6685} 6686 6687svn_error_t * 6688svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn, 6689 const char *name, 6690 const svn_string_t *value, 6691 apr_pool_t *pool) 6692{ 6693 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t)); 6694 svn_prop_t prop; 6695 6696 prop.name = name; 6697 prop.value = value; 6698 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 6699 6700 return svn_fs_fs__change_txn_props(txn, props, pool); 6701} 6702 6703svn_error_t * 6704svn_fs_fs__change_txn_props(svn_fs_txn_t *txn, 6705 const apr_array_header_t *props, 6706 apr_pool_t *pool) 6707{ 6708 const char *txn_prop_filename; 6709 svn_stringbuf_t *buf; 6710 svn_stream_t *stream; 6711 apr_hash_t *txn_prop = apr_hash_make(pool); 6712 int i; 6713 svn_error_t *err; 6714 6715 err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool); 6716 /* Here - and here only - we need to deal with the possibility that the 6717 transaction property file doesn't yet exist. The rest of the 6718 implementation assumes that the file exists, but we're called to set the 6719 initial transaction properties as the transaction is being created. */ 6720 if (err && (APR_STATUS_IS_ENOENT(err->apr_err))) 6721 svn_error_clear(err); 6722 else if (err) 6723 return svn_error_trace(err); 6724 6725 for (i = 0; i < props->nelts; i++) 6726 { 6727 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 6728 6729 svn_hash_sets(txn_prop, prop->name, prop->value); 6730 } 6731 6732 /* Create a new version of the file and write out the new props. */ 6733 /* Open the transaction properties file. */ 6734 buf = svn_stringbuf_create_ensure(1024, pool); 6735 stream = svn_stream_from_stringbuf(buf, pool); 6736 SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool)); 6737 SVN_ERR(svn_stream_close(stream)); 6738 SVN_ERR(svn_io_write_unique(&txn_prop_filename, 6739 path_txn_dir(txn->fs, txn->id, pool), 6740 buf->data, 6741 buf->len, 6742 svn_io_file_del_none, 6743 pool)); 6744 return svn_io_file_rename(txn_prop_filename, 6745 path_txn_props(txn->fs, txn->id, pool), 6746 pool); 6747} 6748 6749svn_error_t * 6750svn_fs_fs__get_txn(transaction_t **txn_p, 6751 svn_fs_t *fs, 6752 const char *txn_id, 6753 apr_pool_t *pool) 6754{ 6755 transaction_t *txn; 6756 node_revision_t *noderev; 6757 svn_fs_id_t *root_id; 6758 6759 txn = apr_pcalloc(pool, sizeof(*txn)); 6760 txn->proplist = apr_hash_make(pool); 6761 6762 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool)); 6763 root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool); 6764 6765 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool)); 6766 6767 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool); 6768 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool); 6769 txn->copies = NULL; 6770 6771 *txn_p = txn; 6772 6773 return SVN_NO_ERROR; 6774} 6775 6776/* Write out the currently available next node_id NODE_ID and copy_id 6777 COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is 6778 used both for creating new unique nodes for the given transaction, as 6779 well as uniquifying representations. Perform temporary allocations in 6780 POOL. */ 6781static svn_error_t * 6782write_next_ids(svn_fs_t *fs, 6783 const char *txn_id, 6784 const char *node_id, 6785 const char *copy_id, 6786 apr_pool_t *pool) 6787{ 6788 apr_file_t *file; 6789 svn_stream_t *out_stream; 6790 6791 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6792 APR_WRITE | APR_TRUNCATE, 6793 APR_OS_DEFAULT, pool)); 6794 6795 out_stream = svn_stream_from_aprfile2(file, TRUE, pool); 6796 6797 SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id)); 6798 6799 SVN_ERR(svn_stream_close(out_stream)); 6800 return svn_io_file_close(file, pool); 6801} 6802 6803/* Find out what the next unique node-id and copy-id are for 6804 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID 6805 and *COPY_ID. The next node-id is used both for creating new unique 6806 nodes for the given transaction, as well as uniquifying representations. 6807 Perform all allocations in POOL. */ 6808static svn_error_t * 6809read_next_ids(const char **node_id, 6810 const char **copy_id, 6811 svn_fs_t *fs, 6812 const char *txn_id, 6813 apr_pool_t *pool) 6814{ 6815 apr_file_t *file; 6816 char buf[MAX_KEY_SIZE*2+3]; 6817 apr_size_t limit; 6818 char *str, *last_str = buf; 6819 6820 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6821 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6822 6823 limit = sizeof(buf); 6824 SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool)); 6825 6826 SVN_ERR(svn_io_file_close(file, pool)); 6827 6828 /* Parse this into two separate strings. */ 6829 6830 str = svn_cstring_tokenize(" ", &last_str); 6831 if (! str) 6832 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6833 _("next-id file corrupt")); 6834 6835 *node_id = apr_pstrdup(pool, str); 6836 6837 str = svn_cstring_tokenize(" ", &last_str); 6838 if (! str) 6839 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6840 _("next-id file corrupt")); 6841 6842 *copy_id = apr_pstrdup(pool, str); 6843 6844 return SVN_NO_ERROR; 6845} 6846 6847/* Get a new and unique to this transaction node-id for transaction 6848 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P. 6849 Node-ids are guaranteed to be unique to this transction, but may 6850 not necessarily be sequential. Perform all allocations in POOL. */ 6851static svn_error_t * 6852get_new_txn_node_id(const char **node_id_p, 6853 svn_fs_t *fs, 6854 const char *txn_id, 6855 apr_pool_t *pool) 6856{ 6857 const char *cur_node_id, *cur_copy_id; 6858 char *node_id; 6859 apr_size_t len; 6860 6861 /* First read in the current next-ids file. */ 6862 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 6863 6864 node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2); 6865 6866 len = strlen(cur_node_id); 6867 svn_fs_fs__next_key(cur_node_id, &len, node_id); 6868 6869 SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool)); 6870 6871 *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL); 6872 6873 return SVN_NO_ERROR; 6874} 6875 6876svn_error_t * 6877svn_fs_fs__create_node(const svn_fs_id_t **id_p, 6878 svn_fs_t *fs, 6879 node_revision_t *noderev, 6880 const char *copy_id, 6881 const char *txn_id, 6882 apr_pool_t *pool) 6883{ 6884 const char *node_id; 6885 const svn_fs_id_t *id; 6886 6887 /* Get a new node-id for this node. */ 6888 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool)); 6889 6890 id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6891 6892 noderev->id = id; 6893 6894 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 6895 6896 *id_p = id; 6897 6898 return SVN_NO_ERROR; 6899} 6900 6901svn_error_t * 6902svn_fs_fs__purge_txn(svn_fs_t *fs, 6903 const char *txn_id, 6904 apr_pool_t *pool) 6905{ 6906 fs_fs_data_t *ffd = fs->fsap_data; 6907 6908 /* Remove the shared transaction object associated with this transaction. */ 6909 SVN_ERR(purge_shared_txn(fs, txn_id, pool)); 6910 /* Remove the directory associated with this transaction. */ 6911 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE, 6912 NULL, NULL, pool)); 6913 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 6914 { 6915 /* Delete protorev and its lock, which aren't in the txn 6916 directory. It's OK if they don't exist (for example, if this 6917 is post-commit and the proto-rev has been moved into 6918 place). */ 6919 SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool), 6920 TRUE, pool)); 6921 SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool), 6922 TRUE, pool)); 6923 } 6924 return SVN_NO_ERROR; 6925} 6926 6927 6928svn_error_t * 6929svn_fs_fs__abort_txn(svn_fs_txn_t *txn, 6930 apr_pool_t *pool) 6931{ 6932 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 6933 6934 /* Now, purge the transaction. */ 6935 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool), 6936 apr_psprintf(pool, _("Transaction '%s' cleanup failed"), 6937 txn->id)); 6938 6939 return SVN_NO_ERROR; 6940} 6941 6942 6943svn_error_t * 6944svn_fs_fs__set_entry(svn_fs_t *fs, 6945 const char *txn_id, 6946 node_revision_t *parent_noderev, 6947 const char *name, 6948 const svn_fs_id_t *id, 6949 svn_node_kind_t kind, 6950 apr_pool_t *pool) 6951{ 6952 representation_t *rep = parent_noderev->data_rep; 6953 const char *filename = path_txn_node_children(fs, parent_noderev->id, pool); 6954 apr_file_t *file; 6955 svn_stream_t *out; 6956 fs_fs_data_t *ffd = fs->fsap_data; 6957 apr_pool_t *subpool = svn_pool_create(pool); 6958 6959 if (!rep || !rep->txn_id) 6960 { 6961 const char *unique_suffix; 6962 apr_hash_t *entries; 6963 6964 /* Before we can modify the directory, we need to dump its old 6965 contents into a mutable representation file. */ 6966 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev, 6967 subpool)); 6968 SVN_ERR(unparse_dir_entries(&entries, entries, subpool)); 6969 SVN_ERR(svn_io_file_open(&file, filename, 6970 APR_WRITE | APR_CREATE | APR_BUFFERED, 6971 APR_OS_DEFAULT, pool)); 6972 out = svn_stream_from_aprfile2(file, TRUE, pool); 6973 SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool)); 6974 6975 svn_pool_clear(subpool); 6976 6977 /* Mark the node-rev's data rep as mutable. */ 6978 rep = apr_pcalloc(pool, sizeof(*rep)); 6979 rep->revision = SVN_INVALID_REVNUM; 6980 rep->txn_id = txn_id; 6981 6982 if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 6983 { 6984 SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool)); 6985 rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix); 6986 } 6987 6988 parent_noderev->data_rep = rep; 6989 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id, 6990 parent_noderev, FALSE, pool)); 6991 } 6992 else 6993 { 6994 /* The directory rep is already mutable, so just open it for append. */ 6995 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND, 6996 APR_OS_DEFAULT, pool)); 6997 out = svn_stream_from_aprfile2(file, TRUE, pool); 6998 } 6999 7000 /* if we have a directory cache for this transaction, update it */ 7001 if (ffd->txn_dir_cache) 7002 { 7003 /* build parameters: (name, new entry) pair */ 7004 const char *key = 7005 svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data; 7006 replace_baton_t baton; 7007 7008 baton.name = name; 7009 baton.new_entry = NULL; 7010 7011 if (id) 7012 { 7013 baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry)); 7014 baton.new_entry->name = name; 7015 baton.new_entry->kind = kind; 7016 baton.new_entry->id = id; 7017 } 7018 7019 /* actually update the cached directory (if cached) */ 7020 SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key, 7021 svn_fs_fs__replace_dir_entry, &baton, 7022 subpool)); 7023 } 7024 svn_pool_clear(subpool); 7025 7026 /* Append an incremental hash entry for the entry change. */ 7027 if (id) 7028 { 7029 const char *val = unparse_dir_entry(kind, id, subpool); 7030 7031 SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n" 7032 "V %" APR_SIZE_T_FMT "\n%s\n", 7033 strlen(name), name, 7034 strlen(val), val)); 7035 } 7036 else 7037 { 7038 SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n", 7039 strlen(name), name)); 7040 } 7041 7042 SVN_ERR(svn_io_file_close(file, subpool)); 7043 svn_pool_destroy(subpool); 7044 return SVN_NO_ERROR; 7045} 7046 7047/* Write a single change entry, path PATH, change CHANGE, and copyfrom 7048 string COPYFROM, into the file specified by FILE. Only include the 7049 node kind field if INCLUDE_NODE_KIND is true. All temporary 7050 allocations are in POOL. */ 7051static svn_error_t * 7052write_change_entry(apr_file_t *file, 7053 const char *path, 7054 svn_fs_path_change2_t *change, 7055 svn_boolean_t include_node_kind, 7056 apr_pool_t *pool) 7057{ 7058 const char *idstr, *buf; 7059 const char *change_string = NULL; 7060 const char *kind_string = ""; 7061 7062 switch (change->change_kind) 7063 { 7064 case svn_fs_path_change_modify: 7065 change_string = ACTION_MODIFY; 7066 break; 7067 case svn_fs_path_change_add: 7068 change_string = ACTION_ADD; 7069 break; 7070 case svn_fs_path_change_delete: 7071 change_string = ACTION_DELETE; 7072 break; 7073 case svn_fs_path_change_replace: 7074 change_string = ACTION_REPLACE; 7075 break; 7076 case svn_fs_path_change_reset: 7077 change_string = ACTION_RESET; 7078 break; 7079 default: 7080 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7081 _("Invalid change type %d"), 7082 change->change_kind); 7083 } 7084 7085 if (change->node_rev_id) 7086 idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data; 7087 else 7088 idstr = ACTION_RESET; 7089 7090 if (include_node_kind) 7091 { 7092 SVN_ERR_ASSERT(change->node_kind == svn_node_dir 7093 || change->node_kind == svn_node_file); 7094 kind_string = apr_psprintf(pool, "-%s", 7095 change->node_kind == svn_node_dir 7096 ? KIND_DIR : KIND_FILE); 7097 } 7098 buf = apr_psprintf(pool, "%s %s%s %s %s %s\n", 7099 idstr, change_string, kind_string, 7100 change->text_mod ? FLAG_TRUE : FLAG_FALSE, 7101 change->prop_mod ? FLAG_TRUE : FLAG_FALSE, 7102 path); 7103 7104 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7105 7106 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) 7107 { 7108 buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev, 7109 change->copyfrom_path); 7110 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7111 } 7112 7113 return svn_io_file_write_full(file, "\n", 1, NULL, pool); 7114} 7115 7116svn_error_t * 7117svn_fs_fs__add_change(svn_fs_t *fs, 7118 const char *txn_id, 7119 const char *path, 7120 const svn_fs_id_t *id, 7121 svn_fs_path_change_kind_t change_kind, 7122 svn_boolean_t text_mod, 7123 svn_boolean_t prop_mod, 7124 svn_node_kind_t node_kind, 7125 svn_revnum_t copyfrom_rev, 7126 const char *copyfrom_path, 7127 apr_pool_t *pool) 7128{ 7129 apr_file_t *file; 7130 svn_fs_path_change2_t *change; 7131 7132 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 7133 APR_APPEND | APR_WRITE | APR_CREATE 7134 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7135 7136 change = svn_fs__path_change_create_internal(id, change_kind, pool); 7137 change->text_mod = text_mod; 7138 change->prop_mod = prop_mod; 7139 change->node_kind = node_kind; 7140 change->copyfrom_rev = copyfrom_rev; 7141 change->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 7142 7143 SVN_ERR(write_change_entry(file, path, change, TRUE, pool)); 7144 7145 return svn_io_file_close(file, pool); 7146} 7147 7148/* This baton is used by the representation writing streams. It keeps 7149 track of the checksum information as well as the total size of the 7150 representation so far. */ 7151struct rep_write_baton 7152{ 7153 /* The FS we are writing to. */ 7154 svn_fs_t *fs; 7155 7156 /* Actual file to which we are writing. */ 7157 svn_stream_t *rep_stream; 7158 7159 /* A stream from the delta combiner. Data written here gets 7160 deltified, then eventually written to rep_stream. */ 7161 svn_stream_t *delta_stream; 7162 7163 /* Where is this representation header stored. */ 7164 apr_off_t rep_offset; 7165 7166 /* Start of the actual data. */ 7167 apr_off_t delta_start; 7168 7169 /* How many bytes have been written to this rep already. */ 7170 svn_filesize_t rep_size; 7171 7172 /* The node revision for which we're writing out info. */ 7173 node_revision_t *noderev; 7174 7175 /* Actual output file. */ 7176 apr_file_t *file; 7177 /* Lock 'cookie' used to unlock the output file once we've finished 7178 writing to it. */ 7179 void *lockcookie; 7180 7181 svn_checksum_ctx_t *md5_checksum_ctx; 7182 svn_checksum_ctx_t *sha1_checksum_ctx; 7183 7184 apr_pool_t *pool; 7185 7186 apr_pool_t *parent_pool; 7187}; 7188 7189/* Handler for the write method of the representation writable stream. 7190 BATON is a rep_write_baton, DATA is the data to write, and *LEN is 7191 the length of this data. */ 7192static svn_error_t * 7193rep_write_contents(void *baton, 7194 const char *data, 7195 apr_size_t *len) 7196{ 7197 struct rep_write_baton *b = baton; 7198 7199 SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len)); 7200 SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len)); 7201 b->rep_size += *len; 7202 7203 /* If we are writing a delta, use that stream. */ 7204 if (b->delta_stream) 7205 return svn_stream_write(b->delta_stream, data, len); 7206 else 7207 return svn_stream_write(b->rep_stream, data, len); 7208} 7209 7210/* Given a node-revision NODEREV in filesystem FS, return the 7211 representation in *REP to use as the base for a text representation 7212 delta if PROPS is FALSE. If PROPS has been set, a suitable props 7213 base representation will be returned. Perform temporary allocations 7214 in *POOL. */ 7215static svn_error_t * 7216choose_delta_base(representation_t **rep, 7217 svn_fs_t *fs, 7218 node_revision_t *noderev, 7219 svn_boolean_t props, 7220 apr_pool_t *pool) 7221{ 7222 int count; 7223 int walk; 7224 node_revision_t *base; 7225 fs_fs_data_t *ffd = fs->fsap_data; 7226 svn_boolean_t maybe_shared_rep = FALSE; 7227 7228 /* If we have no predecessors, then use the empty stream as a 7229 base. */ 7230 if (! noderev->predecessor_count) 7231 { 7232 *rep = NULL; 7233 return SVN_NO_ERROR; 7234 } 7235 7236 /* Flip the rightmost '1' bit of the predecessor count to determine 7237 which file rev (counting from 0) we want to use. (To see why 7238 count & (count - 1) unsets the rightmost set bit, think about how 7239 you decrement a binary number.) */ 7240 count = noderev->predecessor_count; 7241 count = count & (count - 1); 7242 7243 /* We use skip delta for limiting the number of delta operations 7244 along very long node histories. Close to HEAD however, we create 7245 a linear history to minimize delta size. */ 7246 walk = noderev->predecessor_count - count; 7247 if (walk < (int)ffd->max_linear_deltification) 7248 count = noderev->predecessor_count - 1; 7249 7250 /* Finding the delta base over a very long distance can become extremely 7251 expensive for very deep histories, possibly causing client timeouts etc. 7252 OTOH, this is a rare operation and its gains are minimal. Lets simply 7253 start deltification anew close every other 1000 changes or so. */ 7254 if (walk > (int)ffd->max_deltification_walk) 7255 { 7256 *rep = NULL; 7257 return SVN_NO_ERROR; 7258 } 7259 7260 /* Walk back a number of predecessors equal to the difference 7261 between count and the original predecessor count. (For example, 7262 if noderev has ten predecessors and we want the eighth file rev, 7263 walk back two predecessors.) */ 7264 base = noderev; 7265 while ((count++) < noderev->predecessor_count) 7266 { 7267 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs, 7268 base->predecessor_id, pool)); 7269 7270 /* If there is a shared rep along the way, we need to limit the 7271 * length of the deltification chain. 7272 * 7273 * Please note that copied nodes - such as branch directories - will 7274 * look the same (false positive) while reps shared within the same 7275 * revision will not be caught (false negative). 7276 */ 7277 if (props) 7278 { 7279 if ( base->prop_rep 7280 && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision) 7281 maybe_shared_rep = TRUE; 7282 } 7283 else 7284 { 7285 if ( base->data_rep 7286 && svn_fs_fs__id_rev(base->id) > base->data_rep->revision) 7287 maybe_shared_rep = TRUE; 7288 } 7289 } 7290 7291 /* return a suitable base representation */ 7292 *rep = props ? base->prop_rep : base->data_rep; 7293 7294 /* if we encountered a shared rep, it's parent chain may be different 7295 * from the node-rev parent chain. */ 7296 if (*rep && maybe_shared_rep) 7297 { 7298 /* Check whether the length of the deltification chain is acceptable. 7299 * Otherwise, shared reps may form a non-skipping delta chain in 7300 * extreme cases. */ 7301 apr_pool_t *sub_pool = svn_pool_create(pool); 7302 representation_t base_rep = **rep; 7303 7304 /* Some reasonable limit, depending on how acceptable longer linear 7305 * chains are in this repo. Also, allow for some minimal chain. */ 7306 int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2; 7307 7308 /* re-use open files between iterations */ 7309 svn_revnum_t rev_hint = SVN_INVALID_REVNUM; 7310 apr_file_t *file_hint = NULL; 7311 7312 /* follow the delta chain towards the end but for at most 7313 * MAX_CHAIN_LENGTH steps. */ 7314 for (; max_chain_length; --max_chain_length) 7315 { 7316 struct rep_state *rep_state; 7317 struct rep_args *rep_args; 7318 7319 SVN_ERR(create_rep_state_body(&rep_state, 7320 &rep_args, 7321 &file_hint, 7322 &rev_hint, 7323 &base_rep, 7324 fs, 7325 sub_pool)); 7326 if (!rep_args->is_delta || !rep_args->base_revision) 7327 break; 7328 7329 base_rep.revision = rep_args->base_revision; 7330 base_rep.offset = rep_args->base_offset; 7331 base_rep.size = rep_args->base_length; 7332 base_rep.txn_id = NULL; 7333 } 7334 7335 /* start new delta chain if the current one has grown too long */ 7336 if (max_chain_length == 0) 7337 *rep = NULL; 7338 7339 svn_pool_destroy(sub_pool); 7340 } 7341 7342 /* verify that the reps don't form a degenerated '*/ 7343 return SVN_NO_ERROR; 7344} 7345 7346/* Something went wrong and the pool for the rep write is being 7347 cleared before we've finished writing the rep. So we need 7348 to remove the rep from the protorevfile and we need to unlock 7349 the protorevfile. */ 7350static apr_status_t 7351rep_write_cleanup(void *data) 7352{ 7353 struct rep_write_baton *b = data; 7354 const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7355 svn_error_t *err; 7356 7357 /* Truncate and close the protorevfile. */ 7358 err = svn_io_file_trunc(b->file, b->rep_offset, b->pool); 7359 err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool)); 7360 7361 /* Remove our lock regardless of any preceeding errors so that the 7362 being_written flag is always removed and stays consistent with the 7363 file lock which will be removed no matter what since the pool is 7364 going away. */ 7365 err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id, 7366 b->lockcookie, b->pool)); 7367 if (err) 7368 { 7369 apr_status_t rc = err->apr_err; 7370 svn_error_clear(err); 7371 return rc; 7372 } 7373 7374 return APR_SUCCESS; 7375} 7376 7377 7378/* Get a rep_write_baton and store it in *WB_P for the representation 7379 indicated by NODEREV in filesystem FS. Perform allocations in 7380 POOL. Only appropriate for file contents, not for props or 7381 directory contents. */ 7382static svn_error_t * 7383rep_write_get_baton(struct rep_write_baton **wb_p, 7384 svn_fs_t *fs, 7385 node_revision_t *noderev, 7386 apr_pool_t *pool) 7387{ 7388 struct rep_write_baton *b; 7389 apr_file_t *file; 7390 representation_t *base_rep; 7391 svn_stream_t *source; 7392 const char *header; 7393 svn_txdelta_window_handler_t wh; 7394 void *whb; 7395 fs_fs_data_t *ffd = fs->fsap_data; 7396 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7397 7398 b = apr_pcalloc(pool, sizeof(*b)); 7399 7400 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7401 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7402 7403 b->fs = fs; 7404 b->parent_pool = pool; 7405 b->pool = svn_pool_create(pool); 7406 b->rep_size = 0; 7407 b->noderev = noderev; 7408 7409 /* Open the prototype rev file and seek to its end. */ 7410 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, 7411 fs, svn_fs_fs__id_txn_id(noderev->id), 7412 b->pool)); 7413 7414 b->file = file; 7415 b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool); 7416 7417 SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool)); 7418 7419 /* Get the base for this delta. */ 7420 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool)); 7421 SVN_ERR(read_representation(&source, fs, base_rep, b->pool)); 7422 7423 /* Write out the rep header. */ 7424 if (base_rep) 7425 { 7426 header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7427 SVN_FILESIZE_T_FMT "\n", 7428 base_rep->revision, base_rep->offset, 7429 base_rep->size); 7430 } 7431 else 7432 { 7433 header = REP_DELTA "\n"; 7434 } 7435 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7436 b->pool)); 7437 7438 /* Now determine the offset of the actual svndiff data. */ 7439 SVN_ERR(get_file_offset(&b->delta_start, file, b->pool)); 7440 7441 /* Cleanup in case something goes wrong. */ 7442 apr_pool_cleanup_register(b->pool, b, rep_write_cleanup, 7443 apr_pool_cleanup_null); 7444 7445 /* Prepare to write the svndiff data. */ 7446 svn_txdelta_to_svndiff3(&wh, 7447 &whb, 7448 b->rep_stream, 7449 diff_version, 7450 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7451 pool); 7452 7453 b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool); 7454 7455 *wb_p = b; 7456 7457 return SVN_NO_ERROR; 7458} 7459 7460/* For the hash REP->SHA1, try to find an already existing representation 7461 in FS and return it in *OUT_REP. If no such representation exists or 7462 if rep sharing has been disabled for FS, NULL will be returned. Since 7463 there may be new duplicate representations within the same uncommitted 7464 revision, those can be passed in REPS_HASH (maps a sha1 digest onto 7465 representation_t*), otherwise pass in NULL for REPS_HASH. 7466 POOL will be used for allocations. The lifetime of the returned rep is 7467 limited by both, POOL and REP lifetime. 7468 */ 7469static svn_error_t * 7470get_shared_rep(representation_t **old_rep, 7471 svn_fs_t *fs, 7472 representation_t *rep, 7473 apr_hash_t *reps_hash, 7474 apr_pool_t *pool) 7475{ 7476 svn_error_t *err; 7477 fs_fs_data_t *ffd = fs->fsap_data; 7478 7479 /* Return NULL, if rep sharing has been disabled. */ 7480 *old_rep = NULL; 7481 if (!ffd->rep_sharing_allowed) 7482 return SVN_NO_ERROR; 7483 7484 /* Check and see if we already have a representation somewhere that's 7485 identical to the one we just wrote out. Start with the hash lookup 7486 because it is cheepest. */ 7487 if (reps_hash) 7488 *old_rep = apr_hash_get(reps_hash, 7489 rep->sha1_checksum->digest, 7490 APR_SHA1_DIGESTSIZE); 7491 7492 /* If we haven't found anything yet, try harder and consult our DB. */ 7493 if (*old_rep == NULL) 7494 { 7495 err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum, 7496 pool); 7497 /* ### Other error codes that we shouldn't mask out? */ 7498 if (err == SVN_NO_ERROR) 7499 { 7500 if (*old_rep) 7501 SVN_ERR(verify_walker(*old_rep, NULL, fs, pool)); 7502 } 7503 else if (err->apr_err == SVN_ERR_FS_CORRUPT 7504 || SVN_ERROR_IN_CATEGORY(err->apr_err, 7505 SVN_ERR_MALFUNC_CATEGORY_START)) 7506 { 7507 /* Fatal error; don't mask it. 7508 7509 In particular, this block is triggered when the rep-cache refers 7510 to revisions in the future. We signal that as a corruption situation 7511 since, once those revisions are less than youngest (because of more 7512 commits), the rep-cache would be invalid. 7513 */ 7514 SVN_ERR(err); 7515 } 7516 else 7517 { 7518 /* Something's wrong with the rep-sharing index. We can continue 7519 without rep-sharing, but warn. 7520 */ 7521 (fs->warning)(fs->warning_baton, err); 7522 svn_error_clear(err); 7523 *old_rep = NULL; 7524 } 7525 } 7526 7527 /* look for intra-revision matches (usually data reps but not limited 7528 to them in case props happen to look like some data rep) 7529 */ 7530 if (*old_rep == NULL && rep->txn_id) 7531 { 7532 svn_node_kind_t kind; 7533 const char *file_name 7534 = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool); 7535 7536 /* in our txn, is there a rep file named with the wanted SHA1? 7537 If so, read it and use that rep. 7538 */ 7539 SVN_ERR(svn_io_check_path(file_name, &kind, pool)); 7540 if (kind == svn_node_file) 7541 { 7542 svn_stringbuf_t *rep_string; 7543 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool)); 7544 SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data, 7545 rep->txn_id, FALSE, pool)); 7546 } 7547 } 7548 7549 /* Add information that is missing in the cached data. */ 7550 if (*old_rep) 7551 { 7552 /* Use the old rep for this content. */ 7553 (*old_rep)->md5_checksum = rep->md5_checksum; 7554 (*old_rep)->uniquifier = rep->uniquifier; 7555 } 7556 7557 return SVN_NO_ERROR; 7558} 7559 7560/* Close handler for the representation write stream. BATON is a 7561 rep_write_baton. Writes out a new node-rev that correctly 7562 references the representation we just finished writing. */ 7563static svn_error_t * 7564rep_write_contents_close(void *baton) 7565{ 7566 struct rep_write_baton *b = baton; 7567 const char *unique_suffix; 7568 representation_t *rep; 7569 representation_t *old_rep; 7570 apr_off_t offset; 7571 fs_fs_data_t *ffd = b->fs->fsap_data; 7572 7573 rep = apr_pcalloc(b->parent_pool, sizeof(*rep)); 7574 rep->offset = b->rep_offset; 7575 7576 /* Close our delta stream so the last bits of svndiff are written 7577 out. */ 7578 if (b->delta_stream) 7579 SVN_ERR(svn_stream_close(b->delta_stream)); 7580 7581 /* Determine the length of the svndiff data. */ 7582 SVN_ERR(get_file_offset(&offset, b->file, b->pool)); 7583 rep->size = offset - b->delta_start; 7584 7585 /* Fill in the rest of the representation field. */ 7586 rep->expanded_size = b->rep_size; 7587 rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7588 7589 if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 7590 { 7591 SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool)); 7592 rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id, 7593 unique_suffix); 7594 } 7595 rep->revision = SVN_INVALID_REVNUM; 7596 7597 /* Finalize the checksum. */ 7598 SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx, 7599 b->parent_pool)); 7600 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx, 7601 b->parent_pool)); 7602 7603 /* Check and see if we already have a representation somewhere that's 7604 identical to the one we just wrote out. */ 7605 SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool)); 7606 7607 if (old_rep) 7608 { 7609 /* We need to erase from the protorev the data we just wrote. */ 7610 SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool)); 7611 7612 /* Use the old rep for this content. */ 7613 b->noderev->data_rep = old_rep; 7614 } 7615 else 7616 { 7617 /* Write out our cosmetic end marker. */ 7618 SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n")); 7619 7620 b->noderev->data_rep = rep; 7621 } 7622 7623 /* Remove cleanup callback. */ 7624 apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup); 7625 7626 /* Write out the new node-rev information. */ 7627 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE, 7628 b->pool)); 7629 if (!old_rep) 7630 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool)); 7631 7632 SVN_ERR(svn_io_file_close(b->file, b->pool)); 7633 SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool)); 7634 svn_pool_destroy(b->pool); 7635 7636 return SVN_NO_ERROR; 7637} 7638 7639/* Store a writable stream in *CONTENTS_P that will receive all data 7640 written and store it as the file data representation referenced by 7641 NODEREV in filesystem FS. Perform temporary allocations in 7642 POOL. Only appropriate for file data, not props or directory 7643 contents. */ 7644static svn_error_t * 7645set_representation(svn_stream_t **contents_p, 7646 svn_fs_t *fs, 7647 node_revision_t *noderev, 7648 apr_pool_t *pool) 7649{ 7650 struct rep_write_baton *wb; 7651 7652 if (! svn_fs_fs__id_txn_id(noderev->id)) 7653 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7654 _("Attempted to write to non-transaction '%s'"), 7655 svn_fs_fs__id_unparse(noderev->id, pool)->data); 7656 7657 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool)); 7658 7659 *contents_p = svn_stream_create(wb, pool); 7660 svn_stream_set_write(*contents_p, rep_write_contents); 7661 svn_stream_set_close(*contents_p, rep_write_contents_close); 7662 7663 return SVN_NO_ERROR; 7664} 7665 7666svn_error_t * 7667svn_fs_fs__set_contents(svn_stream_t **stream, 7668 svn_fs_t *fs, 7669 node_revision_t *noderev, 7670 apr_pool_t *pool) 7671{ 7672 if (noderev->kind != svn_node_file) 7673 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 7674 _("Can't set text contents of a directory")); 7675 7676 return set_representation(stream, fs, noderev, pool); 7677} 7678 7679svn_error_t * 7680svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p, 7681 svn_fs_t *fs, 7682 const svn_fs_id_t *old_idp, 7683 node_revision_t *new_noderev, 7684 const char *copy_id, 7685 const char *txn_id, 7686 apr_pool_t *pool) 7687{ 7688 const svn_fs_id_t *id; 7689 7690 if (! copy_id) 7691 copy_id = svn_fs_fs__id_copy_id(old_idp); 7692 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id, 7693 txn_id, pool); 7694 7695 new_noderev->id = id; 7696 7697 if (! new_noderev->copyroot_path) 7698 { 7699 new_noderev->copyroot_path = apr_pstrdup(pool, 7700 new_noderev->created_path); 7701 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id); 7702 } 7703 7704 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE, 7705 pool)); 7706 7707 *new_id_p = id; 7708 7709 return SVN_NO_ERROR; 7710} 7711 7712svn_error_t * 7713svn_fs_fs__set_proplist(svn_fs_t *fs, 7714 node_revision_t *noderev, 7715 apr_hash_t *proplist, 7716 apr_pool_t *pool) 7717{ 7718 const char *filename = path_txn_node_props(fs, noderev->id, pool); 7719 apr_file_t *file; 7720 svn_stream_t *out; 7721 7722 /* Dump the property list to the mutable property file. */ 7723 SVN_ERR(svn_io_file_open(&file, filename, 7724 APR_WRITE | APR_CREATE | APR_TRUNCATE 7725 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7726 out = svn_stream_from_aprfile2(file, TRUE, pool); 7727 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool)); 7728 SVN_ERR(svn_io_file_close(file, pool)); 7729 7730 /* Mark the node-rev's prop rep as mutable, if not already done. */ 7731 if (!noderev->prop_rep || !noderev->prop_rep->txn_id) 7732 { 7733 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep)); 7734 noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id); 7735 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 7736 } 7737 7738 return SVN_NO_ERROR; 7739} 7740 7741/* Read the 'current' file for filesystem FS and store the next 7742 available node id in *NODE_ID, and the next available copy id in 7743 *COPY_ID. Allocations are performed from POOL. */ 7744static svn_error_t * 7745get_next_revision_ids(const char **node_id, 7746 const char **copy_id, 7747 svn_fs_t *fs, 7748 apr_pool_t *pool) 7749{ 7750 char *buf; 7751 char *str; 7752 svn_stringbuf_t *content; 7753 7754 SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool)); 7755 buf = content->data; 7756 7757 str = svn_cstring_tokenize(" ", &buf); 7758 if (! str) 7759 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7760 _("Corrupt 'current' file")); 7761 7762 str = svn_cstring_tokenize(" ", &buf); 7763 if (! str) 7764 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7765 _("Corrupt 'current' file")); 7766 7767 *node_id = apr_pstrdup(pool, str); 7768 7769 str = svn_cstring_tokenize(" \n", &buf); 7770 if (! str) 7771 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7772 _("Corrupt 'current' file")); 7773 7774 *copy_id = apr_pstrdup(pool, str); 7775 7776 return SVN_NO_ERROR; 7777} 7778 7779/* This baton is used by the stream created for write_hash_rep. */ 7780struct write_hash_baton 7781{ 7782 svn_stream_t *stream; 7783 7784 apr_size_t size; 7785 7786 svn_checksum_ctx_t *md5_ctx; 7787 svn_checksum_ctx_t *sha1_ctx; 7788}; 7789 7790/* The handler for the write_hash_rep stream. BATON is a 7791 write_hash_baton, DATA has the data to write and *LEN is the number 7792 of bytes to write. */ 7793static svn_error_t * 7794write_hash_handler(void *baton, 7795 const char *data, 7796 apr_size_t *len) 7797{ 7798 struct write_hash_baton *whb = baton; 7799 7800 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 7801 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 7802 7803 SVN_ERR(svn_stream_write(whb->stream, data, len)); 7804 whb->size += *len; 7805 7806 return SVN_NO_ERROR; 7807} 7808 7809/* Write out the hash HASH as a text representation to file FILE. In 7810 the process, record position, the total size of the dump and MD5 as 7811 well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH 7812 is not NULL, it will be used in addition to the on-disk cache to find 7813 earlier reps with the same content. When such existing reps can be 7814 found, we will truncate the one just written from the file and return 7815 the existing rep. Perform temporary allocations in POOL. */ 7816static svn_error_t * 7817write_hash_rep(representation_t *rep, 7818 apr_file_t *file, 7819 apr_hash_t *hash, 7820 svn_fs_t *fs, 7821 apr_hash_t *reps_hash, 7822 apr_pool_t *pool) 7823{ 7824 svn_stream_t *stream; 7825 struct write_hash_baton *whb; 7826 representation_t *old_rep; 7827 7828 SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7829 7830 whb = apr_pcalloc(pool, sizeof(*whb)); 7831 7832 whb->stream = svn_stream_from_aprfile2(file, TRUE, pool); 7833 whb->size = 0; 7834 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7835 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7836 7837 stream = svn_stream_create(whb, pool); 7838 svn_stream_set_write(stream, write_hash_handler); 7839 7840 SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n")); 7841 7842 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7843 7844 /* Store the results. */ 7845 SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7846 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7847 7848 /* Check and see if we already have a representation somewhere that's 7849 identical to the one we just wrote out. */ 7850 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7851 7852 if (old_rep) 7853 { 7854 /* We need to erase from the protorev the data we just wrote. */ 7855 SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7856 7857 /* Use the old rep for this content. */ 7858 memcpy(rep, old_rep, sizeof (*rep)); 7859 } 7860 else 7861 { 7862 /* Write out our cosmetic end marker. */ 7863 SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n")); 7864 7865 /* update the representation */ 7866 rep->size = whb->size; 7867 rep->expanded_size = whb->size; 7868 } 7869 7870 return SVN_NO_ERROR; 7871} 7872 7873/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified 7874 text representation to file FILE. In the process, record the total size 7875 and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH 7876 is not NULL, it will be used in addition to the on-disk cache to find 7877 earlier reps with the same content. When such existing reps can be found, 7878 we will truncate the one just written from the file and return the existing 7879 rep. If PROPS is set, assume that we want to a props representation as 7880 the base for our delta. Perform temporary allocations in POOL. */ 7881static svn_error_t * 7882write_hash_delta_rep(representation_t *rep, 7883 apr_file_t *file, 7884 apr_hash_t *hash, 7885 svn_fs_t *fs, 7886 node_revision_t *noderev, 7887 apr_hash_t *reps_hash, 7888 svn_boolean_t props, 7889 apr_pool_t *pool) 7890{ 7891 svn_txdelta_window_handler_t diff_wh; 7892 void *diff_whb; 7893 7894 svn_stream_t *file_stream; 7895 svn_stream_t *stream; 7896 representation_t *base_rep; 7897 representation_t *old_rep; 7898 svn_stream_t *source; 7899 const char *header; 7900 7901 apr_off_t rep_end = 0; 7902 apr_off_t delta_start = 0; 7903 7904 struct write_hash_baton *whb; 7905 fs_fs_data_t *ffd = fs->fsap_data; 7906 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7907 7908 /* Get the base for this delta. */ 7909 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool)); 7910 SVN_ERR(read_representation(&source, fs, base_rep, pool)); 7911 7912 SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7913 7914 /* Write out the rep header. */ 7915 if (base_rep) 7916 { 7917 header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7918 SVN_FILESIZE_T_FMT "\n", 7919 base_rep->revision, base_rep->offset, 7920 base_rep->size); 7921 } 7922 else 7923 { 7924 header = REP_DELTA "\n"; 7925 } 7926 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7927 pool)); 7928 7929 SVN_ERR(get_file_offset(&delta_start, file, pool)); 7930 file_stream = svn_stream_from_aprfile2(file, TRUE, pool); 7931 7932 /* Prepare to write the svndiff data. */ 7933 svn_txdelta_to_svndiff3(&diff_wh, 7934 &diff_whb, 7935 file_stream, 7936 diff_version, 7937 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7938 pool); 7939 7940 whb = apr_pcalloc(pool, sizeof(*whb)); 7941 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool); 7942 whb->size = 0; 7943 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7944 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7945 7946 /* serialize the hash */ 7947 stream = svn_stream_create(whb, pool); 7948 svn_stream_set_write(stream, write_hash_handler); 7949 7950 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7951 SVN_ERR(svn_stream_close(whb->stream)); 7952 7953 /* Store the results. */ 7954 SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7955 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7956 7957 /* Check and see if we already have a representation somewhere that's 7958 identical to the one we just wrote out. */ 7959 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7960 7961 if (old_rep) 7962 { 7963 /* We need to erase from the protorev the data we just wrote. */ 7964 SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7965 7966 /* Use the old rep for this content. */ 7967 memcpy(rep, old_rep, sizeof (*rep)); 7968 } 7969 else 7970 { 7971 /* Write out our cosmetic end marker. */ 7972 SVN_ERR(get_file_offset(&rep_end, file, pool)); 7973 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 7974 7975 /* update the representation */ 7976 rep->expanded_size = whb->size; 7977 rep->size = rep_end - delta_start; 7978 } 7979 7980 return SVN_NO_ERROR; 7981} 7982 7983/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 7984 of (not yet committed) revision REV in FS. Use POOL for temporary 7985 allocations. 7986 7987 If you change this function, consider updating svn_fs_fs__verify() too. 7988 */ 7989static svn_error_t * 7990validate_root_noderev(svn_fs_t *fs, 7991 node_revision_t *root_noderev, 7992 svn_revnum_t rev, 7993 apr_pool_t *pool) 7994{ 7995 svn_revnum_t head_revnum = rev-1; 7996 int head_predecessor_count; 7997 7998 SVN_ERR_ASSERT(rev > 0); 7999 8000 /* Compute HEAD_PREDECESSOR_COUNT. */ 8001 { 8002 svn_fs_root_t *head_revision; 8003 const svn_fs_id_t *head_root_id; 8004 node_revision_t *head_root_noderev; 8005 8006 /* Get /@HEAD's noderev. */ 8007 SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool)); 8008 SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool)); 8009 SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id, 8010 pool)); 8011 8012 head_predecessor_count = head_root_noderev->predecessor_count; 8013 } 8014 8015 /* Check that the root noderev's predecessor count equals REV. 8016 8017 This kind of corruption was seen on svn.apache.org (both on 8018 the root noderev and on other fspaths' noderevs); see 8019 issue #4129. 8020 8021 Normally (rev == root_noderev->predecessor_count), but here we 8022 use a more roundabout check that should only trigger on new instances 8023 of the corruption, rather then trigger on each and every new commit 8024 to a repository that has triggered the bug somewhere in its root 8025 noderev's history. 8026 */ 8027 if (root_noderev->predecessor_count != -1 8028 && (root_noderev->predecessor_count - head_predecessor_count) 8029 != (rev - head_revnum)) 8030 { 8031 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 8032 _("predecessor count for " 8033 "the root node-revision is wrong: " 8034 "found (%d+%ld != %d), committing r%ld"), 8035 head_predecessor_count, 8036 rev - head_revnum, /* This is equal to 1. */ 8037 root_noderev->predecessor_count, 8038 rev); 8039 } 8040 8041 return SVN_NO_ERROR; 8042} 8043 8044/* Copy a node-revision specified by id ID in fileystem FS from a 8045 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 8046 pointer to the new node-id which will be allocated in POOL. 8047 If this is a directory, copy all children as well. 8048 8049 START_NODE_ID and START_COPY_ID are 8050 the first available node and copy ids for this filesystem, for older 8051 FS formats. 8052 8053 REV is the revision number that this proto-rev-file will represent. 8054 8055 INITIAL_OFFSET is the offset of the proto-rev-file on entry to 8056 commit_body. 8057 8058 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 8059 REPS_POOL) of each data rep that is new in this revision. 8060 8061 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 8062 of the representations of each property rep that is new in this 8063 revision. 8064 8065 AT_ROOT is true if the node revision being written is the root 8066 node-revision. It is only controls additional sanity checking 8067 logic. 8068 8069 Temporary allocations are also from POOL. */ 8070static svn_error_t * 8071write_final_rev(const svn_fs_id_t **new_id_p, 8072 apr_file_t *file, 8073 svn_revnum_t rev, 8074 svn_fs_t *fs, 8075 const svn_fs_id_t *id, 8076 const char *start_node_id, 8077 const char *start_copy_id, 8078 apr_off_t initial_offset, 8079 apr_array_header_t *reps_to_cache, 8080 apr_hash_t *reps_hash, 8081 apr_pool_t *reps_pool, 8082 svn_boolean_t at_root, 8083 apr_pool_t *pool) 8084{ 8085 node_revision_t *noderev; 8086 apr_off_t my_offset; 8087 char my_node_id_buf[MAX_KEY_SIZE + 2]; 8088 char my_copy_id_buf[MAX_KEY_SIZE + 2]; 8089 const svn_fs_id_t *new_id; 8090 const char *node_id, *copy_id, *my_node_id, *my_copy_id; 8091 fs_fs_data_t *ffd = fs->fsap_data; 8092 8093 *new_id_p = NULL; 8094 8095 /* Check to see if this is a transaction node. */ 8096 if (! svn_fs_fs__id_txn_id(id)) 8097 return SVN_NO_ERROR; 8098 8099 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 8100 8101 if (noderev->kind == svn_node_dir) 8102 { 8103 apr_pool_t *subpool; 8104 apr_hash_t *entries, *str_entries; 8105 apr_array_header_t *sorted_entries; 8106 int i; 8107 8108 /* This is a directory. Write out all the children first. */ 8109 subpool = svn_pool_create(pool); 8110 8111 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool)); 8112 /* For the sake of the repository administrator sort the entries 8113 so that the final file is deterministic and repeatable, 8114 however the rest of the FSFS code doesn't require any 8115 particular order here. */ 8116 sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically, 8117 pool); 8118 for (i = 0; i < sorted_entries->nelts; ++i) 8119 { 8120 svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i, 8121 svn_sort__item_t).value; 8122 8123 svn_pool_clear(subpool); 8124 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id, 8125 start_node_id, start_copy_id, initial_offset, 8126 reps_to_cache, reps_hash, reps_pool, FALSE, 8127 subpool)); 8128 if (new_id && (svn_fs_fs__id_rev(new_id) == rev)) 8129 dirent->id = svn_fs_fs__id_copy(new_id, pool); 8130 } 8131 svn_pool_destroy(subpool); 8132 8133 if (noderev->data_rep && noderev->data_rep->txn_id) 8134 { 8135 /* Write out the contents of this directory as a text rep. */ 8136 SVN_ERR(unparse_dir_entries(&str_entries, entries, pool)); 8137 8138 noderev->data_rep->txn_id = NULL; 8139 noderev->data_rep->revision = rev; 8140 8141 if (ffd->deltify_directories) 8142 SVN_ERR(write_hash_delta_rep(noderev->data_rep, file, 8143 str_entries, fs, noderev, NULL, 8144 FALSE, pool)); 8145 else 8146 SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries, 8147 fs, NULL, pool)); 8148 } 8149 } 8150 else 8151 { 8152 /* This is a file. We should make sure the data rep, if it 8153 exists in a "this" state, gets rewritten to our new revision 8154 num. */ 8155 8156 if (noderev->data_rep && noderev->data_rep->txn_id) 8157 { 8158 noderev->data_rep->txn_id = NULL; 8159 noderev->data_rep->revision = rev; 8160 8161 /* See issue 3845. Some unknown mechanism caused the 8162 protorev file to get truncated, so check for that 8163 here. */ 8164 if (noderev->data_rep->offset + noderev->data_rep->size 8165 > initial_offset) 8166 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 8167 _("Truncated protorev file detected")); 8168 } 8169 } 8170 8171 /* Fix up the property reps. */ 8172 if (noderev->prop_rep && noderev->prop_rep->txn_id) 8173 { 8174 apr_hash_t *proplist; 8175 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool)); 8176 8177 noderev->prop_rep->txn_id = NULL; 8178 noderev->prop_rep->revision = rev; 8179 8180 if (ffd->deltify_properties) 8181 SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file, 8182 proplist, fs, noderev, reps_hash, 8183 TRUE, pool)); 8184 else 8185 SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist, 8186 fs, reps_hash, pool)); 8187 } 8188 8189 8190 /* Convert our temporary ID into a permanent revision one. */ 8191 SVN_ERR(get_file_offset(&my_offset, file, pool)); 8192 8193 node_id = svn_fs_fs__id_node_id(noderev->id); 8194 if (*node_id == '_') 8195 { 8196 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8197 my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev); 8198 else 8199 { 8200 svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf); 8201 my_node_id = my_node_id_buf; 8202 } 8203 } 8204 else 8205 my_node_id = node_id; 8206 8207 copy_id = svn_fs_fs__id_copy_id(noderev->id); 8208 if (*copy_id == '_') 8209 { 8210 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8211 my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev); 8212 else 8213 { 8214 svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf); 8215 my_copy_id = my_copy_id_buf; 8216 } 8217 } 8218 else 8219 my_copy_id = copy_id; 8220 8221 if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 8222 noderev->copyroot_rev = rev; 8223 8224 new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset, 8225 pool); 8226 8227 noderev->id = new_id; 8228 8229 if (ffd->rep_sharing_allowed) 8230 { 8231 /* Save the data representation's hash in the rep cache. */ 8232 if ( noderev->data_rep && noderev->kind == svn_node_file 8233 && noderev->data_rep->revision == rev) 8234 { 8235 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8236 APR_ARRAY_PUSH(reps_to_cache, representation_t *) 8237 = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool); 8238 } 8239 8240 if (noderev->prop_rep && noderev->prop_rep->revision == rev) 8241 { 8242 /* Add new property reps to hash and on-disk cache. */ 8243 representation_t *copy 8244 = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool); 8245 8246 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8247 APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy; 8248 8249 apr_hash_set(reps_hash, 8250 copy->sha1_checksum->digest, 8251 APR_SHA1_DIGESTSIZE, 8252 copy); 8253 } 8254 } 8255 8256 /* don't serialize SHA1 for dirs to disk (waste of space) */ 8257 if (noderev->data_rep && noderev->kind == svn_node_dir) 8258 noderev->data_rep->sha1_checksum = NULL; 8259 8260 /* don't serialize SHA1 for props to disk (waste of space) */ 8261 if (noderev->prop_rep) 8262 noderev->prop_rep->sha1_checksum = NULL; 8263 8264 /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 8265 noderev->is_fresh_txn_root = FALSE; 8266 8267 /* Write out our new node-revision. */ 8268 if (at_root) 8269 SVN_ERR(validate_root_noderev(fs, noderev, rev, pool)); 8270 8271 SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool), 8272 noderev, ffd->format, 8273 svn_fs_fs__fs_supports_mergeinfo(fs), 8274 pool)); 8275 8276 /* Return our ID that references the revision file. */ 8277 *new_id_p = noderev->id; 8278 8279 return SVN_NO_ERROR; 8280} 8281 8282/* Write the changed path info from transaction TXN_ID in filesystem 8283 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset 8284 in the file of the beginning of this information. Perform 8285 temporary allocations in POOL. */ 8286static svn_error_t * 8287write_final_changed_path_info(apr_off_t *offset_p, 8288 apr_file_t *file, 8289 svn_fs_t *fs, 8290 const char *txn_id, 8291 apr_pool_t *pool) 8292{ 8293 apr_hash_t *changed_paths; 8294 apr_off_t offset; 8295 apr_pool_t *iterpool = svn_pool_create(pool); 8296 fs_fs_data_t *ffd = fs->fsap_data; 8297 svn_boolean_t include_node_kinds = 8298 ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT; 8299 apr_array_header_t *sorted_changed_paths; 8300 int i; 8301 8302 SVN_ERR(get_file_offset(&offset, file, pool)); 8303 8304 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool)); 8305 /* For the sake of the repository administrator sort the changes so 8306 that the final file is deterministic and repeatable, however the 8307 rest of the FSFS code doesn't require any particular order here. */ 8308 sorted_changed_paths = svn_sort__hash(changed_paths, 8309 svn_sort_compare_items_lexically, pool); 8310 8311 /* Iterate through the changed paths one at a time, and convert the 8312 temporary node-id into a permanent one for each change entry. */ 8313 for (i = 0; i < sorted_changed_paths->nelts; ++i) 8314 { 8315 node_revision_t *noderev; 8316 const svn_fs_id_t *id; 8317 svn_fs_path_change2_t *change; 8318 const char *path; 8319 8320 svn_pool_clear(iterpool); 8321 8322 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value; 8323 path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key; 8324 8325 id = change->node_rev_id; 8326 8327 /* If this was a delete of a mutable node, then it is OK to 8328 leave the change entry pointing to the non-existent temporary 8329 node, since it will never be used. */ 8330 if ((change->change_kind != svn_fs_path_change_delete) && 8331 (! svn_fs_fs__id_txn_id(id))) 8332 { 8333 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool)); 8334 8335 /* noderev has the permanent node-id at this point, so we just 8336 substitute it for the temporary one. */ 8337 change->node_rev_id = noderev->id; 8338 } 8339 8340 /* Write out the new entry into the final rev-file. */ 8341 SVN_ERR(write_change_entry(file, path, change, include_node_kinds, 8342 iterpool)); 8343 } 8344 8345 svn_pool_destroy(iterpool); 8346 8347 *offset_p = offset; 8348 8349 return SVN_NO_ERROR; 8350} 8351 8352/* Atomically update the 'current' file to hold the specifed REV, 8353 NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are 8354 ignored and may be NULL if the FS format does not use them.) 8355 Perform temporary allocations in POOL. */ 8356static svn_error_t * 8357write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id, 8358 const char *next_copy_id, apr_pool_t *pool) 8359{ 8360 char *buf; 8361 const char *tmp_name, *name; 8362 fs_fs_data_t *ffd = fs->fsap_data; 8363 8364 /* Now we can just write out this line. */ 8365 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8366 buf = apr_psprintf(pool, "%ld\n", rev); 8367 else 8368 buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id); 8369 8370 name = svn_fs_fs__path_current(fs, pool); 8371 SVN_ERR(svn_io_write_unique(&tmp_name, 8372 svn_dirent_dirname(name, pool), 8373 buf, strlen(buf), 8374 svn_io_file_del_none, pool)); 8375 8376 return move_into_place(tmp_name, name, name, pool); 8377} 8378 8379/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 8380 youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on 8381 NEW_REV's revision root. 8382 8383 Intended to be called as the very last step in a commit before 'current' 8384 is bumped. This implies that we are holding the write lock. */ 8385static svn_error_t * 8386verify_as_revision_before_current_plus_plus(svn_fs_t *fs, 8387 svn_revnum_t new_rev, 8388 apr_pool_t *pool) 8389{ 8390#ifdef SVN_DEBUG 8391 fs_fs_data_t *ffd = fs->fsap_data; 8392 svn_fs_t *ft; /* fs++ == ft */ 8393 svn_fs_root_t *root; 8394 fs_fs_data_t *ft_ffd; 8395 apr_hash_t *fs_config; 8396 8397 SVN_ERR_ASSERT(ffd->svn_fs_open_); 8398 8399 /* make sure FT does not simply return data cached by other instances 8400 * but actually retrieves it from disk at least once. 8401 */ 8402 fs_config = apr_hash_make(pool); 8403 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 8404 svn_uuid_generate(pool)); 8405 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 8406 fs_config, 8407 pool)); 8408 ft_ffd = ft->fsap_data; 8409 /* Don't let FT consult rep-cache.db, either. */ 8410 ft_ffd->rep_sharing_allowed = FALSE; 8411 8412 /* Time travel! */ 8413 ft_ffd->youngest_rev_cache = new_rev; 8414 8415 SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool)); 8416 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 8417 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 8418 SVN_ERR(svn_fs_fs__verify_root(root, pool)); 8419#endif /* SVN_DEBUG */ 8420 8421 return SVN_NO_ERROR; 8422} 8423 8424/* Update the 'current' file to hold the correct next node and copy_ids 8425 from transaction TXN_ID in filesystem FS. The current revision is 8426 set to REV. Perform temporary allocations in POOL. */ 8427static svn_error_t * 8428write_final_current(svn_fs_t *fs, 8429 const char *txn_id, 8430 svn_revnum_t rev, 8431 const char *start_node_id, 8432 const char *start_copy_id, 8433 apr_pool_t *pool) 8434{ 8435 const char *txn_node_id, *txn_copy_id; 8436 char new_node_id[MAX_KEY_SIZE + 2]; 8437 char new_copy_id[MAX_KEY_SIZE + 2]; 8438 fs_fs_data_t *ffd = fs->fsap_data; 8439 8440 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8441 return write_current(fs, rev, NULL, NULL, pool); 8442 8443 /* To find the next available ids, we add the id that used to be in 8444 the 'current' file, to the next ids from the transaction file. */ 8445 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool)); 8446 8447 svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id); 8448 svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id); 8449 8450 return write_current(fs, rev, new_node_id, new_copy_id, pool); 8451} 8452 8453/* Verify that the user registed with FS has all the locks necessary to 8454 permit all the changes associate with TXN_NAME. 8455 The FS write lock is assumed to be held by the caller. */ 8456static svn_error_t * 8457verify_locks(svn_fs_t *fs, 8458 const char *txn_name, 8459 apr_pool_t *pool) 8460{ 8461 apr_pool_t *subpool = svn_pool_create(pool); 8462 apr_hash_t *changes; 8463 apr_hash_index_t *hi; 8464 apr_array_header_t *changed_paths; 8465 svn_stringbuf_t *last_recursed = NULL; 8466 int i; 8467 8468 /* Fetch the changes for this transaction. */ 8469 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool)); 8470 8471 /* Make an array of the changed paths, and sort them depth-first-ily. */ 8472 changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1, 8473 sizeof(const char *)); 8474 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 8475 APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi); 8476 qsort(changed_paths->elts, changed_paths->nelts, 8477 changed_paths->elt_size, svn_sort_compare_paths); 8478 8479 /* Now, traverse the array of changed paths, verify locks. Note 8480 that if we need to do a recursive verification a path, we'll skip 8481 over children of that path when we get to them. */ 8482 for (i = 0; i < changed_paths->nelts; i++) 8483 { 8484 const char *path; 8485 svn_fs_path_change2_t *change; 8486 svn_boolean_t recurse = TRUE; 8487 8488 svn_pool_clear(subpool); 8489 path = APR_ARRAY_IDX(changed_paths, i, const char *); 8490 8491 /* If this path has already been verified as part of a recursive 8492 check of one of its parents, no need to do it again. */ 8493 if (last_recursed 8494 && svn_dirent_is_child(last_recursed->data, path, subpool)) 8495 continue; 8496 8497 /* Fetch the change associated with our path. */ 8498 change = svn_hash_gets(changes, path); 8499 8500 /* What does it mean to succeed at lock verification for a given 8501 path? For an existing file or directory getting modified 8502 (text, props), it means we hold the lock on the file or 8503 directory. For paths being added or removed, we need to hold 8504 the locks for that path and any children of that path. 8505 8506 WHEW! We have no reliable way to determine the node kind 8507 of deleted items, but fortunately we are going to do a 8508 recursive check on deleted paths regardless of their kind. */ 8509 if (change->change_kind == svn_fs_path_change_modify) 8510 recurse = FALSE; 8511 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE, 8512 subpool)); 8513 8514 /* If we just did a recursive check, remember the path we 8515 checked (so children can be skipped). */ 8516 if (recurse) 8517 { 8518 if (! last_recursed) 8519 last_recursed = svn_stringbuf_create(path, pool); 8520 else 8521 svn_stringbuf_set(last_recursed, path); 8522 } 8523 } 8524 svn_pool_destroy(subpool); 8525 return SVN_NO_ERROR; 8526} 8527 8528/* Baton used for commit_body below. */ 8529struct commit_baton { 8530 svn_revnum_t *new_rev_p; 8531 svn_fs_t *fs; 8532 svn_fs_txn_t *txn; 8533 apr_array_header_t *reps_to_cache; 8534 apr_hash_t *reps_hash; 8535 apr_pool_t *reps_pool; 8536}; 8537 8538/* The work-horse for svn_fs_fs__commit, called with the FS write lock. 8539 This implements the svn_fs_fs__with_write_lock() 'body' callback 8540 type. BATON is a 'struct commit_baton *'. */ 8541static svn_error_t * 8542commit_body(void *baton, apr_pool_t *pool) 8543{ 8544 struct commit_baton *cb = baton; 8545 fs_fs_data_t *ffd = cb->fs->fsap_data; 8546 const char *old_rev_filename, *rev_filename, *proto_filename; 8547 const char *revprop_filename, *final_revprop; 8548 const svn_fs_id_t *root_id, *new_root_id; 8549 const char *start_node_id = NULL, *start_copy_id = NULL; 8550 svn_revnum_t old_rev, new_rev; 8551 apr_file_t *proto_file; 8552 void *proto_file_lockcookie; 8553 apr_off_t initial_offset, changed_path_offset; 8554 char *buf; 8555 apr_hash_t *txnprops; 8556 apr_array_header_t *txnprop_list; 8557 svn_prop_t prop; 8558 svn_string_t date; 8559 8560 /* Get the current youngest revision. */ 8561 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool)); 8562 8563 /* Check to make sure this transaction is based off the most recent 8564 revision. */ 8565 if (cb->txn->base_rev != old_rev) 8566 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 8567 _("Transaction out of date")); 8568 8569 /* Locks may have been added (or stolen) between the calling of 8570 previous svn_fs.h functions and svn_fs_commit_txn(), so we need 8571 to re-examine every changed-path in the txn and re-verify all 8572 discovered locks. */ 8573 SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool)); 8574 8575 /* Get the next node_id and copy_id to use. */ 8576 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8577 SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs, 8578 pool)); 8579 8580 /* We are going to be one better than this puny old revision. */ 8581 new_rev = old_rev + 1; 8582 8583 /* Get a write handle on the proto revision file. */ 8584 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 8585 cb->fs, cb->txn->id, pool)); 8586 SVN_ERR(get_file_offset(&initial_offset, proto_file, pool)); 8587 8588 /* Write out all the node-revisions and directory contents. */ 8589 root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool); 8590 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id, 8591 start_node_id, start_copy_id, initial_offset, 8592 cb->reps_to_cache, cb->reps_hash, cb->reps_pool, 8593 TRUE, pool)); 8594 8595 /* Write the changed-path information. */ 8596 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 8597 cb->fs, cb->txn->id, pool)); 8598 8599 /* Write the final line. */ 8600 buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n", 8601 svn_fs_fs__id_offset(new_root_id), 8602 changed_path_offset); 8603 SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL, 8604 pool)); 8605 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool)); 8606 SVN_ERR(svn_io_file_close(proto_file, pool)); 8607 8608 /* We don't unlock the prototype revision file immediately to avoid a 8609 race with another caller writing to the prototype revision file 8610 before we commit it. */ 8611 8612 /* Remove any temporary txn props representing 'flags'. */ 8613 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool)); 8614 txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t)); 8615 prop.value = NULL; 8616 8617 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 8618 { 8619 prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 8620 APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8621 } 8622 8623 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 8624 { 8625 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 8626 APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8627 } 8628 8629 if (! apr_is_empty_array(txnprop_list)) 8630 SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool)); 8631 8632 /* Create the shard for the rev and revprop file, if we're sharding and 8633 this is the first revision of a new shard. We don't care if this 8634 fails because the shard already existed for some reason. */ 8635 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0) 8636 { 8637 /* Create the revs shard. */ 8638 { 8639 const char *new_dir = path_rev_shard(cb->fs, new_rev, pool); 8640 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8641 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8642 return svn_error_trace(err); 8643 svn_error_clear(err); 8644 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8645 PATH_REVS_DIR, 8646 pool), 8647 new_dir, pool)); 8648 } 8649 8650 /* Create the revprops shard. */ 8651 SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8652 { 8653 const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool); 8654 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8655 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8656 return svn_error_trace(err); 8657 svn_error_clear(err); 8658 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8659 PATH_REVPROPS_DIR, 8660 pool), 8661 new_dir, pool)); 8662 } 8663 } 8664 8665 /* Move the finished rev file into place. */ 8666 SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename, 8667 cb->fs, old_rev, pool)); 8668 rev_filename = path_rev(cb->fs, new_rev, pool); 8669 proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool); 8670 SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename, 8671 pool)); 8672 8673 /* Now that we've moved the prototype revision file out of the way, 8674 we can unlock it (since further attempts to write to the file 8675 will fail as it no longer exists). We must do this so that we can 8676 remove the transaction directory later. */ 8677 SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool)); 8678 8679 /* Update commit time to ensure that svn:date revprops remain ordered. */ 8680 date.data = svn_time_to_cstring(apr_time_now(), pool); 8681 date.len = strlen(date.data); 8682 8683 SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE, 8684 &date, pool)); 8685 8686 /* Move the revprops file into place. */ 8687 SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8688 revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool); 8689 final_revprop = path_revprops(cb->fs, new_rev, pool); 8690 SVN_ERR(move_into_place(revprop_filename, final_revprop, 8691 old_rev_filename, pool)); 8692 8693 /* Update the 'current' file. */ 8694 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool)); 8695 SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id, 8696 start_copy_id, pool)); 8697 8698 /* At this point the new revision is committed and globally visible 8699 so let the caller know it succeeded by giving it the new revision 8700 number, which fulfills svn_fs_commit_txn() contract. Any errors 8701 after this point do not change the fact that a new revision was 8702 created. */ 8703 *cb->new_rev_p = new_rev; 8704 8705 ffd->youngest_rev_cache = new_rev; 8706 8707 /* Remove this transaction directory. */ 8708 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool)); 8709 8710 return SVN_NO_ERROR; 8711} 8712 8713/* Add the representations in REPS_TO_CACHE (an array of representation_t *) 8714 * to the rep-cache database of FS. */ 8715static svn_error_t * 8716write_reps_to_cache(svn_fs_t *fs, 8717 const apr_array_header_t *reps_to_cache, 8718 apr_pool_t *scratch_pool) 8719{ 8720 int i; 8721 8722 for (i = 0; i < reps_to_cache->nelts; i++) 8723 { 8724 representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *); 8725 8726 /* FALSE because we don't care if another parallel commit happened to 8727 * collide with us. (Non-parallel collisions will not be detected.) */ 8728 SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool)); 8729 } 8730 8731 return SVN_NO_ERROR; 8732} 8733 8734svn_error_t * 8735svn_fs_fs__commit(svn_revnum_t *new_rev_p, 8736 svn_fs_t *fs, 8737 svn_fs_txn_t *txn, 8738 apr_pool_t *pool) 8739{ 8740 struct commit_baton cb; 8741 fs_fs_data_t *ffd = fs->fsap_data; 8742 8743 cb.new_rev_p = new_rev_p; 8744 cb.fs = fs; 8745 cb.txn = txn; 8746 8747 if (ffd->rep_sharing_allowed) 8748 { 8749 cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *)); 8750 cb.reps_hash = apr_hash_make(pool); 8751 cb.reps_pool = pool; 8752 } 8753 else 8754 { 8755 cb.reps_to_cache = NULL; 8756 cb.reps_hash = NULL; 8757 cb.reps_pool = NULL; 8758 } 8759 8760 SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool)); 8761 8762 /* At this point, *NEW_REV_P has been set, so errors below won't affect 8763 the success of the commit. (See svn_fs_commit_txn().) */ 8764 8765 if (ffd->rep_sharing_allowed) 8766 { 8767 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 8768 8769 /* Write new entries to the rep-sharing database. 8770 * 8771 * We use an sqlite transaction to speed things up; 8772 * see <http://www.sqlite.org/faq.html#q19>. 8773 */ 8774 SVN_SQLITE__WITH_TXN( 8775 write_reps_to_cache(fs, cb.reps_to_cache, pool), 8776 ffd->rep_cache_db); 8777 } 8778 8779 return SVN_NO_ERROR; 8780} 8781 8782 8783svn_error_t * 8784svn_fs_fs__reserve_copy_id(const char **copy_id_p, 8785 svn_fs_t *fs, 8786 const char *txn_id, 8787 apr_pool_t *pool) 8788{ 8789 const char *cur_node_id, *cur_copy_id; 8790 char *copy_id; 8791 apr_size_t len; 8792 8793 /* First read in the current next-ids file. */ 8794 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 8795 8796 copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2); 8797 8798 len = strlen(cur_copy_id); 8799 svn_fs_fs__next_key(cur_copy_id, &len, copy_id); 8800 8801 SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool)); 8802 8803 *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL); 8804 8805 return SVN_NO_ERROR; 8806} 8807 8808/* Write out the zeroth revision for filesystem FS. */ 8809static svn_error_t * 8810write_revision_zero(svn_fs_t *fs) 8811{ 8812 const char *path_revision_zero = path_rev(fs, 0, fs->pool); 8813 apr_hash_t *proplist; 8814 svn_string_t date; 8815 8816 /* Write out a rev file for revision 0. */ 8817 SVN_ERR(svn_io_file_create(path_revision_zero, 8818 "PLAIN\nEND\nENDREP\n" 8819 "id: 0.0.r0/17\n" 8820 "type: dir\n" 8821 "count: 0\n" 8822 "text: 0 0 4 4 " 8823 "2d2977d1c96f487abe4a1e202dd03b4e\n" 8824 "cpath: /\n" 8825 "\n\n17 107\n", fs->pool)); 8826 SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool)); 8827 8828 /* Set a date on revision 0. */ 8829 date.data = svn_time_to_cstring(apr_time_now(), fs->pool); 8830 date.len = strlen(date.data); 8831 proplist = apr_hash_make(fs->pool); 8832 svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date); 8833 return set_revision_proplist(fs, 0, proplist, fs->pool); 8834} 8835 8836svn_error_t * 8837svn_fs_fs__create(svn_fs_t *fs, 8838 const char *path, 8839 apr_pool_t *pool) 8840{ 8841 int format = SVN_FS_FS__FORMAT_NUMBER; 8842 fs_fs_data_t *ffd = fs->fsap_data; 8843 8844 fs->path = apr_pstrdup(pool, path); 8845 /* See if compatibility with older versions was explicitly requested. */ 8846 if (fs->config) 8847 { 8848 if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)) 8849 format = 1; 8850 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)) 8851 format = 2; 8852 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE)) 8853 format = 3; 8854 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE)) 8855 format = 4; 8856 } 8857 ffd->format = format; 8858 8859 /* Override the default linear layout if this is a new-enough format. */ 8860 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 8861 ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR; 8862 8863 /* Create the revision data directories. */ 8864 if (ffd->max_files_per_dir) 8865 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool)); 8866 else 8867 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR, 8868 pool), 8869 pool)); 8870 8871 /* Create the revprops directory. */ 8872 if (ffd->max_files_per_dir) 8873 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool), 8874 pool)); 8875 else 8876 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, 8877 PATH_REVPROPS_DIR, 8878 pool), 8879 pool)); 8880 8881 /* Create the transaction directory. */ 8882 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR, 8883 pool), 8884 pool)); 8885 8886 /* Create the protorevs directory. */ 8887 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 8888 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR, 8889 pool), 8890 pool)); 8891 8892 /* Create the 'current' file. */ 8893 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool), 8894 (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 8895 ? "0\n" : "0 1 1\n"), 8896 pool)); 8897 SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool)); 8898 SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool)); 8899 8900 SVN_ERR(write_revision_zero(fs)); 8901 8902 /* Create the fsfs.conf file if supported. Older server versions would 8903 simply ignore the file but that might result in a different behavior 8904 than with the later releases. Also, hotcopy would ignore, i.e. not 8905 copy, a fsfs.conf with old formats. */ 8906 if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 8907 SVN_ERR(write_config(fs, pool)); 8908 8909 SVN_ERR(read_config(ffd, fs->path, pool)); 8910 8911 /* Create the min unpacked rev file. */ 8912 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 8913 SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool)); 8914 8915 /* Create the txn-current file if the repository supports 8916 the transaction sequence file. */ 8917 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 8918 { 8919 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool), 8920 "0\n", pool)); 8921 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool), 8922 "", pool)); 8923 } 8924 8925 /* This filesystem is ready. Stamp it with a format number. */ 8926 SVN_ERR(write_format(path_format(fs, pool), 8927 ffd->format, ffd->max_files_per_dir, FALSE, pool)); 8928 8929 ffd->youngest_rev_cache = 0; 8930 return SVN_NO_ERROR; 8931} 8932 8933/* Part of the recovery procedure. Return the largest revision *REV in 8934 filesystem FS. Use POOL for temporary allocation. */ 8935static svn_error_t * 8936recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool) 8937{ 8938 /* Discovering the largest revision in the filesystem would be an 8939 expensive operation if we did a readdir() or searched linearly, 8940 so we'll do a form of binary search. left is a revision that we 8941 know exists, right a revision that we know does not exist. */ 8942 apr_pool_t *iterpool; 8943 svn_revnum_t left, right = 1; 8944 8945 iterpool = svn_pool_create(pool); 8946 /* Keep doubling right, until we find a revision that doesn't exist. */ 8947 while (1) 8948 { 8949 svn_error_t *err; 8950 apr_file_t *file; 8951 8952 err = open_pack_or_rev_file(&file, fs, right, iterpool); 8953 svn_pool_clear(iterpool); 8954 8955 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8956 { 8957 svn_error_clear(err); 8958 break; 8959 } 8960 else 8961 SVN_ERR(err); 8962 8963 right <<= 1; 8964 } 8965 8966 left = right >> 1; 8967 8968 /* We know that left exists and right doesn't. Do a normal bsearch to find 8969 the last revision. */ 8970 while (left + 1 < right) 8971 { 8972 svn_revnum_t probe = left + ((right - left) / 2); 8973 svn_error_t *err; 8974 apr_file_t *file; 8975 8976 err = open_pack_or_rev_file(&file, fs, probe, iterpool); 8977 svn_pool_clear(iterpool); 8978 8979 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8980 { 8981 svn_error_clear(err); 8982 right = probe; 8983 } 8984 else 8985 { 8986 SVN_ERR(err); 8987 left = probe; 8988 } 8989 } 8990 8991 svn_pool_destroy(iterpool); 8992 8993 /* left is now the largest revision that exists. */ 8994 *rev = left; 8995 return SVN_NO_ERROR; 8996} 8997 8998/* A baton for reading a fixed amount from an open file. For 8999 recover_find_max_ids() below. */ 9000struct recover_read_from_file_baton 9001{ 9002 apr_file_t *file; 9003 apr_pool_t *pool; 9004 apr_off_t remaining; 9005}; 9006 9007/* A stream read handler used by recover_find_max_ids() below. 9008 Read and return at most BATON->REMAINING bytes from the stream, 9009 returning nothing after that to indicate EOF. */ 9010static svn_error_t * 9011read_handler_recover(void *baton, char *buffer, apr_size_t *len) 9012{ 9013 struct recover_read_from_file_baton *b = baton; 9014 svn_filesize_t bytes_to_read = *len; 9015 9016 if (b->remaining == 0) 9017 { 9018 /* Return a successful read of zero bytes to signal EOF. */ 9019 *len = 0; 9020 return SVN_NO_ERROR; 9021 } 9022 9023 if (bytes_to_read > b->remaining) 9024 bytes_to_read = b->remaining; 9025 b->remaining -= bytes_to_read; 9026 9027 return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read, 9028 len, NULL, b->pool); 9029} 9030 9031/* Part of the recovery procedure. Read the directory noderev at offset 9032 OFFSET of file REV_FILE (the revision file of revision REV of 9033 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id 9034 and copy-id of that node, if greater than the current value stored 9035 in either. Recurse into any child directories that were modified in 9036 this revision. 9037 9038 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE. 9039 9040 Perform temporary allocation in POOL. */ 9041static svn_error_t * 9042recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev, 9043 apr_file_t *rev_file, apr_off_t offset, 9044 char *max_node_id, char *max_copy_id, 9045 apr_pool_t *pool) 9046{ 9047 apr_hash_t *headers; 9048 char *value; 9049 representation_t *data_rep; 9050 struct rep_args *ra; 9051 struct recover_read_from_file_baton baton; 9052 svn_stream_t *stream; 9053 apr_hash_t *entries; 9054 apr_hash_index_t *hi; 9055 apr_pool_t *iterpool; 9056 9057 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9058 SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE, 9059 pool), 9060 pool)); 9061 9062 /* Check that this is a directory. It should be. */ 9063 value = svn_hash_gets(headers, HEADER_TYPE); 9064 if (value == NULL || strcmp(value, KIND_DIR) != 0) 9065 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9066 _("Recovery encountered a non-directory node")); 9067 9068 /* Get the data location. No data location indicates an empty directory. */ 9069 value = svn_hash_gets(headers, HEADER_TEXT); 9070 if (!value) 9071 return SVN_NO_ERROR; 9072 SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool)); 9073 9074 /* If the directory's data representation wasn't changed in this revision, 9075 we've already scanned the directory's contents for noderevs, so we don't 9076 need to again. This will occur if a property is changed on a directory 9077 without changing the directory's contents. */ 9078 if (data_rep->revision != rev) 9079 return SVN_NO_ERROR; 9080 9081 /* We could use get_dir_contents(), but this is much cheaper. It does 9082 rely on directory entries being stored as PLAIN reps, though. */ 9083 offset = data_rep->offset; 9084 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9085 SVN_ERR(read_rep_line(&ra, rev_file, pool)); 9086 if (ra->is_delta) 9087 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9088 _("Recovery encountered a deltified directory " 9089 "representation")); 9090 9091 /* Now create a stream that's allowed to read only as much data as is 9092 stored in the representation. */ 9093 baton.file = rev_file; 9094 baton.pool = pool; 9095 baton.remaining = data_rep->expanded_size 9096 ? data_rep->expanded_size 9097 : data_rep->size; 9098 stream = svn_stream_create(&baton, pool); 9099 svn_stream_set_read(stream, read_handler_recover); 9100 9101 /* Now read the entries from that stream. */ 9102 entries = apr_hash_make(pool); 9103 SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool)); 9104 SVN_ERR(svn_stream_close(stream)); 9105 9106 /* Now check each of the entries in our directory to find new node and 9107 copy ids, and recurse into new subdirectories. */ 9108 iterpool = svn_pool_create(pool); 9109 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 9110 { 9111 char *str_val; 9112 char *str; 9113 svn_node_kind_t kind; 9114 svn_fs_id_t *id; 9115 const char *node_id, *copy_id; 9116 apr_off_t child_dir_offset; 9117 const svn_string_t *path = svn__apr_hash_index_val(hi); 9118 9119 svn_pool_clear(iterpool); 9120 9121 str_val = apr_pstrdup(iterpool, path->data); 9122 9123 str = svn_cstring_tokenize(" ", &str_val); 9124 if (str == NULL) 9125 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9126 _("Directory entry corrupt")); 9127 9128 if (strcmp(str, KIND_FILE) == 0) 9129 kind = svn_node_file; 9130 else if (strcmp(str, KIND_DIR) == 0) 9131 kind = svn_node_dir; 9132 else 9133 { 9134 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9135 _("Directory entry corrupt")); 9136 } 9137 9138 str = svn_cstring_tokenize(" ", &str_val); 9139 if (str == NULL) 9140 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9141 _("Directory entry corrupt")); 9142 9143 id = svn_fs_fs__id_parse(str, strlen(str), iterpool); 9144 9145 if (svn_fs_fs__id_rev(id) != rev) 9146 { 9147 /* If the node wasn't modified in this revision, we've already 9148 checked the node and copy id. */ 9149 continue; 9150 } 9151 9152 node_id = svn_fs_fs__id_node_id(id); 9153 copy_id = svn_fs_fs__id_copy_id(id); 9154 9155 if (svn_fs_fs__key_compare(node_id, max_node_id) > 0) 9156 { 9157 SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE); 9158 apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE); 9159 } 9160 if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0) 9161 { 9162 SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE); 9163 apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE); 9164 } 9165 9166 if (kind == svn_node_file) 9167 continue; 9168 9169 child_dir_offset = svn_fs_fs__id_offset(id); 9170 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset, 9171 max_node_id, max_copy_id, iterpool)); 9172 } 9173 svn_pool_destroy(iterpool); 9174 9175 return SVN_NO_ERROR; 9176} 9177 9178/* Return TRUE, if for REVISION in FS, we can find the revprop pack file. 9179 * Use POOL for temporary allocations. 9180 * Set *MISSING, if the reason is a missing manifest or pack file. 9181 */ 9182static svn_boolean_t 9183packed_revprop_available(svn_boolean_t *missing, 9184 svn_fs_t *fs, 9185 svn_revnum_t revision, 9186 apr_pool_t *pool) 9187{ 9188 fs_fs_data_t *ffd = fs->fsap_data; 9189 svn_stringbuf_t *content = NULL; 9190 9191 /* try to read the manifest file */ 9192 const char *folder = path_revprops_pack_shard(fs, revision, pool); 9193 const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool); 9194 9195 svn_error_t *err = try_stringbuf_from_file(&content, 9196 missing, 9197 manifest_path, 9198 FALSE, 9199 pool); 9200 9201 /* if the manifest cannot be read, consider the pack files inaccessible 9202 * even if the file itself exists. */ 9203 if (err) 9204 { 9205 svn_error_clear(err); 9206 return FALSE; 9207 } 9208 9209 if (*missing) 9210 return FALSE; 9211 9212 /* parse manifest content until we find the entry for REVISION. 9213 * Revision 0 is never packed. */ 9214 revision = revision < ffd->max_files_per_dir 9215 ? revision - 1 9216 : revision % ffd->max_files_per_dir; 9217 while (content->data) 9218 { 9219 char *next = strchr(content->data, '\n'); 9220 if (next) 9221 { 9222 *next = 0; 9223 ++next; 9224 } 9225 9226 if (revision-- == 0) 9227 { 9228 /* the respective pack file must exist (and be a file) */ 9229 svn_node_kind_t kind; 9230 err = svn_io_check_path(svn_dirent_join(folder, content->data, 9231 pool), 9232 &kind, pool); 9233 if (err) 9234 { 9235 svn_error_clear(err); 9236 return FALSE; 9237 } 9238 9239 *missing = kind == svn_node_none; 9240 return kind == svn_node_file; 9241 } 9242 9243 content->data = next; 9244 } 9245 9246 return FALSE; 9247} 9248 9249/* Baton used for recover_body below. */ 9250struct recover_baton { 9251 svn_fs_t *fs; 9252 svn_cancel_func_t cancel_func; 9253 void *cancel_baton; 9254}; 9255 9256/* The work-horse for svn_fs_fs__recover, called with the FS 9257 write lock. This implements the svn_fs_fs__with_write_lock() 9258 'body' callback type. BATON is a 'struct recover_baton *'. */ 9259static svn_error_t * 9260recover_body(void *baton, apr_pool_t *pool) 9261{ 9262 struct recover_baton *b = baton; 9263 svn_fs_t *fs = b->fs; 9264 fs_fs_data_t *ffd = fs->fsap_data; 9265 svn_revnum_t max_rev; 9266 char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE]; 9267 char *next_node_id = NULL, *next_copy_id = NULL; 9268 svn_revnum_t youngest_rev; 9269 svn_node_kind_t youngest_revprops_kind; 9270 9271 /* Lose potentially corrupted data in temp files */ 9272 SVN_ERR(cleanup_revprop_namespace(fs)); 9273 9274 /* We need to know the largest revision in the filesystem. */ 9275 SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool)); 9276 9277 /* Get the expected youngest revision */ 9278 SVN_ERR(get_youngest(&youngest_rev, fs->path, pool)); 9279 9280 /* Policy note: 9281 9282 Since the revprops file is written after the revs file, the true 9283 maximum available revision is the youngest one for which both are 9284 present. That's probably the same as the max_rev we just found, 9285 but if it's not, we could, in theory, repeatedly decrement 9286 max_rev until we find a revision that has both a revs and 9287 revprops file, then write db/current with that. 9288 9289 But we choose not to. If a repository is so corrupt that it's 9290 missing at least one revprops file, we shouldn't assume that the 9291 youngest revision for which both the revs and revprops files are 9292 present is healthy. In other words, we're willing to recover 9293 from a missing or out-of-date db/current file, because db/current 9294 is truly redundant -- it's basically a cache so we don't have to 9295 find max_rev each time, albeit a cache with unusual semantics, 9296 since it also officially defines when a revision goes live. But 9297 if we're missing more than the cache, it's time to back out and 9298 let the admin reconstruct things by hand: correctness at that 9299 point may depend on external things like checking a commit email 9300 list, looking in particular working copies, etc. 9301 9302 This policy matches well with a typical naive backup scenario. 9303 Say you're rsyncing your FSFS repository nightly to the same 9304 location. Once revs and revprops are written, you've got the 9305 maximum rev; if the backup should bomb before db/current is 9306 written, then db/current could stay arbitrarily out-of-date, but 9307 we can still recover. It's a small window, but we might as well 9308 do what we can. */ 9309 9310 /* Even if db/current were missing, it would be created with 0 by 9311 get_youngest(), so this conditional remains valid. */ 9312 if (youngest_rev > max_rev) 9313 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9314 _("Expected current rev to be <= %ld " 9315 "but found %ld"), max_rev, youngest_rev); 9316 9317 /* We only need to search for maximum IDs for old FS formats which 9318 se global ID counters. */ 9319 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 9320 { 9321 /* Next we need to find the maximum node id and copy id in use across the 9322 filesystem. Unfortunately, the only way we can get this information 9323 is to scan all the noderevs of all the revisions and keep track as 9324 we go along. */ 9325 svn_revnum_t rev; 9326 apr_pool_t *iterpool = svn_pool_create(pool); 9327 char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0"; 9328 apr_size_t len; 9329 9330 for (rev = 0; rev <= max_rev; rev++) 9331 { 9332 apr_file_t *rev_file; 9333 apr_off_t root_offset; 9334 9335 svn_pool_clear(iterpool); 9336 9337 if (b->cancel_func) 9338 SVN_ERR(b->cancel_func(b->cancel_baton)); 9339 9340 SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool)); 9341 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev, 9342 iterpool)); 9343 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset, 9344 max_node_id, max_copy_id, iterpool)); 9345 SVN_ERR(svn_io_file_close(rev_file, iterpool)); 9346 } 9347 svn_pool_destroy(iterpool); 9348 9349 /* Now that we finally have the maximum revision, node-id and copy-id, we 9350 can bump the two ids to get the next of each. */ 9351 len = strlen(max_node_id); 9352 svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf); 9353 next_node_id = next_node_id_buf; 9354 len = strlen(max_copy_id); 9355 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf); 9356 next_copy_id = next_copy_id_buf; 9357 } 9358 9359 /* Before setting current, verify that there is a revprops file 9360 for the youngest revision. (Issue #2992) */ 9361 SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool), 9362 &youngest_revprops_kind, pool)); 9363 if (youngest_revprops_kind == svn_node_none) 9364 { 9365 svn_boolean_t missing = TRUE; 9366 if (!packed_revprop_available(&missing, fs, max_rev, pool)) 9367 { 9368 if (missing) 9369 { 9370 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9371 _("Revision %ld has a revs file but no " 9372 "revprops file"), 9373 max_rev); 9374 } 9375 else 9376 { 9377 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9378 _("Revision %ld has a revs file but the " 9379 "revprops file is inaccessible"), 9380 max_rev); 9381 } 9382 } 9383 } 9384 else if (youngest_revprops_kind != svn_node_file) 9385 { 9386 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9387 _("Revision %ld has a non-file where its " 9388 "revprops file should be"), 9389 max_rev); 9390 } 9391 9392 /* Prune younger-than-(newfound-youngest) revisions from the rep 9393 cache if sharing is enabled taking care not to create the cache 9394 if it does not exist. */ 9395 if (ffd->rep_sharing_allowed) 9396 { 9397 svn_boolean_t rep_cache_exists; 9398 9399 SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool)); 9400 if (rep_cache_exists) 9401 SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool)); 9402 } 9403 9404 /* Now store the discovered youngest revision, and the next IDs if 9405 relevant, in a new 'current' file. */ 9406 return write_current(fs, max_rev, next_node_id, next_copy_id, pool); 9407} 9408 9409/* This implements the fs_library_vtable_t.recover() API. */ 9410svn_error_t * 9411svn_fs_fs__recover(svn_fs_t *fs, 9412 svn_cancel_func_t cancel_func, void *cancel_baton, 9413 apr_pool_t *pool) 9414{ 9415 struct recover_baton b; 9416 9417 /* We have no way to take out an exclusive lock in FSFS, so we're 9418 restricted as to the types of recovery we can do. Luckily, 9419 we just want to recreate the 'current' file, and we can do that just 9420 by blocking other writers. */ 9421 b.fs = fs; 9422 b.cancel_func = cancel_func; 9423 b.cancel_baton = cancel_baton; 9424 return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool); 9425} 9426 9427svn_error_t * 9428svn_fs_fs__set_uuid(svn_fs_t *fs, 9429 const char *uuid, 9430 apr_pool_t *pool) 9431{ 9432 char *my_uuid; 9433 apr_size_t my_uuid_len; 9434 const char *tmp_path; 9435 const char *uuid_path = path_uuid(fs, pool); 9436 9437 if (! uuid) 9438 uuid = svn_uuid_generate(pool); 9439 9440 /* Make sure we have a copy in FS->POOL, and append a newline. */ 9441 my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL); 9442 my_uuid_len = strlen(my_uuid); 9443 9444 SVN_ERR(svn_io_write_unique(&tmp_path, 9445 svn_dirent_dirname(uuid_path, pool), 9446 my_uuid, my_uuid_len, 9447 svn_io_file_del_none, pool)); 9448 9449 /* We use the permissions of the 'current' file, because the 'uuid' 9450 file does not exist during repository creation. */ 9451 SVN_ERR(move_into_place(tmp_path, uuid_path, 9452 svn_fs_fs__path_current(fs, pool), pool)); 9453 9454 /* Remove the newline we added, and stash the UUID. */ 9455 my_uuid[my_uuid_len - 1] = '\0'; 9456 fs->uuid = my_uuid; 9457 9458 return SVN_NO_ERROR; 9459} 9460 9461/** Node origin lazy cache. */ 9462 9463/* If directory PATH does not exist, create it and give it the same 9464 permissions as FS_path.*/ 9465svn_error_t * 9466svn_fs_fs__ensure_dir_exists(const char *path, 9467 const char *fs_path, 9468 apr_pool_t *pool) 9469{ 9470 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool); 9471 if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 9472 { 9473 svn_error_clear(err); 9474 return SVN_NO_ERROR; 9475 } 9476 SVN_ERR(err); 9477 9478 /* We successfully created a new directory. Dup the permissions 9479 from FS->path. */ 9480 return svn_io_copy_perms(fs_path, path, pool); 9481} 9482 9483/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to 9484 'svn_string_t *' node revision IDs. Use POOL for allocations. */ 9485static svn_error_t * 9486get_node_origins_from_file(svn_fs_t *fs, 9487 apr_hash_t **node_origins, 9488 const char *node_origins_file, 9489 apr_pool_t *pool) 9490{ 9491 apr_file_t *fd; 9492 svn_error_t *err; 9493 svn_stream_t *stream; 9494 9495 *node_origins = NULL; 9496 err = svn_io_file_open(&fd, node_origins_file, 9497 APR_READ, APR_OS_DEFAULT, pool); 9498 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 9499 { 9500 svn_error_clear(err); 9501 return SVN_NO_ERROR; 9502 } 9503 SVN_ERR(err); 9504 9505 stream = svn_stream_from_aprfile2(fd, FALSE, pool); 9506 *node_origins = apr_hash_make(pool); 9507 SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool)); 9508 return svn_stream_close(stream); 9509} 9510 9511svn_error_t * 9512svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id, 9513 svn_fs_t *fs, 9514 const char *node_id, 9515 apr_pool_t *pool) 9516{ 9517 apr_hash_t *node_origins; 9518 9519 *origin_id = NULL; 9520 SVN_ERR(get_node_origins_from_file(fs, &node_origins, 9521 path_node_origin(fs, node_id, pool), 9522 pool)); 9523 if (node_origins) 9524 { 9525 svn_string_t *origin_id_str = 9526 svn_hash_gets(node_origins, node_id); 9527 if (origin_id_str) 9528 *origin_id = svn_fs_fs__id_parse(origin_id_str->data, 9529 origin_id_str->len, pool); 9530 } 9531 return SVN_NO_ERROR; 9532} 9533 9534 9535/* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID 9536 pair and adds it to the NODE_ORIGINS_PATH file. */ 9537static svn_error_t * 9538set_node_origins_for_file(svn_fs_t *fs, 9539 const char *node_origins_path, 9540 const char *node_id, 9541 svn_string_t *node_rev_id, 9542 apr_pool_t *pool) 9543{ 9544 const char *path_tmp; 9545 svn_stream_t *stream; 9546 apr_hash_t *origins_hash; 9547 svn_string_t *old_node_rev_id; 9548 9549 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path, 9550 PATH_NODE_ORIGINS_DIR, 9551 pool), 9552 fs->path, pool)); 9553 9554 /* Read the previously existing origins (if any), and merge our 9555 update with it. */ 9556 SVN_ERR(get_node_origins_from_file(fs, &origins_hash, 9557 node_origins_path, pool)); 9558 if (! origins_hash) 9559 origins_hash = apr_hash_make(pool); 9560 9561 old_node_rev_id = svn_hash_gets(origins_hash, node_id); 9562 9563 if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id)) 9564 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9565 _("Node origin for '%s' exists with a different " 9566 "value (%s) than what we were about to store " 9567 "(%s)"), 9568 node_id, old_node_rev_id->data, node_rev_id->data); 9569 9570 svn_hash_sets(origins_hash, node_id, node_rev_id); 9571 9572 /* Sure, there's a race condition here. Two processes could be 9573 trying to add different cache elements to the same file at the 9574 same time, and the entries added by the first one to write will 9575 be lost. But this is just a cache of reconstructible data, so 9576 we'll accept this problem in return for not having to deal with 9577 locking overhead. */ 9578 9579 /* Create a temporary file, write out our hash, and close the file. */ 9580 SVN_ERR(svn_stream_open_unique(&stream, &path_tmp, 9581 svn_dirent_dirname(node_origins_path, pool), 9582 svn_io_file_del_none, pool, pool)); 9583 SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool)); 9584 SVN_ERR(svn_stream_close(stream)); 9585 9586 /* Rename the temp file as the real destination */ 9587 return svn_io_file_rename(path_tmp, node_origins_path, pool); 9588} 9589 9590 9591svn_error_t * 9592svn_fs_fs__set_node_origin(svn_fs_t *fs, 9593 const char *node_id, 9594 const svn_fs_id_t *node_rev_id, 9595 apr_pool_t *pool) 9596{ 9597 svn_error_t *err; 9598 const char *filename = path_node_origin(fs, node_id, pool); 9599 9600 err = set_node_origins_for_file(fs, filename, 9601 node_id, 9602 svn_fs_fs__id_unparse(node_rev_id, pool), 9603 pool); 9604 if (err && APR_STATUS_IS_EACCES(err->apr_err)) 9605 { 9606 /* It's just a cache; stop trying if I can't write. */ 9607 svn_error_clear(err); 9608 err = NULL; 9609 } 9610 return svn_error_trace(err); 9611} 9612 9613 9614svn_error_t * 9615svn_fs_fs__list_transactions(apr_array_header_t **names_p, 9616 svn_fs_t *fs, 9617 apr_pool_t *pool) 9618{ 9619 const char *txn_dir; 9620 apr_hash_t *dirents; 9621 apr_hash_index_t *hi; 9622 apr_array_header_t *names; 9623 apr_size_t ext_len = strlen(PATH_EXT_TXN); 9624 9625 names = apr_array_make(pool, 1, sizeof(const char *)); 9626 9627 /* Get the transactions directory. */ 9628 txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool); 9629 9630 /* Now find a listing of this directory. */ 9631 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 9632 9633 /* Loop through all the entries and return anything that ends with '.txn'. */ 9634 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 9635 { 9636 const char *name = svn__apr_hash_index_key(hi); 9637 apr_ssize_t klen = svn__apr_hash_index_klen(hi); 9638 const char *id; 9639 9640 /* The name must end with ".txn" to be considered a transaction. */ 9641 if ((apr_size_t) klen <= ext_len 9642 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 9643 continue; 9644 9645 /* Truncate the ".txn" extension and store the ID. */ 9646 id = apr_pstrndup(pool, name, strlen(name) - ext_len); 9647 APR_ARRAY_PUSH(names, const char *) = id; 9648 } 9649 9650 *names_p = names; 9651 9652 return SVN_NO_ERROR; 9653} 9654 9655svn_error_t * 9656svn_fs_fs__open_txn(svn_fs_txn_t **txn_p, 9657 svn_fs_t *fs, 9658 const char *name, 9659 apr_pool_t *pool) 9660{ 9661 svn_fs_txn_t *txn; 9662 svn_node_kind_t kind; 9663 transaction_t *local_txn; 9664 9665 /* First check to see if the directory exists. */ 9666 SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool)); 9667 9668 /* Did we find it? */ 9669 if (kind != svn_node_dir) 9670 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 9671 _("No such transaction '%s'"), 9672 name); 9673 9674 txn = apr_pcalloc(pool, sizeof(*txn)); 9675 9676 /* Read in the root node of this transaction. */ 9677 txn->id = apr_pstrdup(pool, name); 9678 txn->fs = fs; 9679 9680 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool)); 9681 9682 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id); 9683 9684 txn->vtable = &txn_vtable; 9685 *txn_p = txn; 9686 9687 return SVN_NO_ERROR; 9688} 9689 9690svn_error_t * 9691svn_fs_fs__txn_proplist(apr_hash_t **table_p, 9692 svn_fs_txn_t *txn, 9693 apr_pool_t *pool) 9694{ 9695 apr_hash_t *proplist = apr_hash_make(pool); 9696 SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool)); 9697 *table_p = proplist; 9698 9699 return SVN_NO_ERROR; 9700} 9701 9702svn_error_t * 9703svn_fs_fs__delete_node_revision(svn_fs_t *fs, 9704 const svn_fs_id_t *id, 9705 apr_pool_t *pool) 9706{ 9707 node_revision_t *noderev; 9708 9709 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 9710 9711 /* Delete any mutable property representation. */ 9712 if (noderev->prop_rep && noderev->prop_rep->txn_id) 9713 SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE, 9714 pool)); 9715 9716 /* Delete any mutable data representation. */ 9717 if (noderev->data_rep && noderev->data_rep->txn_id 9718 && noderev->kind == svn_node_dir) 9719 { 9720 fs_fs_data_t *ffd = fs->fsap_data; 9721 SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE, 9722 pool)); 9723 9724 /* remove the corresponding entry from the cache, if such exists */ 9725 if (ffd->txn_dir_cache) 9726 { 9727 const char *key = svn_fs_fs__id_unparse(id, pool)->data; 9728 SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool)); 9729 } 9730 } 9731 9732 return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool); 9733} 9734 9735 9736 9737/*** Revisions ***/ 9738 9739svn_error_t * 9740svn_fs_fs__revision_prop(svn_string_t **value_p, 9741 svn_fs_t *fs, 9742 svn_revnum_t rev, 9743 const char *propname, 9744 apr_pool_t *pool) 9745{ 9746 apr_hash_t *table; 9747 9748 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9749 SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool)); 9750 9751 *value_p = svn_hash_gets(table, propname); 9752 9753 return SVN_NO_ERROR; 9754} 9755 9756 9757/* Baton used for change_rev_prop_body below. */ 9758struct change_rev_prop_baton { 9759 svn_fs_t *fs; 9760 svn_revnum_t rev; 9761 const char *name; 9762 const svn_string_t *const *old_value_p; 9763 const svn_string_t *value; 9764}; 9765 9766/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS 9767 write lock. This implements the svn_fs_fs__with_write_lock() 9768 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */ 9769static svn_error_t * 9770change_rev_prop_body(void *baton, apr_pool_t *pool) 9771{ 9772 struct change_rev_prop_baton *cb = baton; 9773 apr_hash_t *table; 9774 9775 SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool)); 9776 9777 if (cb->old_value_p) 9778 { 9779 const svn_string_t *wanted_value = *cb->old_value_p; 9780 const svn_string_t *present_value = svn_hash_gets(table, cb->name); 9781 if ((!wanted_value != !present_value) 9782 || (wanted_value && present_value 9783 && !svn_string_compare(wanted_value, present_value))) 9784 { 9785 /* What we expected isn't what we found. */ 9786 return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, 9787 _("revprop '%s' has unexpected value in " 9788 "filesystem"), 9789 cb->name); 9790 } 9791 /* Fall through. */ 9792 } 9793 svn_hash_sets(table, cb->name, cb->value); 9794 9795 return set_revision_proplist(cb->fs, cb->rev, table, pool); 9796} 9797 9798svn_error_t * 9799svn_fs_fs__change_rev_prop(svn_fs_t *fs, 9800 svn_revnum_t rev, 9801 const char *name, 9802 const svn_string_t *const *old_value_p, 9803 const svn_string_t *value, 9804 apr_pool_t *pool) 9805{ 9806 struct change_rev_prop_baton cb; 9807 9808 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9809 9810 cb.fs = fs; 9811 cb.rev = rev; 9812 cb.name = name; 9813 cb.old_value_p = old_value_p; 9814 cb.value = value; 9815 9816 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool); 9817} 9818 9819 9820 9821/*** Transactions ***/ 9822 9823svn_error_t * 9824svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p, 9825 const svn_fs_id_t **base_root_id_p, 9826 svn_fs_t *fs, 9827 const char *txn_name, 9828 apr_pool_t *pool) 9829{ 9830 transaction_t *txn; 9831 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool)); 9832 *root_id_p = txn->root_id; 9833 *base_root_id_p = txn->base_id; 9834 return SVN_NO_ERROR; 9835} 9836 9837 9838/* Generic transaction operations. */ 9839 9840svn_error_t * 9841svn_fs_fs__txn_prop(svn_string_t **value_p, 9842 svn_fs_txn_t *txn, 9843 const char *propname, 9844 apr_pool_t *pool) 9845{ 9846 apr_hash_t *table; 9847 svn_fs_t *fs = txn->fs; 9848 9849 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9850 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool)); 9851 9852 *value_p = svn_hash_gets(table, propname); 9853 9854 return SVN_NO_ERROR; 9855} 9856 9857svn_error_t * 9858svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, 9859 svn_fs_t *fs, 9860 svn_revnum_t rev, 9861 apr_uint32_t flags, 9862 apr_pool_t *pool) 9863{ 9864 svn_string_t date; 9865 svn_prop_t prop; 9866 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t)); 9867 9868 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9869 9870 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool)); 9871 9872 /* Put a datestamp on the newly created txn, so we always know 9873 exactly how old it is. (This will help sysadmins identify 9874 long-abandoned txns that may need to be manually removed.) When 9875 a txn is promoted to a revision, this property will be 9876 automatically overwritten with a revision datestamp. */ 9877 date.data = svn_time_to_cstring(apr_time_now(), pool); 9878 date.len = strlen(date.data); 9879 9880 prop.name = SVN_PROP_REVISION_DATE; 9881 prop.value = &date; 9882 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9883 9884 /* Set temporary txn props that represent the requested 'flags' 9885 behaviors. */ 9886 if (flags & SVN_FS_TXN_CHECK_OOD) 9887 { 9888 prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 9889 prop.value = svn_string_create("true", pool); 9890 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9891 } 9892 9893 if (flags & SVN_FS_TXN_CHECK_LOCKS) 9894 { 9895 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 9896 prop.value = svn_string_create("true", pool); 9897 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9898 } 9899 9900 return svn_fs_fs__change_txn_props(*txn_p, props, pool); 9901} 9902 9903 9904/****** Packing FSFS shards *********/ 9905 9906/* Write a file FILENAME in directory FS_PATH, containing a single line 9907 * with the number REVNUM in ASCII decimal. Move the file into place 9908 * atomically, overwriting any existing file. 9909 * 9910 * Similar to write_current(). */ 9911static svn_error_t * 9912write_revnum_file(const char *fs_path, 9913 const char *filename, 9914 svn_revnum_t revnum, 9915 apr_pool_t *scratch_pool) 9916{ 9917 const char *final_path, *tmp_path; 9918 svn_stream_t *tmp_stream; 9919 9920 final_path = svn_dirent_join(fs_path, filename, scratch_pool); 9921 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path, 9922 svn_io_file_del_none, 9923 scratch_pool, scratch_pool)); 9924 SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum)); 9925 SVN_ERR(svn_stream_close(tmp_stream)); 9926 SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool)); 9927 return SVN_NO_ERROR; 9928} 9929 9930/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions 9931 * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations. 9932 * CANCEL_FUNC and CANCEL_BATON are what you think they are. 9933 * 9934 * If for some reason we detect a partial packing already performed, we 9935 * remove the pack file and start again. 9936 */ 9937static svn_error_t * 9938pack_rev_shard(const char *pack_file_dir, 9939 const char *shard_path, 9940 apr_int64_t shard, 9941 int max_files_per_dir, 9942 svn_cancel_func_t cancel_func, 9943 void *cancel_baton, 9944 apr_pool_t *pool) 9945{ 9946 const char *pack_file_path, *manifest_file_path; 9947 svn_stream_t *pack_stream, *manifest_stream; 9948 svn_revnum_t start_rev, end_rev, rev; 9949 apr_off_t next_offset; 9950 apr_pool_t *iterpool; 9951 9952 /* Some useful paths. */ 9953 pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool); 9954 manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool); 9955 9956 /* Remove any existing pack file for this shard, since it is incomplete. */ 9957 SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 9958 pool)); 9959 9960 /* Create the new directory and pack and manifest files. */ 9961 SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool)); 9962 SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool, 9963 pool)); 9964 SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 9965 pool, pool)); 9966 9967 start_rev = (svn_revnum_t) (shard * max_files_per_dir); 9968 end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 9969 next_offset = 0; 9970 iterpool = svn_pool_create(pool); 9971 9972 /* Iterate over the revisions in this shard, squashing them together. */ 9973 for (rev = start_rev; rev <= end_rev; rev++) 9974 { 9975 svn_stream_t *rev_stream; 9976 apr_finfo_t finfo; 9977 const char *path; 9978 9979 svn_pool_clear(iterpool); 9980 9981 /* Get the size of the file. */ 9982 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 9983 iterpool); 9984 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 9985 9986 /* Update the manifest. */ 9987 SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT 9988 "\n", next_offset)); 9989 next_offset += finfo.size; 9990 9991 /* Copy all the bits from the rev file to the end of the pack file. */ 9992 SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool)); 9993 SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream, 9994 iterpool), 9995 cancel_func, cancel_baton, iterpool)); 9996 } 9997 9998 SVN_ERR(svn_stream_close(manifest_stream)); 9999 SVN_ERR(svn_stream_close(pack_stream)); 10000 SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 10001 SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool)); 10002 SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool)); 10003 10004 svn_pool_destroy(iterpool); 10005 10006 return SVN_NO_ERROR; 10007} 10008 10009/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH 10010 * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR. 10011 * 10012 * The file sizes have already been determined and written to SIZES. 10013 * Please note that this function will be executed while the filesystem 10014 * has been locked and that revprops files will therefore not be modified 10015 * while the pack is in progress. 10016 * 10017 * COMPRESSION_LEVEL defines how well the resulting pack file shall be 10018 * compressed or whether is shall be compressed at all. TOTAL_SIZE is 10019 * a hint on which initial buffer size we should use to hold the pack file 10020 * content. 10021 * 10022 * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations 10023 * are done in SCRATCH_POOL. 10024 */ 10025static svn_error_t * 10026copy_revprops(const char *pack_file_dir, 10027 const char *pack_filename, 10028 const char *shard_path, 10029 svn_revnum_t start_rev, 10030 svn_revnum_t end_rev, 10031 apr_array_header_t *sizes, 10032 apr_size_t total_size, 10033 int compression_level, 10034 svn_cancel_func_t cancel_func, 10035 void *cancel_baton, 10036 apr_pool_t *scratch_pool) 10037{ 10038 svn_stream_t *pack_stream; 10039 apr_file_t *pack_file; 10040 svn_revnum_t rev; 10041 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10042 svn_stream_t *stream; 10043 10044 /* create empty data buffer and a write stream on top of it */ 10045 svn_stringbuf_t *uncompressed 10046 = svn_stringbuf_create_ensure(total_size, scratch_pool); 10047 svn_stringbuf_t *compressed 10048 = svn_stringbuf_create_empty(scratch_pool); 10049 pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); 10050 10051 /* write the pack file header */ 10052 SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0, 10053 sizes->nelts, iterpool)); 10054 10055 /* Some useful paths. */ 10056 SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir, 10057 pack_filename, 10058 scratch_pool), 10059 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, 10060 scratch_pool)); 10061 10062 /* Iterate over the revisions in this shard, squashing them together. */ 10063 for (rev = start_rev; rev <= end_rev; rev++) 10064 { 10065 const char *path; 10066 10067 svn_pool_clear(iterpool); 10068 10069 /* Construct the file name. */ 10070 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10071 iterpool); 10072 10073 /* Copy all the bits from the non-packed revprop file to the end of 10074 * the pack file. */ 10075 SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool)); 10076 SVN_ERR(svn_stream_copy3(stream, pack_stream, 10077 cancel_func, cancel_baton, iterpool)); 10078 } 10079 10080 /* flush stream buffers to content buffer */ 10081 SVN_ERR(svn_stream_close(pack_stream)); 10082 10083 /* compress the content (or just store it for COMPRESSION_LEVEL 0) */ 10084 SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 10085 compressed, compression_level)); 10086 10087 /* write the pack file content to disk */ 10088 stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool); 10089 SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len)); 10090 SVN_ERR(svn_stream_close(stream)); 10091 10092 svn_pool_destroy(iterpool); 10093 10094 return SVN_NO_ERROR; 10095} 10096 10097/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR 10098 * revprop files in it, create a packed shared at PACK_FILE_DIR. 10099 * 10100 * COMPRESSION_LEVEL defines how well the resulting pack file shall be 10101 * compressed or whether is shall be compressed at all. Individual pack 10102 * file containing more than one revision will be limited to a size of 10103 * MAX_PACK_SIZE bytes before compression. 10104 * 10105 * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10106 * allocations are done in SCRATCH_POOL. 10107 */ 10108static svn_error_t * 10109pack_revprops_shard(const char *pack_file_dir, 10110 const char *shard_path, 10111 apr_int64_t shard, 10112 int max_files_per_dir, 10113 apr_off_t max_pack_size, 10114 int compression_level, 10115 svn_cancel_func_t cancel_func, 10116 void *cancel_baton, 10117 apr_pool_t *scratch_pool) 10118{ 10119 const char *manifest_file_path, *pack_filename = NULL; 10120 svn_stream_t *manifest_stream; 10121 svn_revnum_t start_rev, end_rev, rev; 10122 apr_off_t total_size; 10123 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10124 apr_array_header_t *sizes; 10125 10126 /* Some useful paths. */ 10127 manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, 10128 scratch_pool); 10129 10130 /* Remove any existing pack file for this shard, since it is incomplete. */ 10131 SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 10132 scratch_pool)); 10133 10134 /* Create the new directory and manifest file stream. */ 10135 SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool)); 10136 SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 10137 scratch_pool, scratch_pool)); 10138 10139 /* revisions to handle. Special case: revision 0 */ 10140 start_rev = (svn_revnum_t) (shard * max_files_per_dir); 10141 end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 10142 if (start_rev == 0) 10143 ++start_rev; 10144 10145 /* initialize the revprop size info */ 10146 sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t)); 10147 total_size = 2 * SVN_INT64_BUFFER_SIZE; 10148 10149 /* Iterate over the revisions in this shard, determine their size and 10150 * squashing them together into pack files. */ 10151 for (rev = start_rev; rev <= end_rev; rev++) 10152 { 10153 apr_finfo_t finfo; 10154 const char *path; 10155 10156 svn_pool_clear(iterpool); 10157 10158 /* Get the size of the file. */ 10159 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10160 iterpool); 10161 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 10162 10163 /* if we already have started a pack file and this revprop cannot be 10164 * appended to it, write the previous pack file. */ 10165 if (sizes->nelts != 0 && 10166 total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size) 10167 { 10168 SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10169 start_rev, rev-1, sizes, (apr_size_t)total_size, 10170 compression_level, cancel_func, cancel_baton, 10171 iterpool)); 10172 10173 /* next pack file starts empty again */ 10174 apr_array_clear(sizes); 10175 total_size = 2 * SVN_INT64_BUFFER_SIZE; 10176 start_rev = rev; 10177 } 10178 10179 /* Update the manifest. Allocate a file name for the current pack 10180 * file if it is a new one */ 10181 if (sizes->nelts == 0) 10182 pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev); 10183 10184 SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n", 10185 pack_filename)); 10186 10187 /* add to list of files to put into the current pack file */ 10188 APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size; 10189 total_size += SVN_INT64_BUFFER_SIZE + finfo.size; 10190 } 10191 10192 /* write the last pack file */ 10193 if (sizes->nelts != 0) 10194 SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10195 start_rev, rev-1, sizes, (apr_size_t)total_size, 10196 compression_level, cancel_func, cancel_baton, 10197 iterpool)); 10198 10199 /* flush the manifest file and update permissions */ 10200 SVN_ERR(svn_stream_close(manifest_stream)); 10201 SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 10202 10203 svn_pool_destroy(iterpool); 10204 10205 return SVN_NO_ERROR; 10206} 10207 10208/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly 10209 * MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the 10210 * revprop file for revision 0. 10211 * 10212 * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10213 * allocations are done in SCRATCH_POOL. 10214 */ 10215static svn_error_t * 10216delete_revprops_shard(const char *shard_path, 10217 apr_int64_t shard, 10218 int max_files_per_dir, 10219 svn_cancel_func_t cancel_func, 10220 void *cancel_baton, 10221 apr_pool_t *scratch_pool) 10222{ 10223 if (shard == 0) 10224 { 10225 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10226 int i; 10227 10228 /* delete all files except the one for revision 0 */ 10229 for (i = 1; i < max_files_per_dir; ++i) 10230 { 10231 const char *path = svn_dirent_join(shard_path, 10232 apr_psprintf(iterpool, "%d", i), 10233 iterpool); 10234 if (cancel_func) 10235 SVN_ERR((*cancel_func)(cancel_baton)); 10236 10237 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 10238 svn_pool_clear(iterpool); 10239 } 10240 10241 svn_pool_destroy(iterpool); 10242 } 10243 else 10244 SVN_ERR(svn_io_remove_dir2(shard_path, TRUE, 10245 cancel_func, cancel_baton, scratch_pool)); 10246 10247 return SVN_NO_ERROR; 10248} 10249 10250/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and 10251 * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL 10252 * for allocations. REVPROPS_DIR will be NULL if revprop packing is not 10253 * supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that 10254 * case. 10255 * 10256 * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly 10257 * NOTIFY_FUNC and NOTIFY_BATON. 10258 * 10259 * If for some reason we detect a partial packing already performed, we 10260 * remove the pack file and start again. 10261 */ 10262static svn_error_t * 10263pack_shard(const char *revs_dir, 10264 const char *revsprops_dir, 10265 const char *fs_path, 10266 apr_int64_t shard, 10267 int max_files_per_dir, 10268 apr_off_t max_pack_size, 10269 int compression_level, 10270 svn_fs_pack_notify_t notify_func, 10271 void *notify_baton, 10272 svn_cancel_func_t cancel_func, 10273 void *cancel_baton, 10274 apr_pool_t *pool) 10275{ 10276 const char *rev_shard_path, *rev_pack_file_dir; 10277 const char *revprops_shard_path, *revprops_pack_file_dir; 10278 10279 /* Notify caller we're starting to pack this shard. */ 10280 if (notify_func) 10281 SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start, 10282 pool)); 10283 10284 /* Some useful paths. */ 10285 rev_pack_file_dir = svn_dirent_join(revs_dir, 10286 apr_psprintf(pool, 10287 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10288 shard), 10289 pool); 10290 rev_shard_path = svn_dirent_join(revs_dir, 10291 apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10292 pool); 10293 10294 /* pack the revision content */ 10295 SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path, 10296 shard, max_files_per_dir, 10297 cancel_func, cancel_baton, pool)); 10298 10299 /* if enabled, pack the revprops in an equivalent way */ 10300 if (revsprops_dir) 10301 { 10302 revprops_pack_file_dir = svn_dirent_join(revsprops_dir, 10303 apr_psprintf(pool, 10304 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10305 shard), 10306 pool); 10307 revprops_shard_path = svn_dirent_join(revsprops_dir, 10308 apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10309 pool); 10310 10311 SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path, 10312 shard, max_files_per_dir, 10313 (int)(0.9 * max_pack_size), 10314 compression_level, 10315 cancel_func, cancel_baton, pool)); 10316 } 10317 10318 /* Update the min-unpacked-rev file to reflect our newly packed shard. 10319 * (This doesn't update ffd->min_unpacked_rev. That will be updated by 10320 * update_min_unpacked_rev() when necessary.) */ 10321 SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV, 10322 (svn_revnum_t)((shard + 1) * max_files_per_dir), 10323 pool)); 10324 10325 /* Finally, remove the existing shard directories. */ 10326 SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE, 10327 cancel_func, cancel_baton, pool)); 10328 if (revsprops_dir) 10329 SVN_ERR(delete_revprops_shard(revprops_shard_path, 10330 shard, max_files_per_dir, 10331 cancel_func, cancel_baton, pool)); 10332 10333 /* Notify caller we're starting to pack this shard. */ 10334 if (notify_func) 10335 SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end, 10336 pool)); 10337 10338 return SVN_NO_ERROR; 10339} 10340 10341struct pack_baton 10342{ 10343 svn_fs_t *fs; 10344 svn_fs_pack_notify_t notify_func; 10345 void *notify_baton; 10346 svn_cancel_func_t cancel_func; 10347 void *cancel_baton; 10348}; 10349 10350 10351/* The work-horse for svn_fs_fs__pack, called with the FS write lock. 10352 This implements the svn_fs_fs__with_write_lock() 'body' callback 10353 type. BATON is a 'struct pack_baton *'. 10354 10355 WARNING: if you add a call to this function, please note: 10356 The code currently assumes that any piece of code running with 10357 the write-lock set can rely on the ffd->min_unpacked_rev and 10358 ffd->min_unpacked_revprop caches to be up-to-date (and, by 10359 extension, on not having to use a retry when calling 10360 svn_fs_fs__path_rev_absolute() and friends). If you add a call 10361 to this function, consider whether you have to call 10362 update_min_unpacked_rev(). 10363 See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith 10364 */ 10365static svn_error_t * 10366pack_body(void *baton, 10367 apr_pool_t *pool) 10368{ 10369 struct pack_baton *pb = baton; 10370 fs_fs_data_t ffd = {0}; 10371 apr_int64_t completed_shards; 10372 apr_int64_t i; 10373 svn_revnum_t youngest; 10374 apr_pool_t *iterpool; 10375 const char *rev_data_path; 10376 const char *revprops_data_path = NULL; 10377 10378 /* read repository settings */ 10379 SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir, 10380 path_format(pb->fs, pool), pool)); 10381 SVN_ERR(check_format(ffd.format)); 10382 SVN_ERR(read_config(&ffd, pb->fs->path, pool)); 10383 10384 /* If the repository isn't a new enough format, we don't support packing. 10385 Return a friendly error to that effect. */ 10386 if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT) 10387 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 10388 _("FSFS format (%d) too old to pack; please upgrade the filesystem."), 10389 ffd.format); 10390 10391 /* If we aren't using sharding, we can't do any packing, so quit. */ 10392 if (!ffd.max_files_per_dir) 10393 return SVN_NO_ERROR; 10394 10395 SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev, 10396 path_min_unpacked_rev(pb->fs, pool), 10397 pool)); 10398 10399 SVN_ERR(get_youngest(&youngest, pb->fs->path, pool)); 10400 completed_shards = (youngest + 1) / ffd.max_files_per_dir; 10401 10402 /* See if we've already completed all possible shards thus far. */ 10403 if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir)) 10404 return SVN_NO_ERROR; 10405 10406 rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool); 10407 if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 10408 revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR, 10409 pool); 10410 10411 iterpool = svn_pool_create(pool); 10412 for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir; 10413 i < completed_shards; 10414 i++) 10415 { 10416 svn_pool_clear(iterpool); 10417 10418 if (pb->cancel_func) 10419 SVN_ERR(pb->cancel_func(pb->cancel_baton)); 10420 10421 SVN_ERR(pack_shard(rev_data_path, revprops_data_path, 10422 pb->fs->path, i, ffd.max_files_per_dir, 10423 ffd.revprop_pack_size, 10424 ffd.compress_packed_revprops 10425 ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 10426 : SVN_DELTA_COMPRESSION_LEVEL_NONE, 10427 pb->notify_func, pb->notify_baton, 10428 pb->cancel_func, pb->cancel_baton, iterpool)); 10429 } 10430 10431 svn_pool_destroy(iterpool); 10432 return SVN_NO_ERROR; 10433} 10434 10435svn_error_t * 10436svn_fs_fs__pack(svn_fs_t *fs, 10437 svn_fs_pack_notify_t notify_func, 10438 void *notify_baton, 10439 svn_cancel_func_t cancel_func, 10440 void *cancel_baton, 10441 apr_pool_t *pool) 10442{ 10443 struct pack_baton pb = { 0 }; 10444 pb.fs = fs; 10445 pb.notify_func = notify_func; 10446 pb.notify_baton = notify_baton; 10447 pb.cancel_func = cancel_func; 10448 pb.cancel_baton = cancel_baton; 10449 return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool); 10450} 10451 10452 10453/** Verifying. **/ 10454 10455/* Baton type expected by verify_walker(). The purpose is to reuse open 10456 * rev / pack file handles between calls. Its contents need to be cleaned 10457 * periodically to limit resource usage. 10458 */ 10459typedef struct verify_walker_baton_t 10460{ 10461 /* number of calls to verify_walker() since the last clean */ 10462 int iteration_count; 10463 10464 /* number of files opened since the last clean */ 10465 int file_count; 10466 10467 /* progress notification callback to invoke periodically (may be NULL) */ 10468 svn_fs_progress_notify_func_t notify_func; 10469 10470 /* baton to use with NOTIFY_FUNC */ 10471 void *notify_baton; 10472 10473 /* remember the last revision for which we called notify_func */ 10474 svn_revnum_t last_notified_revision; 10475 10476 /* current file handle (or NULL) */ 10477 apr_file_t *file_hint; 10478 10479 /* corresponding revision (or SVN_INVALID_REVNUM) */ 10480 svn_revnum_t rev_hint; 10481 10482 /* pool to use for the file handles etc. */ 10483 apr_pool_t *pool; 10484} verify_walker_baton_t; 10485 10486/* Used by svn_fs_fs__verify(). 10487 Implements svn_fs_fs__walk_rep_reference().walker. */ 10488static svn_error_t * 10489verify_walker(representation_t *rep, 10490 void *baton, 10491 svn_fs_t *fs, 10492 apr_pool_t *scratch_pool) 10493{ 10494 struct rep_state *rs; 10495 struct rep_args *rep_args; 10496 10497 if (baton) 10498 { 10499 verify_walker_baton_t *walker_baton = baton; 10500 apr_file_t * previous_file; 10501 10502 /* notify and free resources periodically */ 10503 if ( walker_baton->iteration_count > 1000 10504 || walker_baton->file_count > 16) 10505 { 10506 if ( walker_baton->notify_func 10507 && rep->revision != walker_baton->last_notified_revision) 10508 { 10509 walker_baton->notify_func(rep->revision, 10510 walker_baton->notify_baton, 10511 scratch_pool); 10512 walker_baton->last_notified_revision = rep->revision; 10513 } 10514 10515 svn_pool_clear(walker_baton->pool); 10516 10517 walker_baton->iteration_count = 0; 10518 walker_baton->file_count = 0; 10519 walker_baton->file_hint = NULL; 10520 walker_baton->rev_hint = SVN_INVALID_REVNUM; 10521 } 10522 10523 /* access the repo data */ 10524 previous_file = walker_baton->file_hint; 10525 SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint, 10526 &walker_baton->rev_hint, rep, fs, 10527 walker_baton->pool)); 10528 10529 /* update resource usage counters */ 10530 walker_baton->iteration_count++; 10531 if (previous_file != walker_baton->file_hint) 10532 walker_baton->file_count++; 10533 } 10534 else 10535 { 10536 /* ### Should this be using read_rep_line() directly? */ 10537 SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs, 10538 scratch_pool)); 10539 } 10540 10541 return SVN_NO_ERROR; 10542} 10543 10544svn_error_t * 10545svn_fs_fs__verify(svn_fs_t *fs, 10546 svn_revnum_t start, 10547 svn_revnum_t end, 10548 svn_fs_progress_notify_func_t notify_func, 10549 void *notify_baton, 10550 svn_cancel_func_t cancel_func, 10551 void *cancel_baton, 10552 apr_pool_t *pool) 10553{ 10554 fs_fs_data_t *ffd = fs->fsap_data; 10555 svn_boolean_t exists; 10556 svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */ 10557 10558 if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) 10559 return SVN_NO_ERROR; 10560 10561 /* Input validation. */ 10562 if (! SVN_IS_VALID_REVNUM(start)) 10563 start = 0; 10564 if (! SVN_IS_VALID_REVNUM(end)) 10565 end = youngest; 10566 SVN_ERR(ensure_revision_exists(fs, start, pool)); 10567 SVN_ERR(ensure_revision_exists(fs, end, pool)); 10568 10569 /* rep-cache verification. */ 10570 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 10571 if (exists) 10572 { 10573 /* provide a baton to allow the reuse of open file handles between 10574 iterations (saves 2/3 of OS level file operations). */ 10575 verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); 10576 baton->rev_hint = SVN_INVALID_REVNUM; 10577 baton->pool = svn_pool_create(pool); 10578 baton->last_notified_revision = SVN_INVALID_REVNUM; 10579 baton->notify_func = notify_func; 10580 baton->notify_baton = notify_baton; 10581 10582 /* tell the user that we are now ready to do *something* */ 10583 if (notify_func) 10584 notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool); 10585 10586 /* Do not attempt to walk the rep-cache database if its file does 10587 not exist, since doing so would create it --- which may confuse 10588 the administrator. Don't take any lock. */ 10589 SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end, 10590 verify_walker, baton, 10591 cancel_func, cancel_baton, 10592 pool)); 10593 10594 /* walker resource cleanup */ 10595 svn_pool_destroy(baton->pool); 10596 } 10597 10598 return SVN_NO_ERROR; 10599} 10600 10601 10602/** Hotcopy. **/ 10603 10604/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at 10605 * the destination and do not differ in terms of kind, size, and mtime. */ 10606static svn_error_t * 10607hotcopy_io_dir_file_copy(const char *src_path, 10608 const char *dst_path, 10609 const char *file, 10610 apr_pool_t *scratch_pool) 10611{ 10612 const svn_io_dirent2_t *src_dirent; 10613 const svn_io_dirent2_t *dst_dirent; 10614 const char *src_target; 10615 const char *dst_target; 10616 10617 /* Does the destination already exist? If not, we must copy it. */ 10618 dst_target = svn_dirent_join(dst_path, file, scratch_pool); 10619 SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE, 10620 scratch_pool, scratch_pool)); 10621 if (dst_dirent->kind != svn_node_none) 10622 { 10623 /* If the destination's stat information indicates that the file 10624 * is equal to the source, don't bother copying the file again. */ 10625 src_target = svn_dirent_join(src_path, file, scratch_pool); 10626 SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE, 10627 scratch_pool, scratch_pool)); 10628 if (src_dirent->kind == dst_dirent->kind && 10629 src_dirent->special == dst_dirent->special && 10630 src_dirent->filesize == dst_dirent->filesize && 10631 src_dirent->mtime <= dst_dirent->mtime) 10632 return SVN_NO_ERROR; 10633 } 10634 10635 return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file, 10636 scratch_pool)); 10637} 10638 10639/* Set *NAME_P to the UTF-8 representation of directory entry NAME. 10640 * NAME is in the internal encoding used by APR; PARENT is in 10641 * UTF-8 and in internal (not local) style. 10642 * 10643 * Use PARENT only for generating an error string if the conversion 10644 * fails because NAME could not be represented in UTF-8. In that 10645 * case, return a two-level error in which the outer error's message 10646 * mentions PARENT, but the inner error's message does not mention 10647 * NAME (except possibly in hex) since NAME may not be printable. 10648 * Such a compound error at least allows the user to go looking in the 10649 * right directory for the problem. 10650 * 10651 * If there is any other error, just return that error directly. 10652 * 10653 * If there is any error, the effect on *NAME_P is undefined. 10654 * 10655 * *NAME_P and NAME may refer to the same storage. 10656 */ 10657static svn_error_t * 10658entry_name_to_utf8(const char **name_p, 10659 const char *name, 10660 const char *parent, 10661 apr_pool_t *pool) 10662{ 10663 svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); 10664 if (err && err->apr_err == APR_EINVAL) 10665 { 10666 return svn_error_createf(err->apr_err, err, 10667 _("Error converting entry " 10668 "in directory '%s' to UTF-8"), 10669 svn_dirent_local_style(parent, pool)); 10670 } 10671 return err; 10672} 10673 10674/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that 10675 * exist in the destination and do not differ from the source in terms of 10676 * kind, size, and mtime. */ 10677static svn_error_t * 10678hotcopy_io_copy_dir_recursively(const char *src, 10679 const char *dst_parent, 10680 const char *dst_basename, 10681 svn_boolean_t copy_perms, 10682 svn_cancel_func_t cancel_func, 10683 void *cancel_baton, 10684 apr_pool_t *pool) 10685{ 10686 svn_node_kind_t kind; 10687 apr_status_t status; 10688 const char *dst_path; 10689 apr_dir_t *this_dir; 10690 apr_finfo_t this_entry; 10691 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 10692 10693 /* Make a subpool for recursion */ 10694 apr_pool_t *subpool = svn_pool_create(pool); 10695 10696 /* The 'dst_path' is simply dst_parent/dst_basename */ 10697 dst_path = svn_dirent_join(dst_parent, dst_basename, pool); 10698 10699 /* Sanity checks: SRC and DST_PARENT are directories, and 10700 DST_BASENAME doesn't already exist in DST_PARENT. */ 10701 SVN_ERR(svn_io_check_path(src, &kind, subpool)); 10702 if (kind != svn_node_dir) 10703 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10704 _("Source '%s' is not a directory"), 10705 svn_dirent_local_style(src, pool)); 10706 10707 SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); 10708 if (kind != svn_node_dir) 10709 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10710 _("Destination '%s' is not a directory"), 10711 svn_dirent_local_style(dst_parent, pool)); 10712 10713 SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); 10714 10715 /* Create the new directory. */ 10716 /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ 10717 SVN_ERR(svn_io_make_dir_recursively(dst_path, pool)); 10718 10719 /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ 10720 SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); 10721 10722 for (status = apr_dir_read(&this_entry, flags, this_dir); 10723 status == APR_SUCCESS; 10724 status = apr_dir_read(&this_entry, flags, this_dir)) 10725 { 10726 if ((this_entry.name[0] == '.') 10727 && ((this_entry.name[1] == '\0') 10728 || ((this_entry.name[1] == '.') 10729 && (this_entry.name[2] == '\0')))) 10730 { 10731 continue; 10732 } 10733 else 10734 { 10735 const char *entryname_utf8; 10736 10737 if (cancel_func) 10738 SVN_ERR(cancel_func(cancel_baton)); 10739 10740 SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, 10741 src, subpool)); 10742 if (this_entry.filetype == APR_REG) /* regular file */ 10743 { 10744 SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8, 10745 subpool)); 10746 } 10747 else if (this_entry.filetype == APR_LNK) /* symlink */ 10748 { 10749 const char *src_target = svn_dirent_join(src, entryname_utf8, 10750 subpool); 10751 const char *dst_target = svn_dirent_join(dst_path, 10752 entryname_utf8, 10753 subpool); 10754 SVN_ERR(svn_io_copy_link(src_target, dst_target, 10755 subpool)); 10756 } 10757 else if (this_entry.filetype == APR_DIR) /* recurse */ 10758 { 10759 const char *src_target; 10760 10761 /* Prevent infinite recursion by filtering off our 10762 newly created destination path. */ 10763 if (strcmp(src, dst_parent) == 0 10764 && strcmp(entryname_utf8, dst_basename) == 0) 10765 continue; 10766 10767 src_target = svn_dirent_join(src, entryname_utf8, subpool); 10768 SVN_ERR(hotcopy_io_copy_dir_recursively(src_target, 10769 dst_path, 10770 entryname_utf8, 10771 copy_perms, 10772 cancel_func, 10773 cancel_baton, 10774 subpool)); 10775 } 10776 /* ### support other APR node types someday?? */ 10777 10778 } 10779 } 10780 10781 if (! (APR_STATUS_IS_ENOENT(status))) 10782 return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 10783 svn_dirent_local_style(src, pool)); 10784 10785 status = apr_dir_close(this_dir); 10786 if (status) 10787 return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 10788 svn_dirent_local_style(src, pool)); 10789 10790 /* Free any memory used by recursion */ 10791 svn_pool_destroy(subpool); 10792 10793 return SVN_NO_ERROR; 10794} 10795 10796/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR 10797 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR. 10798 * Use SCRATCH_POOL for temporary allocations. */ 10799static svn_error_t * 10800hotcopy_copy_shard_file(const char *src_subdir, 10801 const char *dst_subdir, 10802 svn_revnum_t rev, 10803 int max_files_per_dir, 10804 apr_pool_t *scratch_pool) 10805{ 10806 const char *src_subdir_shard = src_subdir, 10807 *dst_subdir_shard = dst_subdir; 10808 10809 if (max_files_per_dir) 10810 { 10811 const char *shard = apr_psprintf(scratch_pool, "%ld", 10812 rev / max_files_per_dir); 10813 src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool); 10814 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10815 10816 if (rev % max_files_per_dir == 0) 10817 { 10818 SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool)); 10819 SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard, 10820 scratch_pool)); 10821 } 10822 } 10823 10824 SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard, 10825 apr_psprintf(scratch_pool, "%ld", rev), 10826 scratch_pool)); 10827 return SVN_NO_ERROR; 10828} 10829 10830 10831/* Copy a packed shard containing revision REV, and which contains 10832 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS. 10833 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS. 10834 * Do not re-copy data which already exists in DST_FS. 10835 * Use SCRATCH_POOL for temporary allocations. */ 10836static svn_error_t * 10837hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev, 10838 svn_fs_t *src_fs, 10839 svn_fs_t *dst_fs, 10840 svn_revnum_t rev, 10841 int max_files_per_dir, 10842 apr_pool_t *scratch_pool) 10843{ 10844 const char *src_subdir; 10845 const char *dst_subdir; 10846 const char *packed_shard; 10847 const char *src_subdir_packed_shard; 10848 svn_revnum_t revprop_rev; 10849 apr_pool_t *iterpool; 10850 fs_fs_data_t *src_ffd = src_fs->fsap_data; 10851 10852 /* Copy the packed shard. */ 10853 src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool); 10854 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool); 10855 packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10856 rev / max_files_per_dir); 10857 src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10858 scratch_pool); 10859 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10860 dst_subdir, packed_shard, 10861 TRUE /* copy_perms */, 10862 NULL /* cancel_func */, NULL, 10863 scratch_pool)); 10864 10865 /* Copy revprops belonging to revisions in this pack. */ 10866 src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10867 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10868 10869 if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 10870 || src_ffd->min_unpacked_rev < rev + max_files_per_dir) 10871 { 10872 /* copy unpacked revprops rev by rev */ 10873 iterpool = svn_pool_create(scratch_pool); 10874 for (revprop_rev = rev; 10875 revprop_rev < rev + max_files_per_dir; 10876 revprop_rev++) 10877 { 10878 svn_pool_clear(iterpool); 10879 10880 SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10881 revprop_rev, max_files_per_dir, 10882 iterpool)); 10883 } 10884 svn_pool_destroy(iterpool); 10885 } 10886 else 10887 { 10888 /* revprop for revision 0 will never be packed */ 10889 if (rev == 0) 10890 SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10891 0, max_files_per_dir, 10892 scratch_pool)); 10893 10894 /* packed revprops folder */ 10895 packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10896 rev / max_files_per_dir); 10897 src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10898 scratch_pool); 10899 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10900 dst_subdir, packed_shard, 10901 TRUE /* copy_perms */, 10902 NULL /* cancel_func */, NULL, 10903 scratch_pool)); 10904 } 10905 10906 /* If necessary, update the min-unpacked rev file in the hotcopy. */ 10907 if (*dst_min_unpacked_rev < rev + max_files_per_dir) 10908 { 10909 *dst_min_unpacked_rev = rev + max_files_per_dir; 10910 SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV, 10911 *dst_min_unpacked_rev, 10912 scratch_pool)); 10913 } 10914 10915 return SVN_NO_ERROR; 10916} 10917 10918/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current' 10919 * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST. 10920 * Use SCRATCH_POOL for temporary allocations. */ 10921static svn_error_t * 10922hotcopy_update_current(svn_revnum_t *dst_youngest, 10923 svn_fs_t *dst_fs, 10924 svn_revnum_t new_youngest, 10925 apr_pool_t *scratch_pool) 10926{ 10927 char next_node_id[MAX_KEY_SIZE] = "0"; 10928 char next_copy_id[MAX_KEY_SIZE] = "0"; 10929 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 10930 10931 if (*dst_youngest >= new_youngest) 10932 return SVN_NO_ERROR; 10933 10934 /* If necessary, get new current next_node and next_copy IDs. */ 10935 if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 10936 { 10937 apr_off_t root_offset; 10938 apr_file_t *rev_file; 10939 char max_node_id[MAX_KEY_SIZE] = "0"; 10940 char max_copy_id[MAX_KEY_SIZE] = "0"; 10941 apr_size_t len; 10942 10943 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 10944 SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool)); 10945 10946 SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest, 10947 scratch_pool)); 10948 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, 10949 dst_fs, new_youngest, scratch_pool)); 10950 SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file, 10951 root_offset, max_node_id, max_copy_id, 10952 scratch_pool)); 10953 SVN_ERR(svn_io_file_close(rev_file, scratch_pool)); 10954 10955 /* We store the _next_ ids. */ 10956 len = strlen(max_node_id); 10957 svn_fs_fs__next_key(max_node_id, &len, next_node_id); 10958 len = strlen(max_copy_id); 10959 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id); 10960 } 10961 10962 /* Update 'current'. */ 10963 SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id, 10964 scratch_pool)); 10965 10966 *dst_youngest = new_youngest; 10967 10968 return SVN_NO_ERROR; 10969} 10970 10971 10972/* Remove revision or revprop files between START_REV (inclusive) and 10973 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume 10974 * sharding as per MAX_FILES_PER_DIR. 10975 * Use SCRATCH_POOL for temporary allocations. */ 10976static svn_error_t * 10977hotcopy_remove_files(svn_fs_t *dst_fs, 10978 const char *dst_subdir, 10979 svn_revnum_t start_rev, 10980 svn_revnum_t end_rev, 10981 int max_files_per_dir, 10982 apr_pool_t *scratch_pool) 10983{ 10984 const char *shard; 10985 const char *dst_subdir_shard; 10986 svn_revnum_t rev; 10987 apr_pool_t *iterpool; 10988 10989 /* Pre-compute paths for initial shard. */ 10990 shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir); 10991 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10992 10993 iterpool = svn_pool_create(scratch_pool); 10994 for (rev = start_rev; rev < end_rev; rev++) 10995 { 10996 const char *path; 10997 svn_pool_clear(iterpool); 10998 10999 /* If necessary, update paths for shard. */ 11000 if (rev != start_rev && rev % max_files_per_dir == 0) 11001 { 11002 shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir); 11003 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 11004 } 11005 11006 /* remove files for REV */ 11007 path = svn_dirent_join(dst_subdir_shard, 11008 apr_psprintf(iterpool, "%ld", rev), 11009 iterpool); 11010 11011 /* Make the rev file writable and remove it. */ 11012 SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool)); 11013 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 11014 } 11015 11016 svn_pool_destroy(iterpool); 11017 11018 return SVN_NO_ERROR; 11019} 11020 11021/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive) 11022 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 11023 * Use SCRATCH_POOL for temporary allocations. */ 11024static svn_error_t * 11025hotcopy_remove_rev_files(svn_fs_t *dst_fs, 11026 svn_revnum_t start_rev, 11027 svn_revnum_t end_rev, 11028 int max_files_per_dir, 11029 apr_pool_t *scratch_pool) 11030{ 11031 SVN_ERR_ASSERT(start_rev <= end_rev); 11032 SVN_ERR(hotcopy_remove_files(dst_fs, 11033 svn_dirent_join(dst_fs->path, 11034 PATH_REVS_DIR, 11035 scratch_pool), 11036 start_rev, end_rev, 11037 max_files_per_dir, scratch_pool)); 11038 11039 return SVN_NO_ERROR; 11040} 11041 11042/* Remove revision properties between START_REV (inclusive) and END_REV 11043 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 11044 * Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will 11045 * not be deleted. */ 11046static svn_error_t * 11047hotcopy_remove_revprop_files(svn_fs_t *dst_fs, 11048 svn_revnum_t start_rev, 11049 svn_revnum_t end_rev, 11050 int max_files_per_dir, 11051 apr_pool_t *scratch_pool) 11052{ 11053 SVN_ERR_ASSERT(start_rev <= end_rev); 11054 11055 /* don't delete rev 0 props */ 11056 SVN_ERR(hotcopy_remove_files(dst_fs, 11057 svn_dirent_join(dst_fs->path, 11058 PATH_REVPROPS_DIR, 11059 scratch_pool), 11060 start_rev ? start_rev : 1, end_rev, 11061 max_files_per_dir, scratch_pool)); 11062 11063 return SVN_NO_ERROR; 11064} 11065 11066/* Verify that DST_FS is a suitable destination for an incremental 11067 * hotcopy from SRC_FS. */ 11068static svn_error_t * 11069hotcopy_incremental_check_preconditions(svn_fs_t *src_fs, 11070 svn_fs_t *dst_fs, 11071 apr_pool_t *pool) 11072{ 11073 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11074 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11075 11076 /* We only support incremental hotcopy between the same format. */ 11077 if (src_ffd->format != dst_ffd->format) 11078 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11079 _("The FSFS format (%d) of the hotcopy source does not match the " 11080 "FSFS format (%d) of the hotcopy destination; please upgrade " 11081 "both repositories to the same format"), 11082 src_ffd->format, dst_ffd->format); 11083 11084 /* Make sure the UUID of source and destination match up. 11085 * We don't want to copy over a different repository. */ 11086 if (strcmp(src_fs->uuid, dst_fs->uuid) != 0) 11087 return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL, 11088 _("The UUID of the hotcopy source does " 11089 "not match the UUID of the hotcopy " 11090 "destination")); 11091 11092 /* Also require same shard size. */ 11093 if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir) 11094 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11095 _("The sharding layout configuration " 11096 "of the hotcopy source does not match " 11097 "the sharding layout configuration of " 11098 "the hotcopy destination")); 11099 return SVN_NO_ERROR; 11100} 11101 11102/* Remove folder PATH. Ignore errors due to the sub-tree not being empty. 11103 * CANCEL_FUNC and CANCEL_BATON do the usual thing. 11104 * Use POOL for temporary allocations. 11105 */ 11106static svn_error_t * 11107remove_folder(const char *path, 11108 svn_cancel_func_t cancel_func, 11109 void *cancel_baton, 11110 apr_pool_t *pool) 11111{ 11112 svn_error_t *err = svn_io_remove_dir2(path, TRUE, 11113 cancel_func, cancel_baton, pool); 11114 11115 if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 11116 { 11117 svn_error_clear(err); 11118 err = SVN_NO_ERROR; 11119 } 11120 11121 return svn_error_trace(err); 11122} 11123 11124/* Baton for hotcopy_body(). */ 11125struct hotcopy_body_baton { 11126 svn_fs_t *src_fs; 11127 svn_fs_t *dst_fs; 11128 svn_boolean_t incremental; 11129 svn_cancel_func_t cancel_func; 11130 void *cancel_baton; 11131} hotcopy_body_baton; 11132 11133/* Perform a hotcopy, either normal or incremental. 11134 * 11135 * Normal hotcopy assumes that the destination exists as an empty 11136 * directory. It behaves like an incremental hotcopy except that 11137 * none of the copied files already exist in the destination. 11138 * 11139 * An incremental hotcopy copies only changed or new files to the destination, 11140 * and removes files from the destination no longer present in the source. 11141 * While the incremental hotcopy is running, readers should still be able 11142 * to access the destintation repository without error and should not see 11143 * revisions currently in progress of being copied. Readers are able to see 11144 * new fully copied revisions even if the entire incremental hotcopy procedure 11145 * has not yet completed. 11146 * 11147 * Writers are blocked out completely during the entire incremental hotcopy 11148 * process to ensure consistency. This function assumes that the repository 11149 * write-lock is held. 11150 */ 11151static svn_error_t * 11152hotcopy_body(void *baton, apr_pool_t *pool) 11153{ 11154 struct hotcopy_body_baton *hbb = baton; 11155 svn_fs_t *src_fs = hbb->src_fs; 11156 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11157 svn_fs_t *dst_fs = hbb->dst_fs; 11158 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11159 int max_files_per_dir = src_ffd->max_files_per_dir; 11160 svn_boolean_t incremental = hbb->incremental; 11161 svn_cancel_func_t cancel_func = hbb->cancel_func; 11162 void* cancel_baton = hbb->cancel_baton; 11163 svn_revnum_t src_youngest; 11164 svn_revnum_t dst_youngest; 11165 svn_revnum_t rev; 11166 svn_revnum_t src_min_unpacked_rev; 11167 svn_revnum_t dst_min_unpacked_rev; 11168 const char *src_subdir; 11169 const char *dst_subdir; 11170 const char *revprop_src_subdir; 11171 const char *revprop_dst_subdir; 11172 apr_pool_t *iterpool; 11173 svn_node_kind_t kind; 11174 11175 /* Try to copy the config. 11176 * 11177 * ### We try copying the config file before doing anything else, 11178 * ### because higher layers will abort the hotcopy if we throw 11179 * ### an error from this function, and that renders the hotcopy 11180 * ### unusable anyway. */ 11181 if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 11182 { 11183 svn_error_t *err; 11184 11185 err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG, 11186 pool); 11187 if (err) 11188 { 11189 if (APR_STATUS_IS_ENOENT(err->apr_err)) 11190 { 11191 /* 1.6.0 to 1.6.11 did not copy the configuration file during 11192 * hotcopy. So if we're hotcopying a repository which has been 11193 * created as a hotcopy itself, it's possible that fsfs.conf 11194 * does not exist. Ask the user to re-create it. 11195 * 11196 * ### It would be nice to make this a non-fatal error, 11197 * ### but this function does not get an svn_fs_t object 11198 * ### so we have no way of just printing a warning via 11199 * ### the fs->warning() callback. */ 11200 11201 const char *msg; 11202 const char *src_abspath; 11203 const char *dst_abspath; 11204 const char *config_relpath; 11205 svn_error_t *err2; 11206 11207 config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool); 11208 err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool); 11209 if (err2) 11210 return svn_error_trace(svn_error_compose_create(err, err2)); 11211 err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool); 11212 if (err2) 11213 return svn_error_trace(svn_error_compose_create(err, err2)); 11214 11215 /* ### hack: strip off the 'db/' directory from paths so 11216 * ### they make sense to the user */ 11217 src_abspath = svn_dirent_dirname(src_abspath, pool); 11218 dst_abspath = svn_dirent_dirname(dst_abspath, pool); 11219 11220 msg = apr_psprintf(pool, 11221 _("Failed to create hotcopy at '%s'. " 11222 "The file '%s' is missing from the source " 11223 "repository. Please create this file, for " 11224 "instance by running 'svnadmin upgrade %s'"), 11225 dst_abspath, config_relpath, src_abspath); 11226 return svn_error_quick_wrap(err, msg); 11227 } 11228 else 11229 return svn_error_trace(err); 11230 } 11231 } 11232 11233 if (cancel_func) 11234 SVN_ERR(cancel_func(cancel_baton)); 11235 11236 /* Find the youngest revision in the source and destination. 11237 * We only support hotcopies from sources with an equal or greater amount 11238 * of revisions than the destination. 11239 * This also catches the case where users accidentally swap the 11240 * source and destination arguments. */ 11241 SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool)); 11242 if (incremental) 11243 { 11244 SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool)); 11245 if (src_youngest < dst_youngest) 11246 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11247 _("The hotcopy destination already contains more revisions " 11248 "(%lu) than the hotcopy source contains (%lu); are source " 11249 "and destination swapped?"), 11250 dst_youngest, src_youngest); 11251 } 11252 else 11253 dst_youngest = 0; 11254 11255 if (cancel_func) 11256 SVN_ERR(cancel_func(cancel_baton)); 11257 11258 /* Copy the min unpacked rev, and read its value. */ 11259 if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11260 { 11261 const char *min_unpacked_rev_path; 11262 11263 min_unpacked_rev_path = svn_dirent_join(src_fs->path, 11264 PATH_MIN_UNPACKED_REV, 11265 pool); 11266 SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev, 11267 min_unpacked_rev_path, 11268 pool)); 11269 11270 min_unpacked_rev_path = svn_dirent_join(dst_fs->path, 11271 PATH_MIN_UNPACKED_REV, 11272 pool); 11273 SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev, 11274 min_unpacked_rev_path, 11275 pool)); 11276 11277 /* We only support packs coming from the hotcopy source. 11278 * The destination should not be packed independently from 11279 * the source. This also catches the case where users accidentally 11280 * swap the source and destination arguments. */ 11281 if (src_min_unpacked_rev < dst_min_unpacked_rev) 11282 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11283 _("The hotcopy destination already contains " 11284 "more packed revisions (%lu) than the " 11285 "hotcopy source contains (%lu)"), 11286 dst_min_unpacked_rev - 1, 11287 src_min_unpacked_rev - 1); 11288 11289 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11290 PATH_MIN_UNPACKED_REV, pool)); 11291 } 11292 else 11293 { 11294 src_min_unpacked_rev = 0; 11295 dst_min_unpacked_rev = 0; 11296 } 11297 11298 if (cancel_func) 11299 SVN_ERR(cancel_func(cancel_baton)); 11300 11301 /* 11302 * Copy the necessary rev files. 11303 */ 11304 11305 src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool); 11306 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool); 11307 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool)); 11308 11309 iterpool = svn_pool_create(pool); 11310 /* First, copy packed shards. */ 11311 for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir) 11312 { 11313 svn_pool_clear(iterpool); 11314 11315 if (cancel_func) 11316 SVN_ERR(cancel_func(cancel_baton)); 11317 11318 /* Copy the packed shard. */ 11319 SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11320 src_fs, dst_fs, 11321 rev, max_files_per_dir, 11322 iterpool)); 11323 11324 /* If necessary, update 'current' to the most recent packed rev, 11325 * so readers can see new revisions which arrived in this pack. */ 11326 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, 11327 rev + max_files_per_dir - 1, 11328 iterpool)); 11329 11330 /* Remove revision files which are now packed. */ 11331 if (incremental) 11332 { 11333 SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev, 11334 rev + max_files_per_dir, 11335 max_files_per_dir, iterpool)); 11336 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11337 SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev, 11338 rev + max_files_per_dir, 11339 max_files_per_dir, 11340 iterpool)); 11341 } 11342 11343 /* Now that all revisions have moved into the pack, the original 11344 * rev dir can be removed. */ 11345 SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool), 11346 cancel_func, cancel_baton, iterpool)); 11347 if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11348 SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool), 11349 cancel_func, cancel_baton, iterpool)); 11350 } 11351 11352 if (cancel_func) 11353 SVN_ERR(cancel_func(cancel_baton)); 11354 11355 /* Now, copy pairs of non-packed revisions and revprop files. 11356 * If necessary, update 'current' after copying all files from a shard. */ 11357 SVN_ERR_ASSERT(rev == src_min_unpacked_rev); 11358 SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev); 11359 revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool); 11360 revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool); 11361 SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool)); 11362 for (; rev <= src_youngest; rev++) 11363 { 11364 svn_error_t *err; 11365 11366 svn_pool_clear(iterpool); 11367 11368 if (cancel_func) 11369 SVN_ERR(cancel_func(cancel_baton)); 11370 11371 /* Copy the rev file. */ 11372 err = hotcopy_copy_shard_file(src_subdir, dst_subdir, 11373 rev, max_files_per_dir, 11374 iterpool); 11375 if (err) 11376 { 11377 if (APR_STATUS_IS_ENOENT(err->apr_err) && 11378 src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11379 { 11380 svn_error_clear(err); 11381 11382 /* The source rev file does not exist. This can happen if the 11383 * source repository is being packed concurrently with this 11384 * hotcopy operation. 11385 * 11386 * If the new revision is now packed, and the youngest revision 11387 * we're interested in is not inside this pack, try to copy the 11388 * pack instead. 11389 * 11390 * If the youngest revision ended up being packed, don't try 11391 * to be smart and work around this. Just abort the hotcopy. */ 11392 SVN_ERR(update_min_unpacked_rev(src_fs, pool)); 11393 if (is_packed_rev(src_fs, rev)) 11394 { 11395 if (is_packed_rev(src_fs, src_youngest)) 11396 return svn_error_createf( 11397 SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11398 _("The assumed HEAD revision (%lu) of the " 11399 "hotcopy source has been packed while the " 11400 "hotcopy was in progress; please restart " 11401 "the hotcopy operation"), 11402 src_youngest); 11403 11404 SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11405 src_fs, dst_fs, 11406 rev, max_files_per_dir, 11407 iterpool)); 11408 rev = dst_min_unpacked_rev; 11409 continue; 11410 } 11411 else 11412 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11413 _("Revision %lu disappeared from the " 11414 "hotcopy source while hotcopy was " 11415 "in progress"), rev); 11416 } 11417 else 11418 return svn_error_trace(err); 11419 } 11420 11421 /* Copy the revprop file. */ 11422 SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir, 11423 revprop_dst_subdir, 11424 rev, max_files_per_dir, 11425 iterpool)); 11426 11427 /* After completing a full shard, update 'current'. */ 11428 if (max_files_per_dir && rev % max_files_per_dir == 0) 11429 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool)); 11430 } 11431 svn_pool_destroy(iterpool); 11432 11433 if (cancel_func) 11434 SVN_ERR(cancel_func(cancel_baton)); 11435 11436 /* We assume that all revisions were copied now, i.e. we didn't exit the 11437 * above loop early. 'rev' was last incremented during exit of the loop. */ 11438 SVN_ERR_ASSERT(rev == src_youngest + 1); 11439 11440 /* All revisions were copied. Update 'current'. */ 11441 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool)); 11442 11443 /* Replace the locks tree. 11444 * This is racy in case readers are currently trying to list locks in 11445 * the destination. However, we need to get rid of stale locks. 11446 * This is the simplest way of doing this, so we accept this small race. */ 11447 dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool); 11448 SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton, 11449 pool)); 11450 src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool); 11451 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11452 if (kind == svn_node_dir) 11453 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path, 11454 PATH_LOCKS_DIR, TRUE, 11455 cancel_func, cancel_baton, pool)); 11456 11457 /* Now copy the node-origins cache tree. */ 11458 src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool); 11459 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11460 if (kind == svn_node_dir) 11461 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path, 11462 PATH_NODE_ORIGINS_DIR, TRUE, 11463 cancel_func, cancel_baton, pool)); 11464 11465 /* 11466 * NB: Data copied below is only read by writers, not readers. 11467 * Writers are still locked out at this point. 11468 */ 11469 11470 if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 11471 { 11472 /* Copy the rep cache and then remove entries for revisions 11473 * younger than the destination's youngest revision. */ 11474 src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool); 11475 dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool); 11476 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11477 if (kind == svn_node_file) 11478 { 11479 SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool)); 11480 SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool)); 11481 } 11482 } 11483 11484 /* Copy the txn-current file. */ 11485 if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11486 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11487 PATH_TXN_CURRENT, pool)); 11488 11489 /* If a revprop generation file exists in the source filesystem, 11490 * reset it to zero (since this is on a different path, it will not 11491 * overlap with data already in cache). Also, clean up stale files 11492 * used for the named atomics implementation. */ 11493 SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool), 11494 &kind, pool)); 11495 if (kind == svn_node_file) 11496 SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool)); 11497 11498 SVN_ERR(cleanup_revprop_namespace(dst_fs)); 11499 11500 /* Hotcopied FS is complete. Stamp it with a format file. */ 11501 SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool), 11502 dst_ffd->format, max_files_per_dir, TRUE, pool)); 11503 11504 return SVN_NO_ERROR; 11505} 11506 11507 11508/* Set up shared data between SRC_FS and DST_FS. */ 11509static void 11510hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs) 11511{ 11512 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11513 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11514 11515 /* The common pool and mutexes are shared between src and dst filesystems. 11516 * During hotcopy we only grab the mutexes for the destination, so there 11517 * is no risk of dead-lock. We don't write to the src filesystem. Shared 11518 * data for the src_fs has already been initialised in fs_hotcopy(). */ 11519 dst_ffd->shared = src_ffd->shared; 11520} 11521 11522/* Create an empty filesystem at DST_FS at DST_PATH with the same 11523 * configuration as SRC_FS (uuid, format, and other parameters). 11524 * After creation DST_FS has no revisions, not even revision zero. */ 11525static svn_error_t * 11526hotcopy_create_empty_dest(svn_fs_t *src_fs, 11527 svn_fs_t *dst_fs, 11528 const char *dst_path, 11529 apr_pool_t *pool) 11530{ 11531 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11532 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11533 11534 dst_fs->path = apr_pstrdup(pool, dst_path); 11535 11536 dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir; 11537 dst_ffd->config = src_ffd->config; 11538 dst_ffd->format = src_ffd->format; 11539 11540 /* Create the revision data directories. */ 11541 if (dst_ffd->max_files_per_dir) 11542 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool), 11543 pool)); 11544 else 11545 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11546 PATH_REVS_DIR, pool), 11547 pool)); 11548 11549 /* Create the revprops directory. */ 11550 if (src_ffd->max_files_per_dir) 11551 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool), 11552 pool)); 11553 else 11554 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11555 PATH_REVPROPS_DIR, 11556 pool), 11557 pool)); 11558 11559 /* Create the transaction directory. */ 11560 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR, 11561 pool), 11562 pool)); 11563 11564 /* Create the protorevs directory. */ 11565 if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 11566 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11567 PATH_TXN_PROTOS_DIR, 11568 pool), 11569 pool)); 11570 11571 /* Create the 'current' file. */ 11572 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool), 11573 (dst_ffd->format >= 11574 SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 11575 ? "0\n" : "0 1 1\n"), 11576 pool)); 11577 11578 /* Create lock file and UUID. */ 11579 SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool)); 11580 SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool)); 11581 11582 /* Create the min unpacked rev file. */ 11583 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11584 SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool), 11585 "0\n", pool)); 11586 /* Create the txn-current file if the repository supports 11587 the transaction sequence file. */ 11588 if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11589 { 11590 SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool), 11591 "0\n", pool)); 11592 SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool), 11593 "", pool)); 11594 } 11595 11596 dst_ffd->youngest_rev_cache = 0; 11597 11598 hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11599 SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11600 11601 return SVN_NO_ERROR; 11602} 11603 11604svn_error_t * 11605svn_fs_fs__hotcopy(svn_fs_t *src_fs, 11606 svn_fs_t *dst_fs, 11607 const char *src_path, 11608 const char *dst_path, 11609 svn_boolean_t incremental, 11610 svn_cancel_func_t cancel_func, 11611 void *cancel_baton, 11612 apr_pool_t *pool) 11613{ 11614 struct hotcopy_body_baton hbb; 11615 11616 if (cancel_func) 11617 SVN_ERR(cancel_func(cancel_baton)); 11618 11619 SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool)); 11620 11621 if (incremental) 11622 { 11623 const char *dst_format_abspath; 11624 svn_node_kind_t dst_format_kind; 11625 11626 /* Check destination format to be sure we know how to incrementally 11627 * hotcopy to the destination FS. */ 11628 dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool); 11629 SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool)); 11630 if (dst_format_kind == svn_node_none) 11631 { 11632 /* Destination doesn't exist yet. Perform a normal hotcopy to a 11633 * empty destination using the same configuration as the source. */ 11634 SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11635 } 11636 else 11637 { 11638 /* Check the existing repository. */ 11639 SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool)); 11640 SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs, 11641 pool)); 11642 hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11643 SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11644 } 11645 } 11646 else 11647 { 11648 /* Start out with an empty destination using the same configuration 11649 * as the source. */ 11650 SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11651 } 11652 11653 if (cancel_func) 11654 SVN_ERR(cancel_func(cancel_baton)); 11655 11656 hbb.src_fs = src_fs; 11657 hbb.dst_fs = dst_fs; 11658 hbb.incremental = incremental; 11659 hbb.cancel_func = cancel_func; 11660 hbb.cancel_baton = cancel_baton; 11661 SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool)); 11662 11663 return SVN_NO_ERROR; 11664} 11665