fs_fs.c revision 269847
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 svn_stream_t *stream; 3992 *final_path = path_revprops(fs, rev, pool); 3993 3994 /* ### do we have a directory sitting around already? we really shouldn't 3995 ### have to get the dirname here. */ 3996 SVN_ERR(svn_stream_open_unique(&stream, tmp_path, 3997 svn_dirent_dirname(*final_path, pool), 3998 svn_io_file_del_none, pool, pool)); 3999 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4000 SVN_ERR(svn_stream_close(stream)); 4001 4002 return SVN_NO_ERROR; 4003} 4004 4005/* After writing the new revprop file(s), call this function to move the 4006 * file at TMP_PATH to FINAL_PATH and give it the permissions from 4007 * PERMS_REFERENCE. 4008 * 4009 * If indicated in BUMP_GENERATION, increase FS' revprop generation. 4010 * Finally, delete all the temporary files given in FILES_TO_DELETE. 4011 * The latter may be NULL. 4012 * 4013 * Use POOL for temporary allocations. 4014 */ 4015static svn_error_t * 4016switch_to_new_revprop(svn_fs_t *fs, 4017 const char *final_path, 4018 const char *tmp_path, 4019 const char *perms_reference, 4020 apr_array_header_t *files_to_delete, 4021 svn_boolean_t bump_generation, 4022 apr_pool_t *pool) 4023{ 4024 /* Now, we may actually be replacing revprops. Make sure that all other 4025 threads and processes will know about this. */ 4026 if (bump_generation) 4027 SVN_ERR(begin_revprop_change(fs, pool)); 4028 4029 SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool)); 4030 4031 /* Indicate that the update (if relevant) has been completed. */ 4032 if (bump_generation) 4033 SVN_ERR(end_revprop_change(fs, pool)); 4034 4035 /* Clean up temporary files, if necessary. */ 4036 if (files_to_delete) 4037 { 4038 apr_pool_t *iterpool = svn_pool_create(pool); 4039 int i; 4040 4041 for (i = 0; i < files_to_delete->nelts; ++i) 4042 { 4043 const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*); 4044 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 4045 svn_pool_clear(iterpool); 4046 } 4047 4048 svn_pool_destroy(iterpool); 4049 } 4050 return SVN_NO_ERROR; 4051} 4052 4053/* Write a pack file header to STREAM that starts at revision START_REVISION 4054 * and contains the indexes [START,END) of SIZES. 4055 */ 4056static svn_error_t * 4057serialize_revprops_header(svn_stream_t *stream, 4058 svn_revnum_t start_revision, 4059 apr_array_header_t *sizes, 4060 int start, 4061 int end, 4062 apr_pool_t *pool) 4063{ 4064 apr_pool_t *iterpool = svn_pool_create(pool); 4065 int i; 4066 4067 SVN_ERR_ASSERT(start < end); 4068 4069 /* start revision and entry count */ 4070 SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision)); 4071 SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start)); 4072 4073 /* the sizes array */ 4074 for (i = start; i < end; ++i) 4075 { 4076 apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t); 4077 SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n", 4078 size)); 4079 } 4080 4081 /* the double newline char indicates the end of the header */ 4082 SVN_ERR(svn_stream_printf(stream, iterpool, "\n")); 4083 4084 svn_pool_clear(iterpool); 4085 return SVN_NO_ERROR; 4086} 4087 4088/* Writes the a pack file to FILE_STREAM. It copies the serialized data 4089 * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX. 4090 * 4091 * The data for the latter is taken from NEW_SERIALIZED. Note, that 4092 * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is 4093 * taken in that case but only a subset of the old data will be copied. 4094 * 4095 * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size. 4096 * POOL is used for temporary allocations. 4097 */ 4098static svn_error_t * 4099repack_revprops(svn_fs_t *fs, 4100 packed_revprops_t *revprops, 4101 int start, 4102 int end, 4103 int changed_index, 4104 svn_stringbuf_t *new_serialized, 4105 apr_off_t new_total_size, 4106 svn_stream_t *file_stream, 4107 apr_pool_t *pool) 4108{ 4109 fs_fs_data_t *ffd = fs->fsap_data; 4110 svn_stream_t *stream; 4111 int i; 4112 4113 /* create data empty buffers and the stream object */ 4114 svn_stringbuf_t *uncompressed 4115 = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool); 4116 svn_stringbuf_t *compressed 4117 = svn_stringbuf_create_empty(pool); 4118 stream = svn_stream_from_stringbuf(uncompressed, pool); 4119 4120 /* write the header*/ 4121 SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start, 4122 revprops->sizes, start, end, pool)); 4123 4124 /* append the serialized revprops */ 4125 for (i = start; i < end; ++i) 4126 if (i == changed_index) 4127 { 4128 SVN_ERR(svn_stream_write(stream, 4129 new_serialized->data, 4130 &new_serialized->len)); 4131 } 4132 else 4133 { 4134 apr_size_t size 4135 = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t); 4136 apr_size_t offset 4137 = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t); 4138 4139 SVN_ERR(svn_stream_write(stream, 4140 revprops->packed_revprops->data + offset, 4141 &size)); 4142 } 4143 4144 /* flush the stream buffer (if any) to our underlying data buffer */ 4145 SVN_ERR(svn_stream_close(stream)); 4146 4147 /* compress / store the data */ 4148 SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 4149 compressed, 4150 ffd->compress_packed_revprops 4151 ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 4152 : SVN_DELTA_COMPRESSION_LEVEL_NONE)); 4153 4154 /* finally, write the content to the target stream and close it */ 4155 SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len)); 4156 SVN_ERR(svn_stream_close(file_stream)); 4157 4158 return SVN_NO_ERROR; 4159} 4160 4161/* Allocate a new pack file name for revisions 4162 * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1] 4163 * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE, 4164 * auto-create that array if necessary. Return an open file stream to 4165 * the new file in *STREAM allocated in POOL. 4166 */ 4167static svn_error_t * 4168repack_stream_open(svn_stream_t **stream, 4169 svn_fs_t *fs, 4170 packed_revprops_t *revprops, 4171 int start, 4172 int end, 4173 apr_array_header_t **files_to_delete, 4174 apr_pool_t *pool) 4175{ 4176 apr_int64_t tag; 4177 const char *tag_string; 4178 svn_string_t *new_filename; 4179 int i; 4180 apr_file_t *file; 4181 int manifest_offset 4182 = (int)(revprops->start_revision - revprops->manifest_start); 4183 4184 /* get the old (= current) file name and enlist it for later deletion */ 4185 const char *old_filename = APR_ARRAY_IDX(revprops->manifest, 4186 start + manifest_offset, 4187 const char*); 4188 4189 if (*files_to_delete == NULL) 4190 *files_to_delete = apr_array_make(pool, 3, sizeof(const char*)); 4191 4192 APR_ARRAY_PUSH(*files_to_delete, const char*) 4193 = svn_dirent_join(revprops->folder, old_filename, pool); 4194 4195 /* increase the tag part, i.e. the counter after the dot */ 4196 tag_string = strchr(old_filename, '.'); 4197 if (tag_string == NULL) 4198 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4199 _("Packed file '%s' misses a tag"), 4200 old_filename); 4201 4202 SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1)); 4203 new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT, 4204 revprops->start_revision + start, 4205 ++tag); 4206 4207 /* update the manifest to point to the new file */ 4208 for (i = start; i < end; ++i) 4209 APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*) 4210 = new_filename->data; 4211 4212 /* create a file stream for the new file */ 4213 SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder, 4214 new_filename->data, 4215 pool), 4216 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 4217 *stream = svn_stream_from_aprfile2(file, FALSE, pool); 4218 4219 return SVN_NO_ERROR; 4220} 4221 4222/* For revision REV in filesystem FS, set the revision properties to 4223 * PROPLIST. Return a new file in *TMP_PATH that the caller shall move 4224 * to *FINAL_PATH to make the change visible. Files to be deleted will 4225 * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated. 4226 * Use POOL for allocations. 4227 */ 4228static svn_error_t * 4229write_packed_revprop(const char **final_path, 4230 const char **tmp_path, 4231 apr_array_header_t **files_to_delete, 4232 svn_fs_t *fs, 4233 svn_revnum_t rev, 4234 apr_hash_t *proplist, 4235 apr_pool_t *pool) 4236{ 4237 fs_fs_data_t *ffd = fs->fsap_data; 4238 packed_revprops_t *revprops; 4239 apr_int64_t generation = 0; 4240 svn_stream_t *stream; 4241 svn_stringbuf_t *serialized; 4242 apr_off_t new_total_size; 4243 int changed_index; 4244 4245 /* read the current revprop generation. This value will not change 4246 * while we hold the global write lock to this FS. */ 4247 if (has_revprop_cache(fs, pool)) 4248 SVN_ERR(read_revprop_generation(&generation, fs, pool)); 4249 4250 /* read contents of the current pack file */ 4251 SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool)); 4252 4253 /* serialize the new revprops */ 4254 serialized = svn_stringbuf_create_empty(pool); 4255 stream = svn_stream_from_stringbuf(serialized, pool); 4256 SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4257 SVN_ERR(svn_stream_close(stream)); 4258 4259 /* calculate the size of the new data */ 4260 changed_index = (int)(rev - revprops->start_revision); 4261 new_total_size = revprops->total_size - revprops->serialized_size 4262 + serialized->len 4263 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE; 4264 4265 APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len; 4266 4267 /* can we put the new data into the same pack as the before? */ 4268 if ( new_total_size < ffd->revprop_pack_size 4269 || revprops->sizes->nelts == 1) 4270 { 4271 /* simply replace the old pack file with new content as we do it 4272 * in the non-packed case */ 4273 4274 *final_path = svn_dirent_join(revprops->folder, revprops->filename, 4275 pool); 4276 SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder, 4277 svn_io_file_del_none, pool, pool)); 4278 SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts, 4279 changed_index, serialized, new_total_size, 4280 stream, pool)); 4281 } 4282 else 4283 { 4284 /* split the pack file into two of roughly equal size */ 4285 int right_count, left_count, i; 4286 4287 int left = 0; 4288 int right = revprops->sizes->nelts - 1; 4289 apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE; 4290 apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE; 4291 4292 /* let left and right side grow such that their size difference 4293 * is minimal after each step. */ 4294 while (left <= right) 4295 if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4296 < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)) 4297 { 4298 left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4299 + SVN_INT64_BUFFER_SIZE; 4300 ++left; 4301 } 4302 else 4303 { 4304 right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t) 4305 + SVN_INT64_BUFFER_SIZE; 4306 --right; 4307 } 4308 4309 /* since the items need much less than SVN_INT64_BUFFER_SIZE 4310 * bytes to represent their length, the split may not be optimal */ 4311 left_count = left; 4312 right_count = revprops->sizes->nelts - left; 4313 4314 /* if new_size is large, one side may exceed the pack size limit. 4315 * In that case, split before and after the modified revprop.*/ 4316 if ( left_size > ffd->revprop_pack_size 4317 || right_size > ffd->revprop_pack_size) 4318 { 4319 left_count = changed_index; 4320 right_count = revprops->sizes->nelts - left_count - 1; 4321 } 4322 4323 /* write the new, split files */ 4324 if (left_count) 4325 { 4326 SVN_ERR(repack_stream_open(&stream, fs, revprops, 0, 4327 left_count, files_to_delete, pool)); 4328 SVN_ERR(repack_revprops(fs, revprops, 0, left_count, 4329 changed_index, serialized, new_total_size, 4330 stream, pool)); 4331 } 4332 4333 if (left_count + right_count < revprops->sizes->nelts) 4334 { 4335 SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index, 4336 changed_index + 1, files_to_delete, 4337 pool)); 4338 SVN_ERR(repack_revprops(fs, revprops, changed_index, 4339 changed_index + 1, 4340 changed_index, serialized, new_total_size, 4341 stream, pool)); 4342 } 4343 4344 if (right_count) 4345 { 4346 SVN_ERR(repack_stream_open(&stream, fs, revprops, 4347 revprops->sizes->nelts - right_count, 4348 revprops->sizes->nelts, 4349 files_to_delete, pool)); 4350 SVN_ERR(repack_revprops(fs, revprops, 4351 revprops->sizes->nelts - right_count, 4352 revprops->sizes->nelts, changed_index, 4353 serialized, new_total_size, stream, 4354 pool)); 4355 } 4356 4357 /* write the new manifest */ 4358 *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); 4359 SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder, 4360 svn_io_file_del_none, pool, pool)); 4361 4362 for (i = 0; i < revprops->manifest->nelts; ++i) 4363 { 4364 const char *filename = APR_ARRAY_IDX(revprops->manifest, i, 4365 const char*); 4366 SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename)); 4367 } 4368 4369 SVN_ERR(svn_stream_close(stream)); 4370 } 4371 4372 return SVN_NO_ERROR; 4373} 4374 4375/* Set the revision property list of revision REV in filesystem FS to 4376 PROPLIST. Use POOL for temporary allocations. */ 4377static svn_error_t * 4378set_revision_proplist(svn_fs_t *fs, 4379 svn_revnum_t rev, 4380 apr_hash_t *proplist, 4381 apr_pool_t *pool) 4382{ 4383 svn_boolean_t is_packed; 4384 svn_boolean_t bump_generation = FALSE; 4385 const char *final_path; 4386 const char *tmp_path; 4387 const char *perms_reference; 4388 apr_array_header_t *files_to_delete = NULL; 4389 4390 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 4391 4392 /* this info will not change while we hold the global FS write lock */ 4393 is_packed = is_packed_revprop(fs, rev); 4394 4395 /* Test whether revprops already exist for this revision. 4396 * Only then will we need to bump the revprop generation. */ 4397 if (has_revprop_cache(fs, pool)) 4398 { 4399 if (is_packed) 4400 { 4401 bump_generation = TRUE; 4402 } 4403 else 4404 { 4405 svn_node_kind_t kind; 4406 SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind, 4407 pool)); 4408 bump_generation = kind != svn_node_none; 4409 } 4410 } 4411 4412 /* Serialize the new revprop data */ 4413 if (is_packed) 4414 SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete, 4415 fs, rev, proplist, pool)); 4416 else 4417 SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path, 4418 fs, rev, proplist, pool)); 4419 4420 /* We use the rev file of this revision as the perms reference, 4421 * because when setting revprops for the first time, the revprop 4422 * file won't exist and therefore can't serve as its own reference. 4423 * (Whereas the rev file should already exist at this point.) 4424 */ 4425 SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool)); 4426 4427 /* Now, switch to the new revprop data. */ 4428 SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference, 4429 files_to_delete, bump_generation, pool)); 4430 4431 return SVN_NO_ERROR; 4432} 4433 4434svn_error_t * 4435svn_fs_fs__revision_proplist(apr_hash_t **proplist_p, 4436 svn_fs_t *fs, 4437 svn_revnum_t rev, 4438 apr_pool_t *pool) 4439{ 4440 SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool)); 4441 4442 return SVN_NO_ERROR; 4443} 4444 4445/* Represents where in the current svndiff data block each 4446 representation is. */ 4447struct rep_state 4448{ 4449 apr_file_t *file; 4450 /* The txdelta window cache to use or NULL. */ 4451 svn_cache__t *window_cache; 4452 /* Caches un-deltified windows. May be NULL. */ 4453 svn_cache__t *combined_cache; 4454 apr_off_t start; /* The starting offset for the raw 4455 svndiff/plaintext data minus header. */ 4456 apr_off_t off; /* The current offset into the file. */ 4457 apr_off_t end; /* The end offset of the raw data. */ 4458 int ver; /* If a delta, what svndiff version? */ 4459 int chunk_index; 4460}; 4461 4462/* See create_rep_state, which wraps this and adds another error. */ 4463static svn_error_t * 4464create_rep_state_body(struct rep_state **rep_state, 4465 struct rep_args **rep_args, 4466 apr_file_t **file_hint, 4467 svn_revnum_t *rev_hint, 4468 representation_t *rep, 4469 svn_fs_t *fs, 4470 apr_pool_t *pool) 4471{ 4472 fs_fs_data_t *ffd = fs->fsap_data; 4473 struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs)); 4474 struct rep_args *ra; 4475 unsigned char buf[4]; 4476 4477 /* If the hint is 4478 * - given, 4479 * - refers to a valid revision, 4480 * - refers to a packed revision, 4481 * - as does the rep we want to read, and 4482 * - refers to the same pack file as the rep 4483 * ... 4484 */ 4485 if ( file_hint && rev_hint && *file_hint 4486 && SVN_IS_VALID_REVNUM(*rev_hint) 4487 && *rev_hint < ffd->min_unpacked_rev 4488 && rep->revision < ffd->min_unpacked_rev 4489 && ( (*rev_hint / ffd->max_files_per_dir) 4490 == (rep->revision / ffd->max_files_per_dir))) 4491 { 4492 /* ... we can re-use the same, already open file object 4493 */ 4494 apr_off_t offset; 4495 SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool)); 4496 4497 offset += rep->offset; 4498 SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool)); 4499 4500 rs->file = *file_hint; 4501 } 4502 else 4503 { 4504 /* otherwise, create a new file object 4505 */ 4506 SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool)); 4507 } 4508 4509 /* remember the current file, if suggested by the caller */ 4510 if (file_hint) 4511 *file_hint = rs->file; 4512 if (rev_hint) 4513 *rev_hint = rep->revision; 4514 4515 /* continue constructing RS and RA */ 4516 rs->window_cache = ffd->txdelta_window_cache; 4517 rs->combined_cache = ffd->combined_window_cache; 4518 4519 SVN_ERR(read_rep_line(&ra, rs->file, pool)); 4520 SVN_ERR(get_file_offset(&rs->start, rs->file, pool)); 4521 rs->off = rs->start; 4522 rs->end = rs->start + rep->size; 4523 *rep_state = rs; 4524 *rep_args = ra; 4525 4526 if (!ra->is_delta) 4527 /* This is a plaintext, so just return the current rep_state. */ 4528 return SVN_NO_ERROR; 4529 4530 /* We are dealing with a delta, find out what version. */ 4531 SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf), 4532 NULL, NULL, pool)); 4533 /* ### Layering violation */ 4534 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N'))) 4535 return svn_error_create 4536 (SVN_ERR_FS_CORRUPT, NULL, 4537 _("Malformed svndiff data in representation")); 4538 rs->ver = buf[3]; 4539 rs->chunk_index = 0; 4540 rs->off += 4; 4541 4542 return SVN_NO_ERROR; 4543} 4544 4545/* Read the rep args for REP in filesystem FS and create a rep_state 4546 for reading the representation. Return the rep_state in *REP_STATE 4547 and the rep args in *REP_ARGS, both allocated in POOL. 4548 4549 When reading multiple reps, i.e. a skip delta chain, you may provide 4550 non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first 4551 call it should be a pointer to NULL.) The function will use these variables 4552 to store the previous call results and tries to re-use them. This may 4553 result in significant savings in I/O for packed files. 4554 */ 4555static svn_error_t * 4556create_rep_state(struct rep_state **rep_state, 4557 struct rep_args **rep_args, 4558 apr_file_t **file_hint, 4559 svn_revnum_t *rev_hint, 4560 representation_t *rep, 4561 svn_fs_t *fs, 4562 apr_pool_t *pool) 4563{ 4564 svn_error_t *err = create_rep_state_body(rep_state, rep_args, 4565 file_hint, rev_hint, 4566 rep, fs, pool); 4567 if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 4568 { 4569 fs_fs_data_t *ffd = fs->fsap_data; 4570 4571 /* ### This always returns "-1" for transaction reps, because 4572 ### this particular bit of code doesn't know if the rep is 4573 ### stored in the protorev or in the mutable area (for props 4574 ### or dir contents). It is pretty rare for FSFS to *read* 4575 ### from the protorev file, though, so this is probably OK. 4576 ### And anyone going to debug corruption errors is probably 4577 ### going to jump straight to this comment anyway! */ 4578 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 4579 "Corrupt representation '%s'", 4580 rep 4581 ? representation_string(rep, ffd->format, TRUE, 4582 TRUE, pool) 4583 : "(null)"); 4584 } 4585 /* ### Call representation_string() ? */ 4586 return svn_error_trace(err); 4587} 4588 4589struct rep_read_baton 4590{ 4591 /* The FS from which we're reading. */ 4592 svn_fs_t *fs; 4593 4594 /* If not NULL, this is the base for the first delta window in rs_list */ 4595 svn_stringbuf_t *base_window; 4596 4597 /* The state of all prior delta representations. */ 4598 apr_array_header_t *rs_list; 4599 4600 /* The plaintext state, if there is a plaintext. */ 4601 struct rep_state *src_state; 4602 4603 /* The index of the current delta chunk, if we are reading a delta. */ 4604 int chunk_index; 4605 4606 /* The buffer where we store undeltified data. */ 4607 char *buf; 4608 apr_size_t buf_pos; 4609 apr_size_t buf_len; 4610 4611 /* A checksum context for summing the data read in order to verify it. 4612 Note: we don't need to use the sha1 checksum because we're only doing 4613 data verification, for which md5 is perfectly safe. */ 4614 svn_checksum_ctx_t *md5_checksum_ctx; 4615 4616 svn_boolean_t checksum_finalized; 4617 4618 /* The stored checksum of the representation we are reading, its 4619 length, and the amount we've read so far. Some of this 4620 information is redundant with rs_list and src_state, but it's 4621 convenient for the checksumming code to have it here. */ 4622 svn_checksum_t *md5_checksum; 4623 4624 svn_filesize_t len; 4625 svn_filesize_t off; 4626 4627 /* The key for the fulltext cache for this rep, if there is a 4628 fulltext cache. */ 4629 pair_cache_key_t fulltext_cache_key; 4630 /* The text we've been reading, if we're going to cache it. */ 4631 svn_stringbuf_t *current_fulltext; 4632 4633 /* Used for temporary allocations during the read. */ 4634 apr_pool_t *pool; 4635 4636 /* Pool used to store file handles and other data that is persistant 4637 for the entire stream read. */ 4638 apr_pool_t *filehandle_pool; 4639}; 4640 4641/* Combine the name of the rev file in RS with the given OFFSET to form 4642 * a cache lookup key. Allocations will be made from POOL. May return 4643 * NULL if the key cannot be constructed. */ 4644static const char* 4645get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool) 4646{ 4647 const char *name; 4648 const char *last_part; 4649 const char *name_last; 4650 4651 /* the rev file name containing the txdelta window. 4652 * If this fails we are in serious trouble anyways. 4653 * And if nobody else detects the problems, the file content checksum 4654 * comparison _will_ find them. 4655 */ 4656 if (apr_file_name_get(&name, rs->file)) 4657 return NULL; 4658 4659 /* Handle packed files as well by scanning backwards until we find the 4660 * revision or pack number. */ 4661 name_last = name + strlen(name) - 1; 4662 while (! svn_ctype_isdigit(*name_last)) 4663 --name_last; 4664 4665 last_part = name_last; 4666 while (svn_ctype_isdigit(*last_part)) 4667 --last_part; 4668 4669 /* We must differentiate between packed files (as of today, the number 4670 * is being followed by a dot) and non-packed files (followed by \0). 4671 * Otherwise, there might be overlaps in the numbering range if the 4672 * repo gets packed after caching the txdeltas of non-packed revs. 4673 * => add the first non-digit char to the packed number. */ 4674 if (name_last[1] != '\0') 4675 ++name_last; 4676 4677 /* copy one char MORE than the actual number to mark packed files, 4678 * i.e. packed revision file content uses different key space then 4679 * non-packed ones: keys for packed rev file content ends with a dot 4680 * for non-packed rev files they end with a digit. */ 4681 name = apr_pstrndup(pool, last_part + 1, name_last - last_part); 4682 return svn_fs_fs__combine_number_and_string(offset, name, pool); 4683} 4684 4685/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4686 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4687 * cache has been given. If a cache is available IS_CACHED will inform 4688 * the caller about the success of the lookup. Allocations (of the window 4689 * in particualar) will be made from POOL. 4690 * 4691 * If the information could be found, put RS and the position within the 4692 * rev file into the same state as if the data had just been read from it. 4693 */ 4694static svn_error_t * 4695get_cached_window(svn_txdelta_window_t **window_p, 4696 struct rep_state *rs, 4697 svn_boolean_t *is_cached, 4698 apr_pool_t *pool) 4699{ 4700 if (! rs->window_cache) 4701 { 4702 /* txdelta window has not been enabled */ 4703 *is_cached = FALSE; 4704 } 4705 else 4706 { 4707 /* ask the cache for the desired txdelta window */ 4708 svn_fs_fs__txdelta_cached_window_t *cached_window; 4709 SVN_ERR(svn_cache__get((void **) &cached_window, 4710 is_cached, 4711 rs->window_cache, 4712 get_window_key(rs, rs->off, pool), 4713 pool)); 4714 4715 if (*is_cached) 4716 { 4717 /* found it. Pass it back to the caller. */ 4718 *window_p = cached_window->window; 4719 4720 /* manipulate the RS as if we just read the data */ 4721 rs->chunk_index++; 4722 rs->off = cached_window->end_offset; 4723 4724 /* manipulate the rev file as if we just read from it */ 4725 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4726 } 4727 } 4728 4729 return SVN_NO_ERROR; 4730} 4731 4732/* Store the WINDOW read at OFFSET for the rep state RS in the current 4733 * FSFS session's cache. This will be a no-op if no cache has been given. 4734 * Temporary allocations will be made from SCRATCH_POOL. */ 4735static svn_error_t * 4736set_cached_window(svn_txdelta_window_t *window, 4737 struct rep_state *rs, 4738 apr_off_t offset, 4739 apr_pool_t *scratch_pool) 4740{ 4741 if (rs->window_cache) 4742 { 4743 /* store the window and the first offset _past_ it */ 4744 svn_fs_fs__txdelta_cached_window_t cached_window; 4745 4746 cached_window.window = window; 4747 cached_window.end_offset = rs->off; 4748 4749 /* but key it with the start offset because that is the known state 4750 * when we will look it up */ 4751 return svn_cache__set(rs->window_cache, 4752 get_window_key(rs, offset, scratch_pool), 4753 &cached_window, 4754 scratch_pool); 4755 } 4756 4757 return SVN_NO_ERROR; 4758} 4759 4760/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4761 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4762 * cache has been given. If a cache is available IS_CACHED will inform 4763 * the caller about the success of the lookup. Allocations (of the window 4764 * in particualar) will be made from POOL. 4765 */ 4766static svn_error_t * 4767get_cached_combined_window(svn_stringbuf_t **window_p, 4768 struct rep_state *rs, 4769 svn_boolean_t *is_cached, 4770 apr_pool_t *pool) 4771{ 4772 if (! rs->combined_cache) 4773 { 4774 /* txdelta window has not been enabled */ 4775 *is_cached = FALSE; 4776 } 4777 else 4778 { 4779 /* ask the cache for the desired txdelta window */ 4780 return svn_cache__get((void **)window_p, 4781 is_cached, 4782 rs->combined_cache, 4783 get_window_key(rs, rs->start, pool), 4784 pool); 4785 } 4786 4787 return SVN_NO_ERROR; 4788} 4789 4790/* Store the WINDOW read at OFFSET for the rep state RS in the current 4791 * FSFS session's cache. This will be a no-op if no cache has been given. 4792 * Temporary allocations will be made from SCRATCH_POOL. */ 4793static svn_error_t * 4794set_cached_combined_window(svn_stringbuf_t *window, 4795 struct rep_state *rs, 4796 apr_off_t offset, 4797 apr_pool_t *scratch_pool) 4798{ 4799 if (rs->combined_cache) 4800 { 4801 /* but key it with the start offset because that is the known state 4802 * when we will look it up */ 4803 return svn_cache__set(rs->combined_cache, 4804 get_window_key(rs, offset, scratch_pool), 4805 window, 4806 scratch_pool); 4807 } 4808 4809 return SVN_NO_ERROR; 4810} 4811 4812/* Build an array of rep_state structures in *LIST giving the delta 4813 reps from first_rep to a plain-text or self-compressed rep. Set 4814 *SRC_STATE to the plain-text rep we find at the end of the chain, 4815 or to NULL if the final delta representation is self-compressed. 4816 The representation to start from is designated by filesystem FS, id 4817 ID, and representation REP. 4818 Also, set *WINDOW_P to the base window content for *LIST, if it 4819 could be found in cache. Otherwise, *LIST will contain the base 4820 representation for the whole delta chain. 4821 Finally, return the expanded size of the representation in 4822 *EXPANDED_SIZE. It will take care of cases where only the on-disk 4823 size is known. */ 4824static svn_error_t * 4825build_rep_list(apr_array_header_t **list, 4826 svn_stringbuf_t **window_p, 4827 struct rep_state **src_state, 4828 svn_filesize_t *expanded_size, 4829 svn_fs_t *fs, 4830 representation_t *first_rep, 4831 apr_pool_t *pool) 4832{ 4833 representation_t rep; 4834 struct rep_state *rs = NULL; 4835 struct rep_args *rep_args; 4836 svn_boolean_t is_cached = FALSE; 4837 apr_file_t *last_file = NULL; 4838 svn_revnum_t last_revision; 4839 4840 *list = apr_array_make(pool, 1, sizeof(struct rep_state *)); 4841 rep = *first_rep; 4842 4843 /* The value as stored in the data struct. 4844 0 is either for unknown length or actually zero length. */ 4845 *expanded_size = first_rep->expanded_size; 4846 4847 /* for the top-level rep, we need the rep_args */ 4848 SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4849 &last_revision, &rep, fs, pool)); 4850 4851 /* Unknown size or empty representation? 4852 That implies the this being the first iteration. 4853 Usually size equals on-disk size, except for empty, 4854 compressed representations (delta, size = 4). 4855 Please note that for all non-empty deltas have 4856 a 4-byte header _plus_ some data. */ 4857 if (*expanded_size == 0) 4858 if (! rep_args->is_delta || first_rep->size != 4) 4859 *expanded_size = first_rep->size; 4860 4861 while (1) 4862 { 4863 /* fetch state, if that has not been done already */ 4864 if (!rs) 4865 SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4866 &last_revision, &rep, fs, pool)); 4867 4868 SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool)); 4869 if (is_cached) 4870 { 4871 /* We already have a reconstructed window in our cache. 4872 Write a pseudo rep_state with the full length. */ 4873 rs->off = rs->start; 4874 rs->end = rs->start + (*window_p)->len; 4875 *src_state = rs; 4876 return SVN_NO_ERROR; 4877 } 4878 4879 if (!rep_args->is_delta) 4880 { 4881 /* This is a plaintext, so just return the current rep_state. */ 4882 *src_state = rs; 4883 return SVN_NO_ERROR; 4884 } 4885 4886 /* Push this rep onto the list. If it's self-compressed, we're done. */ 4887 APR_ARRAY_PUSH(*list, struct rep_state *) = rs; 4888 if (rep_args->is_delta_vs_empty) 4889 { 4890 *src_state = NULL; 4891 return SVN_NO_ERROR; 4892 } 4893 4894 rep.revision = rep_args->base_revision; 4895 rep.offset = rep_args->base_offset; 4896 rep.size = rep_args->base_length; 4897 rep.txn_id = NULL; 4898 4899 rs = NULL; 4900 } 4901} 4902 4903 4904/* Create a rep_read_baton structure for node revision NODEREV in 4905 filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not 4906 NULL, it is the rep's key in the fulltext cache, and a stringbuf 4907 must be allocated to store the text. Perform all allocations in 4908 POOL. If rep is mutable, it must be for file contents. */ 4909static svn_error_t * 4910rep_read_get_baton(struct rep_read_baton **rb_p, 4911 svn_fs_t *fs, 4912 representation_t *rep, 4913 pair_cache_key_t fulltext_cache_key, 4914 apr_pool_t *pool) 4915{ 4916 struct rep_read_baton *b; 4917 4918 b = apr_pcalloc(pool, sizeof(*b)); 4919 b->fs = fs; 4920 b->base_window = NULL; 4921 b->chunk_index = 0; 4922 b->buf = NULL; 4923 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 4924 b->checksum_finalized = FALSE; 4925 b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 4926 b->len = rep->expanded_size; 4927 b->off = 0; 4928 b->fulltext_cache_key = fulltext_cache_key; 4929 b->pool = svn_pool_create(pool); 4930 b->filehandle_pool = svn_pool_create(pool); 4931 4932 SVN_ERR(build_rep_list(&b->rs_list, &b->base_window, 4933 &b->src_state, &b->len, fs, rep, 4934 b->filehandle_pool)); 4935 4936 if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision)) 4937 b->current_fulltext = svn_stringbuf_create_ensure 4938 ((apr_size_t)b->len, 4939 b->filehandle_pool); 4940 else 4941 b->current_fulltext = NULL; 4942 4943 /* Save our output baton. */ 4944 *rb_p = b; 4945 4946 return SVN_NO_ERROR; 4947} 4948 4949/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta 4950 window into *NWIN. */ 4951static svn_error_t * 4952read_delta_window(svn_txdelta_window_t **nwin, int this_chunk, 4953 struct rep_state *rs, apr_pool_t *pool) 4954{ 4955 svn_stream_t *stream; 4956 svn_boolean_t is_cached; 4957 apr_off_t old_offset; 4958 4959 SVN_ERR_ASSERT(rs->chunk_index <= this_chunk); 4960 4961 /* RS->FILE may be shared between RS instances -> make sure we point 4962 * to the right data. */ 4963 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4964 4965 /* Skip windows to reach the current chunk if we aren't there yet. */ 4966 while (rs->chunk_index < this_chunk) 4967 { 4968 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool)); 4969 rs->chunk_index++; 4970 SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4971 if (rs->off >= rs->end) 4972 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 4973 _("Reading one svndiff window read " 4974 "beyond the end of the " 4975 "representation")); 4976 } 4977 4978 /* Read the next window. But first, try to find it in the cache. */ 4979 SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool)); 4980 if (is_cached) 4981 return SVN_NO_ERROR; 4982 4983 /* Actually read the next window. */ 4984 old_offset = rs->off; 4985 stream = svn_stream_from_aprfile2(rs->file, TRUE, pool); 4986 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool)); 4987 rs->chunk_index++; 4988 SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4989 4990 if (rs->off > rs->end) 4991 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 4992 _("Reading one svndiff window read beyond " 4993 "the end of the representation")); 4994 4995 /* the window has not been cached before, thus cache it now 4996 * (if caching is used for them at all) */ 4997 return set_cached_window(*nwin, rs, old_offset, pool); 4998} 4999 5000/* Read SIZE bytes from the representation RS and return it in *NWIN. */ 5001static svn_error_t * 5002read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs, 5003 apr_size_t size, apr_pool_t *pool) 5004{ 5005 /* RS->FILE may be shared between RS instances -> make sure we point 5006 * to the right data. */ 5007 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 5008 5009 /* Read the plain data. */ 5010 *nwin = svn_stringbuf_create_ensure(size, pool); 5011 SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL, 5012 pool)); 5013 (*nwin)->data[size] = 0; 5014 5015 /* Update RS. */ 5016 rs->off += (apr_off_t)size; 5017 5018 return SVN_NO_ERROR; 5019} 5020 5021/* Get the undeltified window that is a result of combining all deltas 5022 from the current desired representation identified in *RB with its 5023 base representation. Store the window in *RESULT. */ 5024static svn_error_t * 5025get_combined_window(svn_stringbuf_t **result, 5026 struct rep_read_baton *rb) 5027{ 5028 apr_pool_t *pool, *new_pool, *window_pool; 5029 int i; 5030 svn_txdelta_window_t *window; 5031 apr_array_header_t *windows; 5032 svn_stringbuf_t *source, *buf = rb->base_window; 5033 struct rep_state *rs; 5034 5035 /* Read all windows that we need to combine. This is fine because 5036 the size of each window is relatively small (100kB) and skip- 5037 delta limits the number of deltas in a chain to well under 100. 5038 Stop early if one of them does not depend on its predecessors. */ 5039 window_pool = svn_pool_create(rb->pool); 5040 windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *)); 5041 for (i = 0; i < rb->rs_list->nelts; ++i) 5042 { 5043 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5044 SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool)); 5045 5046 APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window; 5047 if (window->src_ops == 0) 5048 { 5049 ++i; 5050 break; 5051 } 5052 } 5053 5054 /* Combine in the windows from the other delta reps. */ 5055 pool = svn_pool_create(rb->pool); 5056 for (--i; i >= 0; --i) 5057 { 5058 5059 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5060 window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *); 5061 5062 /* Maybe, we've got a PLAIN start representation. If we do, read 5063 as much data from it as the needed for the txdelta window's source 5064 view. 5065 Note that BUF / SOURCE may only be NULL in the first iteration. */ 5066 source = buf; 5067 if (source == NULL && rb->src_state != NULL) 5068 SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len, 5069 pool)); 5070 5071 /* Combine this window with the current one. */ 5072 new_pool = svn_pool_create(rb->pool); 5073 buf = svn_stringbuf_create_ensure(window->tview_len, new_pool); 5074 buf->len = window->tview_len; 5075 5076 svn_txdelta_apply_instructions(window, source ? source->data : NULL, 5077 buf->data, &buf->len); 5078 if (buf->len != window->tview_len) 5079 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 5080 _("svndiff window length is " 5081 "corrupt")); 5082 5083 /* Cache windows only if the whole rep content could be read as a 5084 single chunk. Only then will no other chunk need a deeper RS 5085 list than the cached chunk. */ 5086 if ((rb->chunk_index == 0) && (rs->off == rs->end)) 5087 SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool)); 5088 5089 /* Cycle pools so that we only need to hold three windows at a time. */ 5090 svn_pool_destroy(pool); 5091 pool = new_pool; 5092 } 5093 5094 svn_pool_destroy(window_pool); 5095 5096 *result = buf; 5097 return SVN_NO_ERROR; 5098} 5099 5100/* Returns whether or not the expanded fulltext of the file is cachable 5101 * based on its size SIZE. The decision depends on the cache used by RB. 5102 */ 5103static svn_boolean_t 5104fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size) 5105{ 5106 return (size < APR_SIZE_MAX) 5107 && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size); 5108} 5109 5110/* Close method used on streams returned by read_representation(). 5111 */ 5112static svn_error_t * 5113rep_read_contents_close(void *baton) 5114{ 5115 struct rep_read_baton *rb = baton; 5116 5117 svn_pool_destroy(rb->pool); 5118 svn_pool_destroy(rb->filehandle_pool); 5119 5120 return SVN_NO_ERROR; 5121} 5122 5123/* Return the next *LEN bytes of the rep and store them in *BUF. */ 5124static svn_error_t * 5125get_contents(struct rep_read_baton *rb, 5126 char *buf, 5127 apr_size_t *len) 5128{ 5129 apr_size_t copy_len, remaining = *len; 5130 char *cur = buf; 5131 struct rep_state *rs; 5132 5133 /* Special case for when there are no delta reps, only a plain 5134 text. */ 5135 if (rb->rs_list->nelts == 0) 5136 { 5137 copy_len = remaining; 5138 rs = rb->src_state; 5139 5140 if (rb->base_window != NULL) 5141 { 5142 /* We got the desired rep directly from the cache. 5143 This is where we need the pseudo rep_state created 5144 by build_rep_list(). */ 5145 apr_size_t offset = (apr_size_t)(rs->off - rs->start); 5146 if (copy_len + offset > rb->base_window->len) 5147 copy_len = offset < rb->base_window->len 5148 ? rb->base_window->len - offset 5149 : 0ul; 5150 5151 memcpy (cur, rb->base_window->data + offset, copy_len); 5152 } 5153 else 5154 { 5155 if (((apr_off_t) copy_len) > rs->end - rs->off) 5156 copy_len = (apr_size_t) (rs->end - rs->off); 5157 SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL, 5158 NULL, rb->pool)); 5159 } 5160 5161 rs->off += copy_len; 5162 *len = copy_len; 5163 return SVN_NO_ERROR; 5164 } 5165 5166 while (remaining > 0) 5167 { 5168 /* If we have buffered data from a previous chunk, use that. */ 5169 if (rb->buf) 5170 { 5171 /* Determine how much to copy from the buffer. */ 5172 copy_len = rb->buf_len - rb->buf_pos; 5173 if (copy_len > remaining) 5174 copy_len = remaining; 5175 5176 /* Actually copy the data. */ 5177 memcpy(cur, rb->buf + rb->buf_pos, copy_len); 5178 rb->buf_pos += copy_len; 5179 cur += copy_len; 5180 remaining -= copy_len; 5181 5182 /* If the buffer is all used up, clear it and empty the 5183 local pool. */ 5184 if (rb->buf_pos == rb->buf_len) 5185 { 5186 svn_pool_clear(rb->pool); 5187 rb->buf = NULL; 5188 } 5189 } 5190 else 5191 { 5192 svn_stringbuf_t *sbuf = NULL; 5193 5194 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *); 5195 if (rs->off == rs->end) 5196 break; 5197 5198 /* Get more buffered data by evaluating a chunk. */ 5199 SVN_ERR(get_combined_window(&sbuf, rb)); 5200 5201 rb->chunk_index++; 5202 rb->buf_len = sbuf->len; 5203 rb->buf = sbuf->data; 5204 rb->buf_pos = 0; 5205 } 5206 } 5207 5208 *len = cur - buf; 5209 5210 return SVN_NO_ERROR; 5211} 5212 5213/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the 5214 representation and store them in *BUF. Sum as we read and verify 5215 the MD5 sum at the end. */ 5216static svn_error_t * 5217rep_read_contents(void *baton, 5218 char *buf, 5219 apr_size_t *len) 5220{ 5221 struct rep_read_baton *rb = baton; 5222 5223 /* Get the next block of data. */ 5224 SVN_ERR(get_contents(rb, buf, len)); 5225 5226 if (rb->current_fulltext) 5227 svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); 5228 5229 /* Perform checksumming. We want to check the checksum as soon as 5230 the last byte of data is read, in case the caller never performs 5231 a short read, but we don't want to finalize the MD5 context 5232 twice. */ 5233 if (!rb->checksum_finalized) 5234 { 5235 SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len)); 5236 rb->off += *len; 5237 if (rb->off == rb->len) 5238 { 5239 svn_checksum_t *md5_checksum; 5240 5241 rb->checksum_finalized = TRUE; 5242 SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx, 5243 rb->pool)); 5244 if (!svn_checksum_match(md5_checksum, rb->md5_checksum)) 5245 return svn_error_create(SVN_ERR_FS_CORRUPT, 5246 svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum, 5247 rb->pool, 5248 _("Checksum mismatch while reading representation")), 5249 NULL); 5250 } 5251 } 5252 5253 if (rb->off == rb->len && rb->current_fulltext) 5254 { 5255 fs_fs_data_t *ffd = rb->fs->fsap_data; 5256 SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key, 5257 rb->current_fulltext, rb->pool)); 5258 rb->current_fulltext = NULL; 5259 } 5260 5261 return SVN_NO_ERROR; 5262} 5263 5264 5265/* Return a stream in *CONTENTS_P that will read the contents of a 5266 representation stored at the location given by REP. Appropriate 5267 for any kind of immutable representation, but only for file 5268 contents (not props or directory contents) in mutable 5269 representations. 5270 5271 If REP is NULL, the representation is assumed to be empty, and the 5272 empty stream is returned. 5273*/ 5274static svn_error_t * 5275read_representation(svn_stream_t **contents_p, 5276 svn_fs_t *fs, 5277 representation_t *rep, 5278 apr_pool_t *pool) 5279{ 5280 if (! rep) 5281 { 5282 *contents_p = svn_stream_empty(pool); 5283 } 5284 else 5285 { 5286 fs_fs_data_t *ffd = fs->fsap_data; 5287 pair_cache_key_t fulltext_cache_key = { 0 }; 5288 svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size; 5289 struct rep_read_baton *rb; 5290 5291 fulltext_cache_key.revision = rep->revision; 5292 fulltext_cache_key.second = rep->offset; 5293 if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5294 && fulltext_size_is_cachable(ffd, len)) 5295 { 5296 svn_stringbuf_t *fulltext; 5297 svn_boolean_t is_cached; 5298 SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached, 5299 ffd->fulltext_cache, &fulltext_cache_key, 5300 pool)); 5301 if (is_cached) 5302 { 5303 *contents_p = svn_stream_from_stringbuf(fulltext, pool); 5304 return SVN_NO_ERROR; 5305 } 5306 } 5307 else 5308 fulltext_cache_key.revision = SVN_INVALID_REVNUM; 5309 5310 SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool)); 5311 5312 *contents_p = svn_stream_create(rb, pool); 5313 svn_stream_set_read(*contents_p, rep_read_contents); 5314 svn_stream_set_close(*contents_p, rep_read_contents_close); 5315 } 5316 5317 return SVN_NO_ERROR; 5318} 5319 5320svn_error_t * 5321svn_fs_fs__get_contents(svn_stream_t **contents_p, 5322 svn_fs_t *fs, 5323 node_revision_t *noderev, 5324 apr_pool_t *pool) 5325{ 5326 return read_representation(contents_p, fs, noderev->data_rep, pool); 5327} 5328 5329/* Baton used when reading delta windows. */ 5330struct delta_read_baton 5331{ 5332 struct rep_state *rs; 5333 svn_checksum_t *checksum; 5334}; 5335 5336/* This implements the svn_txdelta_next_window_fn_t interface. */ 5337static svn_error_t * 5338delta_read_next_window(svn_txdelta_window_t **window, void *baton, 5339 apr_pool_t *pool) 5340{ 5341 struct delta_read_baton *drb = baton; 5342 5343 if (drb->rs->off == drb->rs->end) 5344 { 5345 *window = NULL; 5346 return SVN_NO_ERROR; 5347 } 5348 5349 return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool); 5350} 5351 5352/* This implements the svn_txdelta_md5_digest_fn_t interface. */ 5353static const unsigned char * 5354delta_read_md5_digest(void *baton) 5355{ 5356 struct delta_read_baton *drb = baton; 5357 5358 if (drb->checksum->kind == svn_checksum_md5) 5359 return drb->checksum->digest; 5360 else 5361 return NULL; 5362} 5363 5364svn_error_t * 5365svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p, 5366 svn_fs_t *fs, 5367 node_revision_t *source, 5368 node_revision_t *target, 5369 apr_pool_t *pool) 5370{ 5371 svn_stream_t *source_stream, *target_stream; 5372 5373 /* Try a shortcut: if the target is stored as a delta against the source, 5374 then just use that delta. */ 5375 if (source && source->data_rep && target->data_rep) 5376 { 5377 struct rep_state *rep_state; 5378 struct rep_args *rep_args; 5379 5380 /* Read target's base rep if any. */ 5381 SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL, 5382 target->data_rep, fs, pool)); 5383 5384 /* If that matches source, then use this delta as is. 5385 Note that we want an actual delta here. E.g. a self-delta would 5386 not be good enough. */ 5387 if (rep_args->is_delta 5388 && rep_args->base_revision == source->data_rep->revision 5389 && rep_args->base_offset == source->data_rep->offset) 5390 { 5391 /* Create the delta read baton. */ 5392 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb)); 5393 drb->rs = rep_state; 5394 drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum, 5395 pool); 5396 *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window, 5397 delta_read_md5_digest, pool); 5398 return SVN_NO_ERROR; 5399 } 5400 else 5401 SVN_ERR(svn_io_file_close(rep_state->file, pool)); 5402 } 5403 5404 /* Read both fulltexts and construct a delta. */ 5405 if (source) 5406 SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool)); 5407 else 5408 source_stream = svn_stream_empty(pool); 5409 SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool)); 5410 5411 /* Because source and target stream will already verify their content, 5412 * there is no need to do this once more. In particular if the stream 5413 * content is being fetched from cache. */ 5414 svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool); 5415 5416 return SVN_NO_ERROR; 5417} 5418 5419/* Baton for cache_access_wrapper. Wraps the original parameters of 5420 * svn_fs_fs__try_process_file_content(). 5421 */ 5422typedef struct cache_access_wrapper_baton_t 5423{ 5424 svn_fs_process_contents_func_t func; 5425 void* baton; 5426} cache_access_wrapper_baton_t; 5427 5428/* Wrapper to translate between svn_fs_process_contents_func_t and 5429 * svn_cache__partial_getter_func_t. 5430 */ 5431static svn_error_t * 5432cache_access_wrapper(void **out, 5433 const void *data, 5434 apr_size_t data_len, 5435 void *baton, 5436 apr_pool_t *pool) 5437{ 5438 cache_access_wrapper_baton_t *wrapper_baton = baton; 5439 5440 SVN_ERR(wrapper_baton->func((const unsigned char *)data, 5441 data_len - 1, /* cache adds terminating 0 */ 5442 wrapper_baton->baton, 5443 pool)); 5444 5445 /* non-NULL value to signal the calling cache that all went well */ 5446 *out = baton; 5447 5448 return SVN_NO_ERROR; 5449} 5450 5451svn_error_t * 5452svn_fs_fs__try_process_file_contents(svn_boolean_t *success, 5453 svn_fs_t *fs, 5454 node_revision_t *noderev, 5455 svn_fs_process_contents_func_t processor, 5456 void* baton, 5457 apr_pool_t *pool) 5458{ 5459 representation_t *rep = noderev->data_rep; 5460 if (rep) 5461 { 5462 fs_fs_data_t *ffd = fs->fsap_data; 5463 pair_cache_key_t fulltext_cache_key = { 0 }; 5464 5465 fulltext_cache_key.revision = rep->revision; 5466 fulltext_cache_key.second = rep->offset; 5467 if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5468 && fulltext_size_is_cachable(ffd, rep->expanded_size)) 5469 { 5470 cache_access_wrapper_baton_t wrapper_baton; 5471 void *dummy = NULL; 5472 5473 wrapper_baton.func = processor; 5474 wrapper_baton.baton = baton; 5475 return svn_cache__get_partial(&dummy, success, 5476 ffd->fulltext_cache, 5477 &fulltext_cache_key, 5478 cache_access_wrapper, 5479 &wrapper_baton, 5480 pool); 5481 } 5482 } 5483 5484 *success = FALSE; 5485 return SVN_NO_ERROR; 5486} 5487 5488/* Fetch the contents of a directory into ENTRIES. Values are stored 5489 as filename to string mappings; further conversion is necessary to 5490 convert them into svn_fs_dirent_t values. */ 5491static svn_error_t * 5492get_dir_contents(apr_hash_t *entries, 5493 svn_fs_t *fs, 5494 node_revision_t *noderev, 5495 apr_pool_t *pool) 5496{ 5497 svn_stream_t *contents; 5498 5499 if (noderev->data_rep && noderev->data_rep->txn_id) 5500 { 5501 const char *filename = path_txn_node_children(fs, noderev->id, pool); 5502 5503 /* The representation is mutable. Read the old directory 5504 contents from the mutable children file, followed by the 5505 changes we've made in this transaction. */ 5506 SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool)); 5507 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5508 SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool)); 5509 SVN_ERR(svn_stream_close(contents)); 5510 } 5511 else if (noderev->data_rep) 5512 { 5513 /* use a temporary pool for temp objects. 5514 * Also undeltify content before parsing it. Otherwise, we could only 5515 * parse it byte-by-byte. 5516 */ 5517 apr_pool_t *text_pool = svn_pool_create(pool); 5518 apr_size_t len = noderev->data_rep->expanded_size 5519 ? (apr_size_t)noderev->data_rep->expanded_size 5520 : (apr_size_t)noderev->data_rep->size; 5521 svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool); 5522 text->len = len; 5523 5524 /* The representation is immutable. Read it normally. */ 5525 SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool)); 5526 SVN_ERR(svn_stream_read(contents, text->data, &text->len)); 5527 SVN_ERR(svn_stream_close(contents)); 5528 5529 /* de-serialize hash */ 5530 contents = svn_stream_from_stringbuf(text, text_pool); 5531 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5532 5533 svn_pool_destroy(text_pool); 5534 } 5535 5536 return SVN_NO_ERROR; 5537} 5538 5539 5540static const char * 5541unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id, 5542 apr_pool_t *pool) 5543{ 5544 return apr_psprintf(pool, "%s %s", 5545 (kind == svn_node_file) ? KIND_FILE : KIND_DIR, 5546 svn_fs_fs__id_unparse(id, pool)->data); 5547} 5548 5549/* Given a hash ENTRIES of dirent structions, return a hash in 5550 *STR_ENTRIES_P, that has svn_string_t as the values in the format 5551 specified by the fs_fs directory contents file. Perform 5552 allocations in POOL. */ 5553static svn_error_t * 5554unparse_dir_entries(apr_hash_t **str_entries_p, 5555 apr_hash_t *entries, 5556 apr_pool_t *pool) 5557{ 5558 apr_hash_index_t *hi; 5559 5560 /* For now, we use a our own hash function to ensure that we get a 5561 * (largely) stable order when serializing the data. It also gives 5562 * us some performance improvement. 5563 * 5564 * ### TODO ### 5565 * Use some sorted or other fixed order data container. 5566 */ 5567 *str_entries_p = svn_hash__make(pool); 5568 5569 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 5570 { 5571 const void *key; 5572 apr_ssize_t klen; 5573 svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi); 5574 const char *new_val; 5575 5576 apr_hash_this(hi, &key, &klen, NULL); 5577 new_val = unparse_dir_entry(dirent->kind, dirent->id, pool); 5578 apr_hash_set(*str_entries_p, key, klen, 5579 svn_string_create(new_val, pool)); 5580 } 5581 5582 return SVN_NO_ERROR; 5583} 5584 5585 5586/* Given a hash STR_ENTRIES with values as svn_string_t as specified 5587 in an FSFS directory contents listing, return a hash of dirents in 5588 *ENTRIES_P. Perform allocations in POOL. */ 5589static svn_error_t * 5590parse_dir_entries(apr_hash_t **entries_p, 5591 apr_hash_t *str_entries, 5592 const char *unparsed_id, 5593 apr_pool_t *pool) 5594{ 5595 apr_hash_index_t *hi; 5596 5597 *entries_p = apr_hash_make(pool); 5598 5599 /* Translate the string dir entries into real entries. */ 5600 for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi)) 5601 { 5602 const char *name = svn__apr_hash_index_key(hi); 5603 svn_string_t *str_val = svn__apr_hash_index_val(hi); 5604 char *str, *last_str; 5605 svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent)); 5606 5607 last_str = apr_pstrdup(pool, str_val->data); 5608 dirent->name = apr_pstrdup(pool, name); 5609 5610 str = svn_cstring_tokenize(" ", &last_str); 5611 if (str == NULL) 5612 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5613 _("Directory entry corrupt in '%s'"), 5614 unparsed_id); 5615 5616 if (strcmp(str, KIND_FILE) == 0) 5617 { 5618 dirent->kind = svn_node_file; 5619 } 5620 else if (strcmp(str, KIND_DIR) == 0) 5621 { 5622 dirent->kind = svn_node_dir; 5623 } 5624 else 5625 { 5626 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5627 _("Directory entry corrupt in '%s'"), 5628 unparsed_id); 5629 } 5630 5631 str = svn_cstring_tokenize(" ", &last_str); 5632 if (str == NULL) 5633 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5634 _("Directory entry corrupt in '%s'"), 5635 unparsed_id); 5636 5637 dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool); 5638 5639 svn_hash_sets(*entries_p, dirent->name, dirent); 5640 } 5641 5642 return SVN_NO_ERROR; 5643} 5644 5645/* Return the cache object in FS responsible to storing the directory 5646 * the NODEREV. If none exists, return NULL. */ 5647static svn_cache__t * 5648locate_dir_cache(svn_fs_t *fs, 5649 node_revision_t *noderev) 5650{ 5651 fs_fs_data_t *ffd = fs->fsap_data; 5652 return svn_fs_fs__id_txn_id(noderev->id) 5653 ? ffd->txn_dir_cache 5654 : ffd->dir_cache; 5655} 5656 5657svn_error_t * 5658svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p, 5659 svn_fs_t *fs, 5660 node_revision_t *noderev, 5661 apr_pool_t *pool) 5662{ 5663 const char *unparsed_id = NULL; 5664 apr_hash_t *unparsed_entries, *parsed_entries; 5665 5666 /* find the cache we may use */ 5667 svn_cache__t *cache = locate_dir_cache(fs, noderev); 5668 if (cache) 5669 { 5670 svn_boolean_t found; 5671 5672 unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data; 5673 SVN_ERR(svn_cache__get((void **) entries_p, &found, cache, 5674 unparsed_id, pool)); 5675 if (found) 5676 return SVN_NO_ERROR; 5677 } 5678 5679 /* Read in the directory hash. */ 5680 unparsed_entries = apr_hash_make(pool); 5681 SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool)); 5682 SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries, 5683 unparsed_id, pool)); 5684 5685 /* Update the cache, if we are to use one. */ 5686 if (cache) 5687 SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool)); 5688 5689 *entries_p = parsed_entries; 5690 return SVN_NO_ERROR; 5691} 5692 5693svn_error_t * 5694svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent, 5695 svn_fs_t *fs, 5696 node_revision_t *noderev, 5697 const char *name, 5698 apr_pool_t *result_pool, 5699 apr_pool_t *scratch_pool) 5700{ 5701 svn_boolean_t found = FALSE; 5702 5703 /* find the cache we may use */ 5704 svn_cache__t *cache = locate_dir_cache(fs, noderev); 5705 if (cache) 5706 { 5707 const char *unparsed_id = 5708 svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data; 5709 5710 /* Cache lookup. */ 5711 SVN_ERR(svn_cache__get_partial((void **)dirent, 5712 &found, 5713 cache, 5714 unparsed_id, 5715 svn_fs_fs__extract_dir_entry, 5716 (void*)name, 5717 result_pool)); 5718 } 5719 5720 /* fetch data from disk if we did not find it in the cache */ 5721 if (! found) 5722 { 5723 apr_hash_t *entries; 5724 svn_fs_dirent_t *entry; 5725 svn_fs_dirent_t *entry_copy = NULL; 5726 5727 /* read the dir from the file system. It will probably be put it 5728 into the cache for faster lookup in future calls. */ 5729 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, 5730 scratch_pool)); 5731 5732 /* find desired entry and return a copy in POOL, if found */ 5733 entry = svn_hash_gets(entries, name); 5734 if (entry != NULL) 5735 { 5736 entry_copy = apr_palloc(result_pool, sizeof(*entry_copy)); 5737 entry_copy->name = apr_pstrdup(result_pool, entry->name); 5738 entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool); 5739 entry_copy->kind = entry->kind; 5740 } 5741 5742 *dirent = entry_copy; 5743 } 5744 5745 return SVN_NO_ERROR; 5746} 5747 5748svn_error_t * 5749svn_fs_fs__get_proplist(apr_hash_t **proplist_p, 5750 svn_fs_t *fs, 5751 node_revision_t *noderev, 5752 apr_pool_t *pool) 5753{ 5754 apr_hash_t *proplist; 5755 svn_stream_t *stream; 5756 5757 if (noderev->prop_rep && noderev->prop_rep->txn_id) 5758 { 5759 const char *filename = path_txn_node_props(fs, noderev->id, pool); 5760 proplist = apr_hash_make(pool); 5761 5762 SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool)); 5763 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5764 SVN_ERR(svn_stream_close(stream)); 5765 } 5766 else if (noderev->prop_rep) 5767 { 5768 fs_fs_data_t *ffd = fs->fsap_data; 5769 representation_t *rep = noderev->prop_rep; 5770 pair_cache_key_t key = { 0 }; 5771 5772 key.revision = rep->revision; 5773 key.second = rep->offset; 5774 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5775 { 5776 svn_boolean_t is_cached; 5777 SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 5778 ffd->properties_cache, &key, pool)); 5779 if (is_cached) 5780 return SVN_NO_ERROR; 5781 } 5782 5783 proplist = apr_hash_make(pool); 5784 SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool)); 5785 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5786 SVN_ERR(svn_stream_close(stream)); 5787 5788 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5789 SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool)); 5790 } 5791 else 5792 { 5793 /* return an empty prop list if the node doesn't have any props */ 5794 proplist = apr_hash_make(pool); 5795 } 5796 5797 *proplist_p = proplist; 5798 5799 return SVN_NO_ERROR; 5800} 5801 5802svn_error_t * 5803svn_fs_fs__file_length(svn_filesize_t *length, 5804 node_revision_t *noderev, 5805 apr_pool_t *pool) 5806{ 5807 if (noderev->data_rep) 5808 *length = noderev->data_rep->expanded_size; 5809 else 5810 *length = 0; 5811 5812 return SVN_NO_ERROR; 5813} 5814 5815svn_boolean_t 5816svn_fs_fs__noderev_same_rep_key(representation_t *a, 5817 representation_t *b) 5818{ 5819 if (a == b) 5820 return TRUE; 5821 5822 if (a == NULL || b == NULL) 5823 return FALSE; 5824 5825 if (a->offset != b->offset) 5826 return FALSE; 5827 5828 if (a->revision != b->revision) 5829 return FALSE; 5830 5831 if (a->uniquifier == b->uniquifier) 5832 return TRUE; 5833 5834 if (a->uniquifier == NULL || b->uniquifier == NULL) 5835 return FALSE; 5836 5837 return strcmp(a->uniquifier, b->uniquifier) == 0; 5838} 5839 5840svn_error_t * 5841svn_fs_fs__file_checksum(svn_checksum_t **checksum, 5842 node_revision_t *noderev, 5843 svn_checksum_kind_t kind, 5844 apr_pool_t *pool) 5845{ 5846 if (noderev->data_rep) 5847 { 5848 switch(kind) 5849 { 5850 case svn_checksum_md5: 5851 *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum, 5852 pool); 5853 break; 5854 case svn_checksum_sha1: 5855 *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum, 5856 pool); 5857 break; 5858 default: 5859 *checksum = NULL; 5860 } 5861 } 5862 else 5863 *checksum = NULL; 5864 5865 return SVN_NO_ERROR; 5866} 5867 5868representation_t * 5869svn_fs_fs__rep_copy(representation_t *rep, 5870 apr_pool_t *pool) 5871{ 5872 representation_t *rep_new; 5873 5874 if (rep == NULL) 5875 return NULL; 5876 5877 rep_new = apr_pcalloc(pool, sizeof(*rep_new)); 5878 5879 memcpy(rep_new, rep, sizeof(*rep_new)); 5880 rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 5881 rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool); 5882 rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier); 5883 5884 return rep_new; 5885} 5886 5887/* Merge the internal-use-only CHANGE into a hash of public-FS 5888 svn_fs_path_change2_t CHANGES, collapsing multiple changes into a 5889 single summarical (is that real word?) change per path. Also keep 5890 the COPYFROM_CACHE up to date with new adds and replaces. */ 5891static svn_error_t * 5892fold_change(apr_hash_t *changes, 5893 const change_t *change, 5894 apr_hash_t *copyfrom_cache) 5895{ 5896 apr_pool_t *pool = apr_hash_pool_get(changes); 5897 svn_fs_path_change2_t *old_change, *new_change; 5898 const char *path; 5899 apr_size_t path_len = strlen(change->path); 5900 5901 if ((old_change = apr_hash_get(changes, change->path, path_len))) 5902 { 5903 /* This path already exists in the hash, so we have to merge 5904 this change into the already existing one. */ 5905 5906 /* Sanity check: only allow NULL node revision ID in the 5907 `reset' case. */ 5908 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset)) 5909 return svn_error_create 5910 (SVN_ERR_FS_CORRUPT, NULL, 5911 _("Missing required node revision ID")); 5912 5913 /* Sanity check: we should be talking about the same node 5914 revision ID as our last change except where the last change 5915 was a deletion. */ 5916 if (change->noderev_id 5917 && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id)) 5918 && (old_change->change_kind != svn_fs_path_change_delete)) 5919 return svn_error_create 5920 (SVN_ERR_FS_CORRUPT, NULL, 5921 _("Invalid change ordering: new node revision ID " 5922 "without delete")); 5923 5924 /* Sanity check: an add, replacement, or reset must be the first 5925 thing to follow a deletion. */ 5926 if ((old_change->change_kind == svn_fs_path_change_delete) 5927 && (! ((change->kind == svn_fs_path_change_replace) 5928 || (change->kind == svn_fs_path_change_reset) 5929 || (change->kind == svn_fs_path_change_add)))) 5930 return svn_error_create 5931 (SVN_ERR_FS_CORRUPT, NULL, 5932 _("Invalid change ordering: non-add change on deleted path")); 5933 5934 /* Sanity check: an add can't follow anything except 5935 a delete or reset. */ 5936 if ((change->kind == svn_fs_path_change_add) 5937 && (old_change->change_kind != svn_fs_path_change_delete) 5938 && (old_change->change_kind != svn_fs_path_change_reset)) 5939 return svn_error_create 5940 (SVN_ERR_FS_CORRUPT, NULL, 5941 _("Invalid change ordering: add change on preexisting path")); 5942 5943 /* Now, merge that change in. */ 5944 switch (change->kind) 5945 { 5946 case svn_fs_path_change_reset: 5947 /* A reset here will simply remove the path change from the 5948 hash. */ 5949 old_change = NULL; 5950 break; 5951 5952 case svn_fs_path_change_delete: 5953 if (old_change->change_kind == svn_fs_path_change_add) 5954 { 5955 /* If the path was introduced in this transaction via an 5956 add, and we are deleting it, just remove the path 5957 altogether. */ 5958 old_change = NULL; 5959 } 5960 else 5961 { 5962 /* A deletion overrules all previous changes. */ 5963 old_change->change_kind = svn_fs_path_change_delete; 5964 old_change->text_mod = change->text_mod; 5965 old_change->prop_mod = change->prop_mod; 5966 old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5967 old_change->copyfrom_path = NULL; 5968 } 5969 break; 5970 5971 case svn_fs_path_change_add: 5972 case svn_fs_path_change_replace: 5973 /* An add at this point must be following a previous delete, 5974 so treat it just like a replace. */ 5975 old_change->change_kind = svn_fs_path_change_replace; 5976 old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, 5977 pool); 5978 old_change->text_mod = change->text_mod; 5979 old_change->prop_mod = change->prop_mod; 5980 if (change->copyfrom_rev == SVN_INVALID_REVNUM) 5981 { 5982 old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5983 old_change->copyfrom_path = NULL; 5984 } 5985 else 5986 { 5987 old_change->copyfrom_rev = change->copyfrom_rev; 5988 old_change->copyfrom_path = apr_pstrdup(pool, 5989 change->copyfrom_path); 5990 } 5991 break; 5992 5993 case svn_fs_path_change_modify: 5994 default: 5995 if (change->text_mod) 5996 old_change->text_mod = TRUE; 5997 if (change->prop_mod) 5998 old_change->prop_mod = TRUE; 5999 break; 6000 } 6001 6002 /* Point our new_change to our (possibly modified) old_change. */ 6003 new_change = old_change; 6004 } 6005 else 6006 { 6007 /* This change is new to the hash, so make a new public change 6008 structure from the internal one (in the hash's pool), and dup 6009 the path into the hash's pool, too. */ 6010 new_change = apr_pcalloc(pool, sizeof(*new_change)); 6011 new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool); 6012 new_change->change_kind = change->kind; 6013 new_change->text_mod = change->text_mod; 6014 new_change->prop_mod = change->prop_mod; 6015 /* In FSFS, copyfrom_known is *always* true, since we've always 6016 * stored copyfroms in changed paths lists. */ 6017 new_change->copyfrom_known = TRUE; 6018 if (change->copyfrom_rev != SVN_INVALID_REVNUM) 6019 { 6020 new_change->copyfrom_rev = change->copyfrom_rev; 6021 new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path); 6022 } 6023 else 6024 { 6025 new_change->copyfrom_rev = SVN_INVALID_REVNUM; 6026 new_change->copyfrom_path = NULL; 6027 } 6028 } 6029 6030 if (new_change) 6031 new_change->node_kind = change->node_kind; 6032 6033 /* Add (or update) this path. 6034 6035 Note: this key might already be present, and it would be nice to 6036 re-use its value, but there is no way to fetch it. The API makes no 6037 guarantees that this (new) key will not be retained. Thus, we (again) 6038 copy the key into the target pool to ensure a proper lifetime. */ 6039 path = apr_pstrmemdup(pool, change->path, path_len); 6040 apr_hash_set(changes, path, path_len, new_change); 6041 6042 /* Update the copyfrom cache, if any. */ 6043 if (copyfrom_cache) 6044 { 6045 apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache); 6046 const char *copyfrom_string = NULL, *copyfrom_key = path; 6047 if (new_change) 6048 { 6049 if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev)) 6050 copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s", 6051 new_change->copyfrom_rev, 6052 new_change->copyfrom_path); 6053 else 6054 copyfrom_string = ""; 6055 } 6056 /* We need to allocate a copy of the key in the copyfrom_pool if 6057 * we're not doing a deletion and if it isn't already there. */ 6058 if ( copyfrom_string 6059 && ( ! apr_hash_count(copyfrom_cache) 6060 || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len))) 6061 copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len); 6062 6063 apr_hash_set(copyfrom_cache, copyfrom_key, path_len, 6064 copyfrom_string); 6065 } 6066 6067 return SVN_NO_ERROR; 6068} 6069 6070/* The 256 is an arbitrary size large enough to hold the node id and the 6071 * various flags. */ 6072#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256 6073 6074/* Read the next entry in the changes record from file FILE and store 6075 the resulting change in *CHANGE_P. If there is no next record, 6076 store NULL there. Perform all allocations from POOL. */ 6077static svn_error_t * 6078read_change(change_t **change_p, 6079 apr_file_t *file, 6080 apr_pool_t *pool) 6081{ 6082 char buf[MAX_CHANGE_LINE_LEN]; 6083 apr_size_t len = sizeof(buf); 6084 change_t *change; 6085 char *str, *last_str = buf, *kind_str; 6086 svn_error_t *err; 6087 6088 /* Default return value. */ 6089 *change_p = NULL; 6090 6091 err = svn_io_read_length_line(file, buf, &len, pool); 6092 6093 /* Check for a blank line. */ 6094 if (err || (len == 0)) 6095 { 6096 if (err && APR_STATUS_IS_EOF(err->apr_err)) 6097 { 6098 svn_error_clear(err); 6099 return SVN_NO_ERROR; 6100 } 6101 if ((len == 0) && (! err)) 6102 return SVN_NO_ERROR; 6103 return svn_error_trace(err); 6104 } 6105 6106 change = apr_pcalloc(pool, sizeof(*change)); 6107 6108 /* Get the node-id of the change. */ 6109 str = svn_cstring_tokenize(" ", &last_str); 6110 if (str == NULL) 6111 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6112 _("Invalid changes line in rev-file")); 6113 6114 change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool); 6115 if (change->noderev_id == NULL) 6116 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6117 _("Invalid changes line in rev-file")); 6118 6119 /* Get the change type. */ 6120 str = svn_cstring_tokenize(" ", &last_str); 6121 if (str == NULL) 6122 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6123 _("Invalid changes line in rev-file")); 6124 6125 /* Don't bother to check the format number before looking for 6126 * node-kinds: just read them if you find them. */ 6127 change->node_kind = svn_node_unknown; 6128 kind_str = strchr(str, '-'); 6129 if (kind_str) 6130 { 6131 /* Cap off the end of "str" (the action). */ 6132 *kind_str = '\0'; 6133 kind_str++; 6134 if (strcmp(kind_str, KIND_FILE) == 0) 6135 change->node_kind = svn_node_file; 6136 else if (strcmp(kind_str, KIND_DIR) == 0) 6137 change->node_kind = svn_node_dir; 6138 else 6139 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6140 _("Invalid changes line in rev-file")); 6141 } 6142 6143 if (strcmp(str, ACTION_MODIFY) == 0) 6144 { 6145 change->kind = svn_fs_path_change_modify; 6146 } 6147 else if (strcmp(str, ACTION_ADD) == 0) 6148 { 6149 change->kind = svn_fs_path_change_add; 6150 } 6151 else if (strcmp(str, ACTION_DELETE) == 0) 6152 { 6153 change->kind = svn_fs_path_change_delete; 6154 } 6155 else if (strcmp(str, ACTION_REPLACE) == 0) 6156 { 6157 change->kind = svn_fs_path_change_replace; 6158 } 6159 else if (strcmp(str, ACTION_RESET) == 0) 6160 { 6161 change->kind = svn_fs_path_change_reset; 6162 } 6163 else 6164 { 6165 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6166 _("Invalid change kind in rev file")); 6167 } 6168 6169 /* Get the text-mod flag. */ 6170 str = svn_cstring_tokenize(" ", &last_str); 6171 if (str == NULL) 6172 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6173 _("Invalid changes line in rev-file")); 6174 6175 if (strcmp(str, FLAG_TRUE) == 0) 6176 { 6177 change->text_mod = TRUE; 6178 } 6179 else if (strcmp(str, FLAG_FALSE) == 0) 6180 { 6181 change->text_mod = FALSE; 6182 } 6183 else 6184 { 6185 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6186 _("Invalid text-mod flag in rev-file")); 6187 } 6188 6189 /* Get the prop-mod flag. */ 6190 str = svn_cstring_tokenize(" ", &last_str); 6191 if (str == NULL) 6192 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6193 _("Invalid changes line in rev-file")); 6194 6195 if (strcmp(str, FLAG_TRUE) == 0) 6196 { 6197 change->prop_mod = TRUE; 6198 } 6199 else if (strcmp(str, FLAG_FALSE) == 0) 6200 { 6201 change->prop_mod = FALSE; 6202 } 6203 else 6204 { 6205 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6206 _("Invalid prop-mod flag in rev-file")); 6207 } 6208 6209 /* Get the changed path. */ 6210 change->path = apr_pstrdup(pool, last_str); 6211 6212 6213 /* Read the next line, the copyfrom line. */ 6214 len = sizeof(buf); 6215 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 6216 6217 if (len == 0) 6218 { 6219 change->copyfrom_rev = SVN_INVALID_REVNUM; 6220 change->copyfrom_path = NULL; 6221 } 6222 else 6223 { 6224 last_str = buf; 6225 str = svn_cstring_tokenize(" ", &last_str); 6226 if (! str) 6227 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6228 _("Invalid changes line in rev-file")); 6229 change->copyfrom_rev = SVN_STR_TO_REV(str); 6230 6231 if (! last_str) 6232 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6233 _("Invalid changes line in rev-file")); 6234 6235 change->copyfrom_path = apr_pstrdup(pool, last_str); 6236 } 6237 6238 *change_p = change; 6239 6240 return SVN_NO_ERROR; 6241} 6242 6243/* Examine all the changed path entries in CHANGES and store them in 6244 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary 6245 *data. Store a hash of paths to copyfrom "REV PATH" strings in 6246 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that 6247 the changed-path entries have already been folded (by 6248 write_final_changed_path_info) and may be out of order, so we shouldn't 6249 remove children of replaced or deleted directories. Do all 6250 allocations in POOL. */ 6251static svn_error_t * 6252process_changes(apr_hash_t *changed_paths, 6253 apr_hash_t *copyfrom_cache, 6254 apr_array_header_t *changes, 6255 svn_boolean_t prefolded, 6256 apr_pool_t *pool) 6257{ 6258 apr_pool_t *iterpool = svn_pool_create(pool); 6259 int i; 6260 6261 /* Read in the changes one by one, folding them into our local hash 6262 as necessary. */ 6263 6264 for (i = 0; i < changes->nelts; ++i) 6265 { 6266 change_t *change = APR_ARRAY_IDX(changes, i, change_t *); 6267 6268 SVN_ERR(fold_change(changed_paths, change, copyfrom_cache)); 6269 6270 /* Now, if our change was a deletion or replacement, we have to 6271 blow away any changes thus far on paths that are (or, were) 6272 children of this path. 6273 ### i won't bother with another iteration pool here -- at 6274 most we talking about a few extra dups of paths into what 6275 is already a temporary subpool. 6276 */ 6277 6278 if (((change->kind == svn_fs_path_change_delete) 6279 || (change->kind == svn_fs_path_change_replace)) 6280 && ! prefolded) 6281 { 6282 apr_hash_index_t *hi; 6283 6284 /* a potential child path must contain at least 2 more chars 6285 (the path separator plus at least one char for the name). 6286 Also, we should not assume that all paths have been normalized 6287 i.e. some might have trailing path separators. 6288 */ 6289 apr_ssize_t change_path_len = strlen(change->path); 6290 apr_ssize_t min_child_len = change_path_len == 0 6291 ? 1 6292 : change->path[change_path_len-1] == '/' 6293 ? change_path_len + 1 6294 : change_path_len + 2; 6295 6296 /* CAUTION: This is the inner loop of an O(n^2) algorithm. 6297 The number of changes to process may be >> 1000. 6298 Therefore, keep the inner loop as tight as possible. 6299 */ 6300 for (hi = apr_hash_first(iterpool, changed_paths); 6301 hi; 6302 hi = apr_hash_next(hi)) 6303 { 6304 /* KEY is the path. */ 6305 const void *path; 6306 apr_ssize_t klen; 6307 apr_hash_this(hi, &path, &klen, NULL); 6308 6309 /* If we come across a child of our path, remove it. 6310 Call svn_dirent_is_child only if there is a chance that 6311 this is actually a sub-path. 6312 */ 6313 if ( klen >= min_child_len 6314 && svn_dirent_is_child(change->path, path, iterpool)) 6315 apr_hash_set(changed_paths, path, klen, NULL); 6316 } 6317 } 6318 6319 /* Clear the per-iteration subpool. */ 6320 svn_pool_clear(iterpool); 6321 } 6322 6323 /* Destroy the per-iteration subpool. */ 6324 svn_pool_destroy(iterpool); 6325 6326 return SVN_NO_ERROR; 6327} 6328 6329/* Fetch all the changes from FILE and store them in *CHANGES. Do all 6330 allocations in POOL. */ 6331static svn_error_t * 6332read_all_changes(apr_array_header_t **changes, 6333 apr_file_t *file, 6334 apr_pool_t *pool) 6335{ 6336 change_t *change; 6337 6338 /* pre-allocate enough room for most change lists 6339 (will be auto-expanded as necessary) */ 6340 *changes = apr_array_make(pool, 30, sizeof(change_t *)); 6341 6342 SVN_ERR(read_change(&change, file, pool)); 6343 while (change) 6344 { 6345 APR_ARRAY_PUSH(*changes, change_t*) = change; 6346 SVN_ERR(read_change(&change, file, pool)); 6347 } 6348 6349 return SVN_NO_ERROR; 6350} 6351 6352svn_error_t * 6353svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p, 6354 svn_fs_t *fs, 6355 const char *txn_id, 6356 apr_pool_t *pool) 6357{ 6358 apr_file_t *file; 6359 apr_hash_t *changed_paths = apr_hash_make(pool); 6360 apr_array_header_t *changes; 6361 apr_pool_t *scratch_pool = svn_pool_create(pool); 6362 6363 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 6364 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6365 6366 SVN_ERR(read_all_changes(&changes, file, scratch_pool)); 6367 SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool)); 6368 svn_pool_destroy(scratch_pool); 6369 6370 SVN_ERR(svn_io_file_close(file, pool)); 6371 6372 *changed_paths_p = changed_paths; 6373 6374 return SVN_NO_ERROR; 6375} 6376 6377/* Fetch the list of change in revision REV in FS and return it in *CHANGES. 6378 * Allocate the result in POOL. 6379 */ 6380static svn_error_t * 6381get_changes(apr_array_header_t **changes, 6382 svn_fs_t *fs, 6383 svn_revnum_t rev, 6384 apr_pool_t *pool) 6385{ 6386 apr_off_t changes_offset; 6387 apr_file_t *revision_file; 6388 svn_boolean_t found; 6389 fs_fs_data_t *ffd = fs->fsap_data; 6390 6391 /* try cache lookup first */ 6392 6393 if (ffd->changes_cache) 6394 { 6395 SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache, 6396 &rev, pool)); 6397 if (found) 6398 return SVN_NO_ERROR; 6399 } 6400 6401 /* read changes from revision file */ 6402 6403 SVN_ERR(ensure_revision_exists(fs, rev, pool)); 6404 6405 SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool)); 6406 6407 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs, 6408 rev, pool)); 6409 6410 SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool)); 6411 SVN_ERR(read_all_changes(changes, revision_file, pool)); 6412 6413 SVN_ERR(svn_io_file_close(revision_file, pool)); 6414 6415 /* cache for future reference */ 6416 6417 if (ffd->changes_cache) 6418 SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool)); 6419 6420 return SVN_NO_ERROR; 6421} 6422 6423 6424svn_error_t * 6425svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p, 6426 svn_fs_t *fs, 6427 svn_revnum_t rev, 6428 apr_hash_t *copyfrom_cache, 6429 apr_pool_t *pool) 6430{ 6431 apr_hash_t *changed_paths; 6432 apr_array_header_t *changes; 6433 apr_pool_t *scratch_pool = svn_pool_create(pool); 6434 6435 SVN_ERR(get_changes(&changes, fs, rev, scratch_pool)); 6436 6437 changed_paths = svn_hash__make(pool); 6438 6439 SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes, 6440 TRUE, pool)); 6441 svn_pool_destroy(scratch_pool); 6442 6443 *changed_paths_p = changed_paths; 6444 6445 return SVN_NO_ERROR; 6446} 6447 6448/* Copy a revision node-rev SRC into the current transaction TXN_ID in 6449 the filesystem FS. This is only used to create the root of a transaction. 6450 Allocations are from POOL. */ 6451static svn_error_t * 6452create_new_txn_noderev_from_rev(svn_fs_t *fs, 6453 const char *txn_id, 6454 svn_fs_id_t *src, 6455 apr_pool_t *pool) 6456{ 6457 node_revision_t *noderev; 6458 const char *node_id, *copy_id; 6459 6460 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool)); 6461 6462 if (svn_fs_fs__id_txn_id(noderev->id)) 6463 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6464 _("Copying from transactions not allowed")); 6465 6466 noderev->predecessor_id = noderev->id; 6467 noderev->predecessor_count++; 6468 noderev->copyfrom_path = NULL; 6469 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 6470 6471 /* For the transaction root, the copyroot never changes. */ 6472 6473 node_id = svn_fs_fs__id_node_id(noderev->id); 6474 copy_id = svn_fs_fs__id_copy_id(noderev->id); 6475 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6476 6477 return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool); 6478} 6479 6480/* A structure used by get_and_increment_txn_key_body(). */ 6481struct get_and_increment_txn_key_baton { 6482 svn_fs_t *fs; 6483 char *txn_id; 6484 apr_pool_t *pool; 6485}; 6486 6487/* Callback used in the implementation of create_txn_dir(). This gets 6488 the current base 36 value in PATH_TXN_CURRENT and increments it. 6489 It returns the original value by the baton. */ 6490static svn_error_t * 6491get_and_increment_txn_key_body(void *baton, apr_pool_t *pool) 6492{ 6493 struct get_and_increment_txn_key_baton *cb = baton; 6494 const char *txn_current_filename = path_txn_current(cb->fs, pool); 6495 const char *tmp_filename; 6496 char next_txn_id[MAX_KEY_SIZE+3]; 6497 apr_size_t len; 6498 6499 svn_stringbuf_t *buf; 6500 SVN_ERR(read_content(&buf, txn_current_filename, cb->pool)); 6501 6502 /* remove trailing newlines */ 6503 svn_stringbuf_strip_whitespace(buf); 6504 cb->txn_id = buf->data; 6505 len = buf->len; 6506 6507 /* Increment the key and add a trailing \n to the string so the 6508 txn-current file has a newline in it. */ 6509 svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id); 6510 next_txn_id[len] = '\n'; 6511 ++len; 6512 next_txn_id[len] = '\0'; 6513 6514 SVN_ERR(svn_io_write_unique(&tmp_filename, 6515 svn_dirent_dirname(txn_current_filename, pool), 6516 next_txn_id, len, svn_io_file_del_none, pool)); 6517 SVN_ERR(move_into_place(tmp_filename, txn_current_filename, 6518 txn_current_filename, pool)); 6519 6520 return SVN_NO_ERROR; 6521} 6522 6523/* Create a unique directory for a transaction in FS based on revision 6524 REV. Return the ID for this transaction in *ID_P. Use a sequence 6525 value in the transaction ID to prevent reuse of transaction IDs. */ 6526static svn_error_t * 6527create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6528 apr_pool_t *pool) 6529{ 6530 struct get_and_increment_txn_key_baton cb; 6531 const char *txn_dir; 6532 6533 /* Get the current transaction sequence value, which is a base-36 6534 number, from the txn-current file, and write an 6535 incremented value back out to the file. Place the revision 6536 number the transaction is based off into the transaction id. */ 6537 cb.pool = pool; 6538 cb.fs = fs; 6539 SVN_ERR(with_txn_current_lock(fs, 6540 get_and_increment_txn_key_body, 6541 &cb, 6542 pool)); 6543 *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id); 6544 6545 txn_dir = svn_dirent_join_many(pool, 6546 fs->path, 6547 PATH_TXNS_DIR, 6548 apr_pstrcat(pool, *id_p, PATH_EXT_TXN, 6549 (char *)NULL), 6550 NULL); 6551 6552 return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool); 6553} 6554 6555/* Create a unique directory for a transaction in FS based on revision 6556 REV. Return the ID for this transaction in *ID_P. This 6557 implementation is used in svn 1.4 and earlier repositories and is 6558 kept in 1.5 and greater to support the --pre-1.4-compatible and 6559 --pre-1.5-compatible repository creation options. Reused 6560 transaction IDs are possible with this implementation. */ 6561static svn_error_t * 6562create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6563 apr_pool_t *pool) 6564{ 6565 unsigned int i; 6566 apr_pool_t *subpool; 6567 const char *unique_path, *prefix; 6568 6569 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */ 6570 prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR, 6571 apr_psprintf(pool, "%ld", rev), NULL); 6572 6573 subpool = svn_pool_create(pool); 6574 for (i = 1; i <= 99999; i++) 6575 { 6576 svn_error_t *err; 6577 6578 svn_pool_clear(subpool); 6579 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i); 6580 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool); 6581 if (! err) 6582 { 6583 /* We succeeded. Return the basename minus the ".txn" extension. */ 6584 const char *name = svn_dirent_basename(unique_path, subpool); 6585 *id_p = apr_pstrndup(pool, name, 6586 strlen(name) - strlen(PATH_EXT_TXN)); 6587 svn_pool_destroy(subpool); 6588 return SVN_NO_ERROR; 6589 } 6590 if (! APR_STATUS_IS_EEXIST(err->apr_err)) 6591 return svn_error_trace(err); 6592 svn_error_clear(err); 6593 } 6594 6595 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 6596 NULL, 6597 _("Unable to create transaction directory " 6598 "in '%s' for revision %ld"), 6599 svn_dirent_local_style(fs->path, pool), 6600 rev); 6601} 6602 6603svn_error_t * 6604svn_fs_fs__create_txn(svn_fs_txn_t **txn_p, 6605 svn_fs_t *fs, 6606 svn_revnum_t rev, 6607 apr_pool_t *pool) 6608{ 6609 fs_fs_data_t *ffd = fs->fsap_data; 6610 svn_fs_txn_t *txn; 6611 svn_fs_id_t *root_id; 6612 6613 txn = apr_pcalloc(pool, sizeof(*txn)); 6614 6615 /* Get the txn_id. */ 6616 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 6617 SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool)); 6618 else 6619 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool)); 6620 6621 txn->fs = fs; 6622 txn->base_rev = rev; 6623 6624 txn->vtable = &txn_vtable; 6625 *txn_p = txn; 6626 6627 /* Create a new root node for this transaction. */ 6628 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool)); 6629 SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool)); 6630 6631 /* Create an empty rev file. */ 6632 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "", 6633 pool)); 6634 6635 /* Create an empty rev-lock file. */ 6636 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "", 6637 pool)); 6638 6639 /* Create an empty changes file. */ 6640 SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "", 6641 pool)); 6642 6643 /* Create the next-ids file. */ 6644 return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n", 6645 pool); 6646} 6647 6648/* Store the property list for transaction TXN_ID in PROPLIST. 6649 Perform temporary allocations in POOL. */ 6650static svn_error_t * 6651get_txn_proplist(apr_hash_t *proplist, 6652 svn_fs_t *fs, 6653 const char *txn_id, 6654 apr_pool_t *pool) 6655{ 6656 svn_stream_t *stream; 6657 6658 /* Check for issue #3696. (When we find and fix the cause, we can change 6659 * this to an assertion.) */ 6660 if (txn_id == NULL) 6661 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 6662 _("Internal error: a null transaction id was " 6663 "passed to get_txn_proplist()")); 6664 6665 /* Open the transaction properties file. */ 6666 SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool), 6667 pool, pool)); 6668 6669 /* Read in the property list. */ 6670 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 6671 6672 return svn_stream_close(stream); 6673} 6674 6675svn_error_t * 6676svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn, 6677 const char *name, 6678 const svn_string_t *value, 6679 apr_pool_t *pool) 6680{ 6681 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t)); 6682 svn_prop_t prop; 6683 6684 prop.name = name; 6685 prop.value = value; 6686 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 6687 6688 return svn_fs_fs__change_txn_props(txn, props, pool); 6689} 6690 6691svn_error_t * 6692svn_fs_fs__change_txn_props(svn_fs_txn_t *txn, 6693 const apr_array_header_t *props, 6694 apr_pool_t *pool) 6695{ 6696 const char *txn_prop_filename; 6697 svn_stringbuf_t *buf; 6698 svn_stream_t *stream; 6699 apr_hash_t *txn_prop = apr_hash_make(pool); 6700 int i; 6701 svn_error_t *err; 6702 6703 err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool); 6704 /* Here - and here only - we need to deal with the possibility that the 6705 transaction property file doesn't yet exist. The rest of the 6706 implementation assumes that the file exists, but we're called to set the 6707 initial transaction properties as the transaction is being created. */ 6708 if (err && (APR_STATUS_IS_ENOENT(err->apr_err))) 6709 svn_error_clear(err); 6710 else if (err) 6711 return svn_error_trace(err); 6712 6713 for (i = 0; i < props->nelts; i++) 6714 { 6715 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 6716 6717 svn_hash_sets(txn_prop, prop->name, prop->value); 6718 } 6719 6720 /* Create a new version of the file and write out the new props. */ 6721 /* Open the transaction properties file. */ 6722 buf = svn_stringbuf_create_ensure(1024, pool); 6723 stream = svn_stream_from_stringbuf(buf, pool); 6724 SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool)); 6725 SVN_ERR(svn_stream_close(stream)); 6726 SVN_ERR(svn_io_write_unique(&txn_prop_filename, 6727 path_txn_dir(txn->fs, txn->id, pool), 6728 buf->data, 6729 buf->len, 6730 svn_io_file_del_none, 6731 pool)); 6732 return svn_io_file_rename(txn_prop_filename, 6733 path_txn_props(txn->fs, txn->id, pool), 6734 pool); 6735} 6736 6737svn_error_t * 6738svn_fs_fs__get_txn(transaction_t **txn_p, 6739 svn_fs_t *fs, 6740 const char *txn_id, 6741 apr_pool_t *pool) 6742{ 6743 transaction_t *txn; 6744 node_revision_t *noderev; 6745 svn_fs_id_t *root_id; 6746 6747 txn = apr_pcalloc(pool, sizeof(*txn)); 6748 txn->proplist = apr_hash_make(pool); 6749 6750 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool)); 6751 root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool); 6752 6753 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool)); 6754 6755 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool); 6756 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool); 6757 txn->copies = NULL; 6758 6759 *txn_p = txn; 6760 6761 return SVN_NO_ERROR; 6762} 6763 6764/* Write out the currently available next node_id NODE_ID and copy_id 6765 COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is 6766 used both for creating new unique nodes for the given transaction, as 6767 well as uniquifying representations. Perform temporary allocations in 6768 POOL. */ 6769static svn_error_t * 6770write_next_ids(svn_fs_t *fs, 6771 const char *txn_id, 6772 const char *node_id, 6773 const char *copy_id, 6774 apr_pool_t *pool) 6775{ 6776 apr_file_t *file; 6777 svn_stream_t *out_stream; 6778 6779 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6780 APR_WRITE | APR_TRUNCATE, 6781 APR_OS_DEFAULT, pool)); 6782 6783 out_stream = svn_stream_from_aprfile2(file, TRUE, pool); 6784 6785 SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id)); 6786 6787 SVN_ERR(svn_stream_close(out_stream)); 6788 return svn_io_file_close(file, pool); 6789} 6790 6791/* Find out what the next unique node-id and copy-id are for 6792 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID 6793 and *COPY_ID. The next node-id is used both for creating new unique 6794 nodes for the given transaction, as well as uniquifying representations. 6795 Perform all allocations in POOL. */ 6796static svn_error_t * 6797read_next_ids(const char **node_id, 6798 const char **copy_id, 6799 svn_fs_t *fs, 6800 const char *txn_id, 6801 apr_pool_t *pool) 6802{ 6803 apr_file_t *file; 6804 char buf[MAX_KEY_SIZE*2+3]; 6805 apr_size_t limit; 6806 char *str, *last_str = buf; 6807 6808 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6809 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6810 6811 limit = sizeof(buf); 6812 SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool)); 6813 6814 SVN_ERR(svn_io_file_close(file, pool)); 6815 6816 /* Parse this into two separate strings. */ 6817 6818 str = svn_cstring_tokenize(" ", &last_str); 6819 if (! str) 6820 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6821 _("next-id file corrupt")); 6822 6823 *node_id = apr_pstrdup(pool, str); 6824 6825 str = svn_cstring_tokenize(" ", &last_str); 6826 if (! str) 6827 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6828 _("next-id file corrupt")); 6829 6830 *copy_id = apr_pstrdup(pool, str); 6831 6832 return SVN_NO_ERROR; 6833} 6834 6835/* Get a new and unique to this transaction node-id for transaction 6836 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P. 6837 Node-ids are guaranteed to be unique to this transction, but may 6838 not necessarily be sequential. Perform all allocations in POOL. */ 6839static svn_error_t * 6840get_new_txn_node_id(const char **node_id_p, 6841 svn_fs_t *fs, 6842 const char *txn_id, 6843 apr_pool_t *pool) 6844{ 6845 const char *cur_node_id, *cur_copy_id; 6846 char *node_id; 6847 apr_size_t len; 6848 6849 /* First read in the current next-ids file. */ 6850 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 6851 6852 node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2); 6853 6854 len = strlen(cur_node_id); 6855 svn_fs_fs__next_key(cur_node_id, &len, node_id); 6856 6857 SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool)); 6858 6859 *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL); 6860 6861 return SVN_NO_ERROR; 6862} 6863 6864svn_error_t * 6865svn_fs_fs__create_node(const svn_fs_id_t **id_p, 6866 svn_fs_t *fs, 6867 node_revision_t *noderev, 6868 const char *copy_id, 6869 const char *txn_id, 6870 apr_pool_t *pool) 6871{ 6872 const char *node_id; 6873 const svn_fs_id_t *id; 6874 6875 /* Get a new node-id for this node. */ 6876 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool)); 6877 6878 id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6879 6880 noderev->id = id; 6881 6882 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 6883 6884 *id_p = id; 6885 6886 return SVN_NO_ERROR; 6887} 6888 6889svn_error_t * 6890svn_fs_fs__purge_txn(svn_fs_t *fs, 6891 const char *txn_id, 6892 apr_pool_t *pool) 6893{ 6894 fs_fs_data_t *ffd = fs->fsap_data; 6895 6896 /* Remove the shared transaction object associated with this transaction. */ 6897 SVN_ERR(purge_shared_txn(fs, txn_id, pool)); 6898 /* Remove the directory associated with this transaction. */ 6899 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE, 6900 NULL, NULL, pool)); 6901 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 6902 { 6903 /* Delete protorev and its lock, which aren't in the txn 6904 directory. It's OK if they don't exist (for example, if this 6905 is post-commit and the proto-rev has been moved into 6906 place). */ 6907 SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool), 6908 TRUE, pool)); 6909 SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool), 6910 TRUE, pool)); 6911 } 6912 return SVN_NO_ERROR; 6913} 6914 6915 6916svn_error_t * 6917svn_fs_fs__abort_txn(svn_fs_txn_t *txn, 6918 apr_pool_t *pool) 6919{ 6920 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 6921 6922 /* Now, purge the transaction. */ 6923 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool), 6924 apr_psprintf(pool, _("Transaction '%s' cleanup failed"), 6925 txn->id)); 6926 6927 return SVN_NO_ERROR; 6928} 6929 6930 6931svn_error_t * 6932svn_fs_fs__set_entry(svn_fs_t *fs, 6933 const char *txn_id, 6934 node_revision_t *parent_noderev, 6935 const char *name, 6936 const svn_fs_id_t *id, 6937 svn_node_kind_t kind, 6938 apr_pool_t *pool) 6939{ 6940 representation_t *rep = parent_noderev->data_rep; 6941 const char *filename = path_txn_node_children(fs, parent_noderev->id, pool); 6942 apr_file_t *file; 6943 svn_stream_t *out; 6944 fs_fs_data_t *ffd = fs->fsap_data; 6945 apr_pool_t *subpool = svn_pool_create(pool); 6946 6947 if (!rep || !rep->txn_id) 6948 { 6949 const char *unique_suffix; 6950 apr_hash_t *entries; 6951 6952 /* Before we can modify the directory, we need to dump its old 6953 contents into a mutable representation file. */ 6954 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev, 6955 subpool)); 6956 SVN_ERR(unparse_dir_entries(&entries, entries, subpool)); 6957 SVN_ERR(svn_io_file_open(&file, filename, 6958 APR_WRITE | APR_CREATE | APR_BUFFERED, 6959 APR_OS_DEFAULT, pool)); 6960 out = svn_stream_from_aprfile2(file, TRUE, pool); 6961 SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool)); 6962 6963 svn_pool_clear(subpool); 6964 6965 /* Mark the node-rev's data rep as mutable. */ 6966 rep = apr_pcalloc(pool, sizeof(*rep)); 6967 rep->revision = SVN_INVALID_REVNUM; 6968 rep->txn_id = txn_id; 6969 SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool)); 6970 rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix); 6971 parent_noderev->data_rep = rep; 6972 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id, 6973 parent_noderev, FALSE, pool)); 6974 } 6975 else 6976 { 6977 /* The directory rep is already mutable, so just open it for append. */ 6978 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND, 6979 APR_OS_DEFAULT, pool)); 6980 out = svn_stream_from_aprfile2(file, TRUE, pool); 6981 } 6982 6983 /* if we have a directory cache for this transaction, update it */ 6984 if (ffd->txn_dir_cache) 6985 { 6986 /* build parameters: (name, new entry) pair */ 6987 const char *key = 6988 svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data; 6989 replace_baton_t baton; 6990 6991 baton.name = name; 6992 baton.new_entry = NULL; 6993 6994 if (id) 6995 { 6996 baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry)); 6997 baton.new_entry->name = name; 6998 baton.new_entry->kind = kind; 6999 baton.new_entry->id = id; 7000 } 7001 7002 /* actually update the cached directory (if cached) */ 7003 SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key, 7004 svn_fs_fs__replace_dir_entry, &baton, 7005 subpool)); 7006 } 7007 svn_pool_clear(subpool); 7008 7009 /* Append an incremental hash entry for the entry change. */ 7010 if (id) 7011 { 7012 const char *val = unparse_dir_entry(kind, id, subpool); 7013 7014 SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n" 7015 "V %" APR_SIZE_T_FMT "\n%s\n", 7016 strlen(name), name, 7017 strlen(val), val)); 7018 } 7019 else 7020 { 7021 SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n", 7022 strlen(name), name)); 7023 } 7024 7025 SVN_ERR(svn_io_file_close(file, subpool)); 7026 svn_pool_destroy(subpool); 7027 return SVN_NO_ERROR; 7028} 7029 7030/* Write a single change entry, path PATH, change CHANGE, and copyfrom 7031 string COPYFROM, into the file specified by FILE. Only include the 7032 node kind field if INCLUDE_NODE_KIND is true. All temporary 7033 allocations are in POOL. */ 7034static svn_error_t * 7035write_change_entry(apr_file_t *file, 7036 const char *path, 7037 svn_fs_path_change2_t *change, 7038 svn_boolean_t include_node_kind, 7039 apr_pool_t *pool) 7040{ 7041 const char *idstr, *buf; 7042 const char *change_string = NULL; 7043 const char *kind_string = ""; 7044 7045 switch (change->change_kind) 7046 { 7047 case svn_fs_path_change_modify: 7048 change_string = ACTION_MODIFY; 7049 break; 7050 case svn_fs_path_change_add: 7051 change_string = ACTION_ADD; 7052 break; 7053 case svn_fs_path_change_delete: 7054 change_string = ACTION_DELETE; 7055 break; 7056 case svn_fs_path_change_replace: 7057 change_string = ACTION_REPLACE; 7058 break; 7059 case svn_fs_path_change_reset: 7060 change_string = ACTION_RESET; 7061 break; 7062 default: 7063 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7064 _("Invalid change type %d"), 7065 change->change_kind); 7066 } 7067 7068 if (change->node_rev_id) 7069 idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data; 7070 else 7071 idstr = ACTION_RESET; 7072 7073 if (include_node_kind) 7074 { 7075 SVN_ERR_ASSERT(change->node_kind == svn_node_dir 7076 || change->node_kind == svn_node_file); 7077 kind_string = apr_psprintf(pool, "-%s", 7078 change->node_kind == svn_node_dir 7079 ? KIND_DIR : KIND_FILE); 7080 } 7081 buf = apr_psprintf(pool, "%s %s%s %s %s %s\n", 7082 idstr, change_string, kind_string, 7083 change->text_mod ? FLAG_TRUE : FLAG_FALSE, 7084 change->prop_mod ? FLAG_TRUE : FLAG_FALSE, 7085 path); 7086 7087 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7088 7089 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) 7090 { 7091 buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev, 7092 change->copyfrom_path); 7093 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7094 } 7095 7096 return svn_io_file_write_full(file, "\n", 1, NULL, pool); 7097} 7098 7099svn_error_t * 7100svn_fs_fs__add_change(svn_fs_t *fs, 7101 const char *txn_id, 7102 const char *path, 7103 const svn_fs_id_t *id, 7104 svn_fs_path_change_kind_t change_kind, 7105 svn_boolean_t text_mod, 7106 svn_boolean_t prop_mod, 7107 svn_node_kind_t node_kind, 7108 svn_revnum_t copyfrom_rev, 7109 const char *copyfrom_path, 7110 apr_pool_t *pool) 7111{ 7112 apr_file_t *file; 7113 svn_fs_path_change2_t *change; 7114 7115 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 7116 APR_APPEND | APR_WRITE | APR_CREATE 7117 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7118 7119 change = svn_fs__path_change_create_internal(id, change_kind, pool); 7120 change->text_mod = text_mod; 7121 change->prop_mod = prop_mod; 7122 change->node_kind = node_kind; 7123 change->copyfrom_rev = copyfrom_rev; 7124 change->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 7125 7126 SVN_ERR(write_change_entry(file, path, change, TRUE, pool)); 7127 7128 return svn_io_file_close(file, pool); 7129} 7130 7131/* This baton is used by the representation writing streams. It keeps 7132 track of the checksum information as well as the total size of the 7133 representation so far. */ 7134struct rep_write_baton 7135{ 7136 /* The FS we are writing to. */ 7137 svn_fs_t *fs; 7138 7139 /* Actual file to which we are writing. */ 7140 svn_stream_t *rep_stream; 7141 7142 /* A stream from the delta combiner. Data written here gets 7143 deltified, then eventually written to rep_stream. */ 7144 svn_stream_t *delta_stream; 7145 7146 /* Where is this representation header stored. */ 7147 apr_off_t rep_offset; 7148 7149 /* Start of the actual data. */ 7150 apr_off_t delta_start; 7151 7152 /* How many bytes have been written to this rep already. */ 7153 svn_filesize_t rep_size; 7154 7155 /* The node revision for which we're writing out info. */ 7156 node_revision_t *noderev; 7157 7158 /* Actual output file. */ 7159 apr_file_t *file; 7160 /* Lock 'cookie' used to unlock the output file once we've finished 7161 writing to it. */ 7162 void *lockcookie; 7163 7164 svn_checksum_ctx_t *md5_checksum_ctx; 7165 svn_checksum_ctx_t *sha1_checksum_ctx; 7166 7167 apr_pool_t *pool; 7168 7169 apr_pool_t *parent_pool; 7170}; 7171 7172/* Handler for the write method of the representation writable stream. 7173 BATON is a rep_write_baton, DATA is the data to write, and *LEN is 7174 the length of this data. */ 7175static svn_error_t * 7176rep_write_contents(void *baton, 7177 const char *data, 7178 apr_size_t *len) 7179{ 7180 struct rep_write_baton *b = baton; 7181 7182 SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len)); 7183 SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len)); 7184 b->rep_size += *len; 7185 7186 /* If we are writing a delta, use that stream. */ 7187 if (b->delta_stream) 7188 return svn_stream_write(b->delta_stream, data, len); 7189 else 7190 return svn_stream_write(b->rep_stream, data, len); 7191} 7192 7193/* Given a node-revision NODEREV in filesystem FS, return the 7194 representation in *REP to use as the base for a text representation 7195 delta if PROPS is FALSE. If PROPS has been set, a suitable props 7196 base representation will be returned. Perform temporary allocations 7197 in *POOL. */ 7198static svn_error_t * 7199choose_delta_base(representation_t **rep, 7200 svn_fs_t *fs, 7201 node_revision_t *noderev, 7202 svn_boolean_t props, 7203 apr_pool_t *pool) 7204{ 7205 int count; 7206 int walk; 7207 node_revision_t *base; 7208 fs_fs_data_t *ffd = fs->fsap_data; 7209 svn_boolean_t maybe_shared_rep = FALSE; 7210 7211 /* If we have no predecessors, then use the empty stream as a 7212 base. */ 7213 if (! noderev->predecessor_count) 7214 { 7215 *rep = NULL; 7216 return SVN_NO_ERROR; 7217 } 7218 7219 /* Flip the rightmost '1' bit of the predecessor count to determine 7220 which file rev (counting from 0) we want to use. (To see why 7221 count & (count - 1) unsets the rightmost set bit, think about how 7222 you decrement a binary number.) */ 7223 count = noderev->predecessor_count; 7224 count = count & (count - 1); 7225 7226 /* We use skip delta for limiting the number of delta operations 7227 along very long node histories. Close to HEAD however, we create 7228 a linear history to minimize delta size. */ 7229 walk = noderev->predecessor_count - count; 7230 if (walk < (int)ffd->max_linear_deltification) 7231 count = noderev->predecessor_count - 1; 7232 7233 /* Finding the delta base over a very long distance can become extremely 7234 expensive for very deep histories, possibly causing client timeouts etc. 7235 OTOH, this is a rare operation and its gains are minimal. Lets simply 7236 start deltification anew close every other 1000 changes or so. */ 7237 if (walk > (int)ffd->max_deltification_walk) 7238 { 7239 *rep = NULL; 7240 return SVN_NO_ERROR; 7241 } 7242 7243 /* Walk back a number of predecessors equal to the difference 7244 between count and the original predecessor count. (For example, 7245 if noderev has ten predecessors and we want the eighth file rev, 7246 walk back two predecessors.) */ 7247 base = noderev; 7248 while ((count++) < noderev->predecessor_count) 7249 { 7250 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs, 7251 base->predecessor_id, pool)); 7252 7253 /* If there is a shared rep along the way, we need to limit the 7254 * length of the deltification chain. 7255 * 7256 * Please note that copied nodes - such as branch directories - will 7257 * look the same (false positive) while reps shared within the same 7258 * revision will not be caught (false negative). 7259 */ 7260 if (props) 7261 { 7262 if ( base->prop_rep 7263 && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision) 7264 maybe_shared_rep = TRUE; 7265 } 7266 else 7267 { 7268 if ( base->data_rep 7269 && svn_fs_fs__id_rev(base->id) > base->data_rep->revision) 7270 maybe_shared_rep = TRUE; 7271 } 7272 } 7273 7274 /* return a suitable base representation */ 7275 *rep = props ? base->prop_rep : base->data_rep; 7276 7277 /* if we encountered a shared rep, it's parent chain may be different 7278 * from the node-rev parent chain. */ 7279 if (*rep && maybe_shared_rep) 7280 { 7281 /* Check whether the length of the deltification chain is acceptable. 7282 * Otherwise, shared reps may form a non-skipping delta chain in 7283 * extreme cases. */ 7284 apr_pool_t *sub_pool = svn_pool_create(pool); 7285 representation_t base_rep = **rep; 7286 7287 /* Some reasonable limit, depending on how acceptable longer linear 7288 * chains are in this repo. Also, allow for some minimal chain. */ 7289 int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2; 7290 7291 /* re-use open files between iterations */ 7292 svn_revnum_t rev_hint = SVN_INVALID_REVNUM; 7293 apr_file_t *file_hint = NULL; 7294 7295 /* follow the delta chain towards the end but for at most 7296 * MAX_CHAIN_LENGTH steps. */ 7297 for (; max_chain_length; --max_chain_length) 7298 { 7299 struct rep_state *rep_state; 7300 struct rep_args *rep_args; 7301 7302 SVN_ERR(create_rep_state_body(&rep_state, 7303 &rep_args, 7304 &file_hint, 7305 &rev_hint, 7306 &base_rep, 7307 fs, 7308 sub_pool)); 7309 if (!rep_args->is_delta || !rep_args->base_revision) 7310 break; 7311 7312 base_rep.revision = rep_args->base_revision; 7313 base_rep.offset = rep_args->base_offset; 7314 base_rep.size = rep_args->base_length; 7315 base_rep.txn_id = NULL; 7316 } 7317 7318 /* start new delta chain if the current one has grown too long */ 7319 if (max_chain_length == 0) 7320 *rep = NULL; 7321 7322 svn_pool_destroy(sub_pool); 7323 } 7324 7325 /* verify that the reps don't form a degenerated '*/ 7326 return SVN_NO_ERROR; 7327} 7328 7329/* Something went wrong and the pool for the rep write is being 7330 cleared before we've finished writing the rep. So we need 7331 to remove the rep from the protorevfile and we need to unlock 7332 the protorevfile. */ 7333static apr_status_t 7334rep_write_cleanup(void *data) 7335{ 7336 struct rep_write_baton *b = data; 7337 const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7338 svn_error_t *err; 7339 7340 /* Truncate and close the protorevfile. */ 7341 err = svn_io_file_trunc(b->file, b->rep_offset, b->pool); 7342 err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool)); 7343 7344 /* Remove our lock regardless of any preceeding errors so that the 7345 being_written flag is always removed and stays consistent with the 7346 file lock which will be removed no matter what since the pool is 7347 going away. */ 7348 err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id, 7349 b->lockcookie, b->pool)); 7350 if (err) 7351 { 7352 apr_status_t rc = err->apr_err; 7353 svn_error_clear(err); 7354 return rc; 7355 } 7356 7357 return APR_SUCCESS; 7358} 7359 7360 7361/* Get a rep_write_baton and store it in *WB_P for the representation 7362 indicated by NODEREV in filesystem FS. Perform allocations in 7363 POOL. Only appropriate for file contents, not for props or 7364 directory contents. */ 7365static svn_error_t * 7366rep_write_get_baton(struct rep_write_baton **wb_p, 7367 svn_fs_t *fs, 7368 node_revision_t *noderev, 7369 apr_pool_t *pool) 7370{ 7371 struct rep_write_baton *b; 7372 apr_file_t *file; 7373 representation_t *base_rep; 7374 svn_stream_t *source; 7375 const char *header; 7376 svn_txdelta_window_handler_t wh; 7377 void *whb; 7378 fs_fs_data_t *ffd = fs->fsap_data; 7379 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7380 7381 b = apr_pcalloc(pool, sizeof(*b)); 7382 7383 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7384 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7385 7386 b->fs = fs; 7387 b->parent_pool = pool; 7388 b->pool = svn_pool_create(pool); 7389 b->rep_size = 0; 7390 b->noderev = noderev; 7391 7392 /* Open the prototype rev file and seek to its end. */ 7393 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, 7394 fs, svn_fs_fs__id_txn_id(noderev->id), 7395 b->pool)); 7396 7397 b->file = file; 7398 b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool); 7399 7400 SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool)); 7401 7402 /* Get the base for this delta. */ 7403 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool)); 7404 SVN_ERR(read_representation(&source, fs, base_rep, b->pool)); 7405 7406 /* Write out the rep header. */ 7407 if (base_rep) 7408 { 7409 header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7410 SVN_FILESIZE_T_FMT "\n", 7411 base_rep->revision, base_rep->offset, 7412 base_rep->size); 7413 } 7414 else 7415 { 7416 header = REP_DELTA "\n"; 7417 } 7418 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7419 b->pool)); 7420 7421 /* Now determine the offset of the actual svndiff data. */ 7422 SVN_ERR(get_file_offset(&b->delta_start, file, b->pool)); 7423 7424 /* Cleanup in case something goes wrong. */ 7425 apr_pool_cleanup_register(b->pool, b, rep_write_cleanup, 7426 apr_pool_cleanup_null); 7427 7428 /* Prepare to write the svndiff data. */ 7429 svn_txdelta_to_svndiff3(&wh, 7430 &whb, 7431 b->rep_stream, 7432 diff_version, 7433 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7434 pool); 7435 7436 b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool); 7437 7438 *wb_p = b; 7439 7440 return SVN_NO_ERROR; 7441} 7442 7443/* For the hash REP->SHA1, try to find an already existing representation 7444 in FS and return it in *OUT_REP. If no such representation exists or 7445 if rep sharing has been disabled for FS, NULL will be returned. Since 7446 there may be new duplicate representations within the same uncommitted 7447 revision, those can be passed in REPS_HASH (maps a sha1 digest onto 7448 representation_t*), otherwise pass in NULL for REPS_HASH. 7449 POOL will be used for allocations. The lifetime of the returned rep is 7450 limited by both, POOL and REP lifetime. 7451 */ 7452static svn_error_t * 7453get_shared_rep(representation_t **old_rep, 7454 svn_fs_t *fs, 7455 representation_t *rep, 7456 apr_hash_t *reps_hash, 7457 apr_pool_t *pool) 7458{ 7459 svn_error_t *err; 7460 fs_fs_data_t *ffd = fs->fsap_data; 7461 7462 /* Return NULL, if rep sharing has been disabled. */ 7463 *old_rep = NULL; 7464 if (!ffd->rep_sharing_allowed) 7465 return SVN_NO_ERROR; 7466 7467 /* Check and see if we already have a representation somewhere that's 7468 identical to the one we just wrote out. Start with the hash lookup 7469 because it is cheepest. */ 7470 if (reps_hash) 7471 *old_rep = apr_hash_get(reps_hash, 7472 rep->sha1_checksum->digest, 7473 APR_SHA1_DIGESTSIZE); 7474 7475 /* If we haven't found anything yet, try harder and consult our DB. */ 7476 if (*old_rep == NULL) 7477 { 7478 err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum, 7479 pool); 7480 /* ### Other error codes that we shouldn't mask out? */ 7481 if (err == SVN_NO_ERROR) 7482 { 7483 if (*old_rep) 7484 SVN_ERR(verify_walker(*old_rep, NULL, fs, pool)); 7485 } 7486 else if (err->apr_err == SVN_ERR_FS_CORRUPT 7487 || SVN_ERROR_IN_CATEGORY(err->apr_err, 7488 SVN_ERR_MALFUNC_CATEGORY_START)) 7489 { 7490 /* Fatal error; don't mask it. 7491 7492 In particular, this block is triggered when the rep-cache refers 7493 to revisions in the future. We signal that as a corruption situation 7494 since, once those revisions are less than youngest (because of more 7495 commits), the rep-cache would be invalid. 7496 */ 7497 SVN_ERR(err); 7498 } 7499 else 7500 { 7501 /* Something's wrong with the rep-sharing index. We can continue 7502 without rep-sharing, but warn. 7503 */ 7504 (fs->warning)(fs->warning_baton, err); 7505 svn_error_clear(err); 7506 *old_rep = NULL; 7507 } 7508 } 7509 7510 /* look for intra-revision matches (usually data reps but not limited 7511 to them in case props happen to look like some data rep) 7512 */ 7513 if (*old_rep == NULL && rep->txn_id) 7514 { 7515 svn_node_kind_t kind; 7516 const char *file_name 7517 = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool); 7518 7519 /* in our txn, is there a rep file named with the wanted SHA1? 7520 If so, read it and use that rep. 7521 */ 7522 SVN_ERR(svn_io_check_path(file_name, &kind, pool)); 7523 if (kind == svn_node_file) 7524 { 7525 svn_stringbuf_t *rep_string; 7526 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool)); 7527 SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data, 7528 rep->txn_id, FALSE, pool)); 7529 } 7530 } 7531 7532 /* Add information that is missing in the cached data. */ 7533 if (*old_rep) 7534 { 7535 /* Use the old rep for this content. */ 7536 (*old_rep)->md5_checksum = rep->md5_checksum; 7537 (*old_rep)->uniquifier = rep->uniquifier; 7538 } 7539 7540 return SVN_NO_ERROR; 7541} 7542 7543/* Close handler for the representation write stream. BATON is a 7544 rep_write_baton. Writes out a new node-rev that correctly 7545 references the representation we just finished writing. */ 7546static svn_error_t * 7547rep_write_contents_close(void *baton) 7548{ 7549 struct rep_write_baton *b = baton; 7550 const char *unique_suffix; 7551 representation_t *rep; 7552 representation_t *old_rep; 7553 apr_off_t offset; 7554 7555 rep = apr_pcalloc(b->parent_pool, sizeof(*rep)); 7556 rep->offset = b->rep_offset; 7557 7558 /* Close our delta stream so the last bits of svndiff are written 7559 out. */ 7560 if (b->delta_stream) 7561 SVN_ERR(svn_stream_close(b->delta_stream)); 7562 7563 /* Determine the length of the svndiff data. */ 7564 SVN_ERR(get_file_offset(&offset, b->file, b->pool)); 7565 rep->size = offset - b->delta_start; 7566 7567 /* Fill in the rest of the representation field. */ 7568 rep->expanded_size = b->rep_size; 7569 rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7570 SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool)); 7571 rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id, 7572 unique_suffix); 7573 rep->revision = SVN_INVALID_REVNUM; 7574 7575 /* Finalize the checksum. */ 7576 SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx, 7577 b->parent_pool)); 7578 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx, 7579 b->parent_pool)); 7580 7581 /* Check and see if we already have a representation somewhere that's 7582 identical to the one we just wrote out. */ 7583 SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool)); 7584 7585 if (old_rep) 7586 { 7587 /* We need to erase from the protorev the data we just wrote. */ 7588 SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool)); 7589 7590 /* Use the old rep for this content. */ 7591 b->noderev->data_rep = old_rep; 7592 } 7593 else 7594 { 7595 /* Write out our cosmetic end marker. */ 7596 SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n")); 7597 7598 b->noderev->data_rep = rep; 7599 } 7600 7601 /* Remove cleanup callback. */ 7602 apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup); 7603 7604 /* Write out the new node-rev information. */ 7605 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE, 7606 b->pool)); 7607 if (!old_rep) 7608 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool)); 7609 7610 SVN_ERR(svn_io_file_close(b->file, b->pool)); 7611 SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool)); 7612 svn_pool_destroy(b->pool); 7613 7614 return SVN_NO_ERROR; 7615} 7616 7617/* Store a writable stream in *CONTENTS_P that will receive all data 7618 written and store it as the file data representation referenced by 7619 NODEREV in filesystem FS. Perform temporary allocations in 7620 POOL. Only appropriate for file data, not props or directory 7621 contents. */ 7622static svn_error_t * 7623set_representation(svn_stream_t **contents_p, 7624 svn_fs_t *fs, 7625 node_revision_t *noderev, 7626 apr_pool_t *pool) 7627{ 7628 struct rep_write_baton *wb; 7629 7630 if (! svn_fs_fs__id_txn_id(noderev->id)) 7631 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7632 _("Attempted to write to non-transaction '%s'"), 7633 svn_fs_fs__id_unparse(noderev->id, pool)->data); 7634 7635 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool)); 7636 7637 *contents_p = svn_stream_create(wb, pool); 7638 svn_stream_set_write(*contents_p, rep_write_contents); 7639 svn_stream_set_close(*contents_p, rep_write_contents_close); 7640 7641 return SVN_NO_ERROR; 7642} 7643 7644svn_error_t * 7645svn_fs_fs__set_contents(svn_stream_t **stream, 7646 svn_fs_t *fs, 7647 node_revision_t *noderev, 7648 apr_pool_t *pool) 7649{ 7650 if (noderev->kind != svn_node_file) 7651 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 7652 _("Can't set text contents of a directory")); 7653 7654 return set_representation(stream, fs, noderev, pool); 7655} 7656 7657svn_error_t * 7658svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p, 7659 svn_fs_t *fs, 7660 const svn_fs_id_t *old_idp, 7661 node_revision_t *new_noderev, 7662 const char *copy_id, 7663 const char *txn_id, 7664 apr_pool_t *pool) 7665{ 7666 const svn_fs_id_t *id; 7667 7668 if (! copy_id) 7669 copy_id = svn_fs_fs__id_copy_id(old_idp); 7670 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id, 7671 txn_id, pool); 7672 7673 new_noderev->id = id; 7674 7675 if (! new_noderev->copyroot_path) 7676 { 7677 new_noderev->copyroot_path = apr_pstrdup(pool, 7678 new_noderev->created_path); 7679 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id); 7680 } 7681 7682 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE, 7683 pool)); 7684 7685 *new_id_p = id; 7686 7687 return SVN_NO_ERROR; 7688} 7689 7690svn_error_t * 7691svn_fs_fs__set_proplist(svn_fs_t *fs, 7692 node_revision_t *noderev, 7693 apr_hash_t *proplist, 7694 apr_pool_t *pool) 7695{ 7696 const char *filename = path_txn_node_props(fs, noderev->id, pool); 7697 apr_file_t *file; 7698 svn_stream_t *out; 7699 7700 /* Dump the property list to the mutable property file. */ 7701 SVN_ERR(svn_io_file_open(&file, filename, 7702 APR_WRITE | APR_CREATE | APR_TRUNCATE 7703 | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7704 out = svn_stream_from_aprfile2(file, TRUE, pool); 7705 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool)); 7706 SVN_ERR(svn_io_file_close(file, pool)); 7707 7708 /* Mark the node-rev's prop rep as mutable, if not already done. */ 7709 if (!noderev->prop_rep || !noderev->prop_rep->txn_id) 7710 { 7711 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep)); 7712 noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id); 7713 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 7714 } 7715 7716 return SVN_NO_ERROR; 7717} 7718 7719/* Read the 'current' file for filesystem FS and store the next 7720 available node id in *NODE_ID, and the next available copy id in 7721 *COPY_ID. Allocations are performed from POOL. */ 7722static svn_error_t * 7723get_next_revision_ids(const char **node_id, 7724 const char **copy_id, 7725 svn_fs_t *fs, 7726 apr_pool_t *pool) 7727{ 7728 char *buf; 7729 char *str; 7730 svn_stringbuf_t *content; 7731 7732 SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool)); 7733 buf = content->data; 7734 7735 str = svn_cstring_tokenize(" ", &buf); 7736 if (! str) 7737 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7738 _("Corrupt 'current' file")); 7739 7740 str = svn_cstring_tokenize(" ", &buf); 7741 if (! str) 7742 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7743 _("Corrupt 'current' file")); 7744 7745 *node_id = apr_pstrdup(pool, str); 7746 7747 str = svn_cstring_tokenize(" \n", &buf); 7748 if (! str) 7749 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7750 _("Corrupt 'current' file")); 7751 7752 *copy_id = apr_pstrdup(pool, str); 7753 7754 return SVN_NO_ERROR; 7755} 7756 7757/* This baton is used by the stream created for write_hash_rep. */ 7758struct write_hash_baton 7759{ 7760 svn_stream_t *stream; 7761 7762 apr_size_t size; 7763 7764 svn_checksum_ctx_t *md5_ctx; 7765 svn_checksum_ctx_t *sha1_ctx; 7766}; 7767 7768/* The handler for the write_hash_rep stream. BATON is a 7769 write_hash_baton, DATA has the data to write and *LEN is the number 7770 of bytes to write. */ 7771static svn_error_t * 7772write_hash_handler(void *baton, 7773 const char *data, 7774 apr_size_t *len) 7775{ 7776 struct write_hash_baton *whb = baton; 7777 7778 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 7779 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 7780 7781 SVN_ERR(svn_stream_write(whb->stream, data, len)); 7782 whb->size += *len; 7783 7784 return SVN_NO_ERROR; 7785} 7786 7787/* Write out the hash HASH as a text representation to file FILE. In 7788 the process, record position, the total size of the dump and MD5 as 7789 well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH 7790 is not NULL, it will be used in addition to the on-disk cache to find 7791 earlier reps with the same content. When such existing reps can be 7792 found, we will truncate the one just written from the file and return 7793 the existing rep. Perform temporary allocations in POOL. */ 7794static svn_error_t * 7795write_hash_rep(representation_t *rep, 7796 apr_file_t *file, 7797 apr_hash_t *hash, 7798 svn_fs_t *fs, 7799 apr_hash_t *reps_hash, 7800 apr_pool_t *pool) 7801{ 7802 svn_stream_t *stream; 7803 struct write_hash_baton *whb; 7804 representation_t *old_rep; 7805 7806 SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7807 7808 whb = apr_pcalloc(pool, sizeof(*whb)); 7809 7810 whb->stream = svn_stream_from_aprfile2(file, TRUE, pool); 7811 whb->size = 0; 7812 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7813 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7814 7815 stream = svn_stream_create(whb, pool); 7816 svn_stream_set_write(stream, write_hash_handler); 7817 7818 SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n")); 7819 7820 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7821 7822 /* Store the results. */ 7823 SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7824 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7825 7826 /* Check and see if we already have a representation somewhere that's 7827 identical to the one we just wrote out. */ 7828 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7829 7830 if (old_rep) 7831 { 7832 /* We need to erase from the protorev the data we just wrote. */ 7833 SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7834 7835 /* Use the old rep for this content. */ 7836 memcpy(rep, old_rep, sizeof (*rep)); 7837 } 7838 else 7839 { 7840 /* Write out our cosmetic end marker. */ 7841 SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n")); 7842 7843 /* update the representation */ 7844 rep->size = whb->size; 7845 rep->expanded_size = 0; 7846 } 7847 7848 return SVN_NO_ERROR; 7849} 7850 7851/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified 7852 text representation to file FILE. In the process, record the total size 7853 and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH 7854 is not NULL, it will be used in addition to the on-disk cache to find 7855 earlier reps with the same content. When such existing reps can be found, 7856 we will truncate the one just written from the file and return the existing 7857 rep. If PROPS is set, assume that we want to a props representation as 7858 the base for our delta. Perform temporary allocations in POOL. */ 7859static svn_error_t * 7860write_hash_delta_rep(representation_t *rep, 7861 apr_file_t *file, 7862 apr_hash_t *hash, 7863 svn_fs_t *fs, 7864 node_revision_t *noderev, 7865 apr_hash_t *reps_hash, 7866 svn_boolean_t props, 7867 apr_pool_t *pool) 7868{ 7869 svn_txdelta_window_handler_t diff_wh; 7870 void *diff_whb; 7871 7872 svn_stream_t *file_stream; 7873 svn_stream_t *stream; 7874 representation_t *base_rep; 7875 representation_t *old_rep; 7876 svn_stream_t *source; 7877 const char *header; 7878 7879 apr_off_t rep_end = 0; 7880 apr_off_t delta_start = 0; 7881 7882 struct write_hash_baton *whb; 7883 fs_fs_data_t *ffd = fs->fsap_data; 7884 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7885 7886 /* Get the base for this delta. */ 7887 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool)); 7888 SVN_ERR(read_representation(&source, fs, base_rep, pool)); 7889 7890 SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7891 7892 /* Write out the rep header. */ 7893 if (base_rep) 7894 { 7895 header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7896 SVN_FILESIZE_T_FMT "\n", 7897 base_rep->revision, base_rep->offset, 7898 base_rep->size); 7899 } 7900 else 7901 { 7902 header = REP_DELTA "\n"; 7903 } 7904 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7905 pool)); 7906 7907 SVN_ERR(get_file_offset(&delta_start, file, pool)); 7908 file_stream = svn_stream_from_aprfile2(file, TRUE, pool); 7909 7910 /* Prepare to write the svndiff data. */ 7911 svn_txdelta_to_svndiff3(&diff_wh, 7912 &diff_whb, 7913 file_stream, 7914 diff_version, 7915 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7916 pool); 7917 7918 whb = apr_pcalloc(pool, sizeof(*whb)); 7919 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool); 7920 whb->size = 0; 7921 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7922 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7923 7924 /* serialize the hash */ 7925 stream = svn_stream_create(whb, pool); 7926 svn_stream_set_write(stream, write_hash_handler); 7927 7928 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7929 SVN_ERR(svn_stream_close(whb->stream)); 7930 7931 /* Store the results. */ 7932 SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7933 SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7934 7935 /* Check and see if we already have a representation somewhere that's 7936 identical to the one we just wrote out. */ 7937 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7938 7939 if (old_rep) 7940 { 7941 /* We need to erase from the protorev the data we just wrote. */ 7942 SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7943 7944 /* Use the old rep for this content. */ 7945 memcpy(rep, old_rep, sizeof (*rep)); 7946 } 7947 else 7948 { 7949 /* Write out our cosmetic end marker. */ 7950 SVN_ERR(get_file_offset(&rep_end, file, pool)); 7951 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 7952 7953 /* update the representation */ 7954 rep->expanded_size = whb->size; 7955 rep->size = rep_end - delta_start; 7956 } 7957 7958 return SVN_NO_ERROR; 7959} 7960 7961/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 7962 of (not yet committed) revision REV in FS. Use POOL for temporary 7963 allocations. 7964 7965 If you change this function, consider updating svn_fs_fs__verify() too. 7966 */ 7967static svn_error_t * 7968validate_root_noderev(svn_fs_t *fs, 7969 node_revision_t *root_noderev, 7970 svn_revnum_t rev, 7971 apr_pool_t *pool) 7972{ 7973 svn_revnum_t head_revnum = rev-1; 7974 int head_predecessor_count; 7975 7976 SVN_ERR_ASSERT(rev > 0); 7977 7978 /* Compute HEAD_PREDECESSOR_COUNT. */ 7979 { 7980 svn_fs_root_t *head_revision; 7981 const svn_fs_id_t *head_root_id; 7982 node_revision_t *head_root_noderev; 7983 7984 /* Get /@HEAD's noderev. */ 7985 SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool)); 7986 SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool)); 7987 SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id, 7988 pool)); 7989 7990 head_predecessor_count = head_root_noderev->predecessor_count; 7991 } 7992 7993 /* Check that the root noderev's predecessor count equals REV. 7994 7995 This kind of corruption was seen on svn.apache.org (both on 7996 the root noderev and on other fspaths' noderevs); see 7997 issue #4129. 7998 7999 Normally (rev == root_noderev->predecessor_count), but here we 8000 use a more roundabout check that should only trigger on new instances 8001 of the corruption, rather then trigger on each and every new commit 8002 to a repository that has triggered the bug somewhere in its root 8003 noderev's history. 8004 */ 8005 if (root_noderev->predecessor_count != -1 8006 && (root_noderev->predecessor_count - head_predecessor_count) 8007 != (rev - head_revnum)) 8008 { 8009 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 8010 _("predecessor count for " 8011 "the root node-revision is wrong: " 8012 "found (%d+%ld != %d), committing r%ld"), 8013 head_predecessor_count, 8014 rev - head_revnum, /* This is equal to 1. */ 8015 root_noderev->predecessor_count, 8016 rev); 8017 } 8018 8019 return SVN_NO_ERROR; 8020} 8021 8022/* Copy a node-revision specified by id ID in fileystem FS from a 8023 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 8024 pointer to the new node-id which will be allocated in POOL. 8025 If this is a directory, copy all children as well. 8026 8027 START_NODE_ID and START_COPY_ID are 8028 the first available node and copy ids for this filesystem, for older 8029 FS formats. 8030 8031 REV is the revision number that this proto-rev-file will represent. 8032 8033 INITIAL_OFFSET is the offset of the proto-rev-file on entry to 8034 commit_body. 8035 8036 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 8037 REPS_POOL) of each data rep that is new in this revision. 8038 8039 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 8040 of the representations of each property rep that is new in this 8041 revision. 8042 8043 AT_ROOT is true if the node revision being written is the root 8044 node-revision. It is only controls additional sanity checking 8045 logic. 8046 8047 Temporary allocations are also from POOL. */ 8048static svn_error_t * 8049write_final_rev(const svn_fs_id_t **new_id_p, 8050 apr_file_t *file, 8051 svn_revnum_t rev, 8052 svn_fs_t *fs, 8053 const svn_fs_id_t *id, 8054 const char *start_node_id, 8055 const char *start_copy_id, 8056 apr_off_t initial_offset, 8057 apr_array_header_t *reps_to_cache, 8058 apr_hash_t *reps_hash, 8059 apr_pool_t *reps_pool, 8060 svn_boolean_t at_root, 8061 apr_pool_t *pool) 8062{ 8063 node_revision_t *noderev; 8064 apr_off_t my_offset; 8065 char my_node_id_buf[MAX_KEY_SIZE + 2]; 8066 char my_copy_id_buf[MAX_KEY_SIZE + 2]; 8067 const svn_fs_id_t *new_id; 8068 const char *node_id, *copy_id, *my_node_id, *my_copy_id; 8069 fs_fs_data_t *ffd = fs->fsap_data; 8070 8071 *new_id_p = NULL; 8072 8073 /* Check to see if this is a transaction node. */ 8074 if (! svn_fs_fs__id_txn_id(id)) 8075 return SVN_NO_ERROR; 8076 8077 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 8078 8079 if (noderev->kind == svn_node_dir) 8080 { 8081 apr_pool_t *subpool; 8082 apr_hash_t *entries, *str_entries; 8083 apr_array_header_t *sorted_entries; 8084 int i; 8085 8086 /* This is a directory. Write out all the children first. */ 8087 subpool = svn_pool_create(pool); 8088 8089 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool)); 8090 /* For the sake of the repository administrator sort the entries 8091 so that the final file is deterministic and repeatable, 8092 however the rest of the FSFS code doesn't require any 8093 particular order here. */ 8094 sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically, 8095 pool); 8096 for (i = 0; i < sorted_entries->nelts; ++i) 8097 { 8098 svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i, 8099 svn_sort__item_t).value; 8100 8101 svn_pool_clear(subpool); 8102 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id, 8103 start_node_id, start_copy_id, initial_offset, 8104 reps_to_cache, reps_hash, reps_pool, FALSE, 8105 subpool)); 8106 if (new_id && (svn_fs_fs__id_rev(new_id) == rev)) 8107 dirent->id = svn_fs_fs__id_copy(new_id, pool); 8108 } 8109 svn_pool_destroy(subpool); 8110 8111 if (noderev->data_rep && noderev->data_rep->txn_id) 8112 { 8113 /* Write out the contents of this directory as a text rep. */ 8114 SVN_ERR(unparse_dir_entries(&str_entries, entries, pool)); 8115 8116 noderev->data_rep->txn_id = NULL; 8117 noderev->data_rep->revision = rev; 8118 8119 if (ffd->deltify_directories) 8120 SVN_ERR(write_hash_delta_rep(noderev->data_rep, file, 8121 str_entries, fs, noderev, NULL, 8122 FALSE, pool)); 8123 else 8124 SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries, 8125 fs, NULL, pool)); 8126 } 8127 } 8128 else 8129 { 8130 /* This is a file. We should make sure the data rep, if it 8131 exists in a "this" state, gets rewritten to our new revision 8132 num. */ 8133 8134 if (noderev->data_rep && noderev->data_rep->txn_id) 8135 { 8136 noderev->data_rep->txn_id = NULL; 8137 noderev->data_rep->revision = rev; 8138 8139 /* See issue 3845. Some unknown mechanism caused the 8140 protorev file to get truncated, so check for that 8141 here. */ 8142 if (noderev->data_rep->offset + noderev->data_rep->size 8143 > initial_offset) 8144 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 8145 _("Truncated protorev file detected")); 8146 } 8147 } 8148 8149 /* Fix up the property reps. */ 8150 if (noderev->prop_rep && noderev->prop_rep->txn_id) 8151 { 8152 apr_hash_t *proplist; 8153 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool)); 8154 8155 noderev->prop_rep->txn_id = NULL; 8156 noderev->prop_rep->revision = rev; 8157 8158 if (ffd->deltify_properties) 8159 SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file, 8160 proplist, fs, noderev, reps_hash, 8161 TRUE, pool)); 8162 else 8163 SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist, 8164 fs, reps_hash, pool)); 8165 } 8166 8167 8168 /* Convert our temporary ID into a permanent revision one. */ 8169 SVN_ERR(get_file_offset(&my_offset, file, pool)); 8170 8171 node_id = svn_fs_fs__id_node_id(noderev->id); 8172 if (*node_id == '_') 8173 { 8174 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8175 my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev); 8176 else 8177 { 8178 svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf); 8179 my_node_id = my_node_id_buf; 8180 } 8181 } 8182 else 8183 my_node_id = node_id; 8184 8185 copy_id = svn_fs_fs__id_copy_id(noderev->id); 8186 if (*copy_id == '_') 8187 { 8188 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8189 my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev); 8190 else 8191 { 8192 svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf); 8193 my_copy_id = my_copy_id_buf; 8194 } 8195 } 8196 else 8197 my_copy_id = copy_id; 8198 8199 if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 8200 noderev->copyroot_rev = rev; 8201 8202 new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset, 8203 pool); 8204 8205 noderev->id = new_id; 8206 8207 if (ffd->rep_sharing_allowed) 8208 { 8209 /* Save the data representation's hash in the rep cache. */ 8210 if ( noderev->data_rep && noderev->kind == svn_node_file 8211 && noderev->data_rep->revision == rev) 8212 { 8213 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8214 APR_ARRAY_PUSH(reps_to_cache, representation_t *) 8215 = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool); 8216 } 8217 8218 if (noderev->prop_rep && noderev->prop_rep->revision == rev) 8219 { 8220 /* Add new property reps to hash and on-disk cache. */ 8221 representation_t *copy 8222 = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool); 8223 8224 SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8225 APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy; 8226 8227 apr_hash_set(reps_hash, 8228 copy->sha1_checksum->digest, 8229 APR_SHA1_DIGESTSIZE, 8230 copy); 8231 } 8232 } 8233 8234 /* don't serialize SHA1 for dirs to disk (waste of space) */ 8235 if (noderev->data_rep && noderev->kind == svn_node_dir) 8236 noderev->data_rep->sha1_checksum = NULL; 8237 8238 /* don't serialize SHA1 for props to disk (waste of space) */ 8239 if (noderev->prop_rep) 8240 noderev->prop_rep->sha1_checksum = NULL; 8241 8242 /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 8243 noderev->is_fresh_txn_root = FALSE; 8244 8245 /* Write out our new node-revision. */ 8246 if (at_root) 8247 SVN_ERR(validate_root_noderev(fs, noderev, rev, pool)); 8248 8249 SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool), 8250 noderev, ffd->format, 8251 svn_fs_fs__fs_supports_mergeinfo(fs), 8252 pool)); 8253 8254 /* Return our ID that references the revision file. */ 8255 *new_id_p = noderev->id; 8256 8257 return SVN_NO_ERROR; 8258} 8259 8260/* Write the changed path info from transaction TXN_ID in filesystem 8261 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset 8262 in the file of the beginning of this information. Perform 8263 temporary allocations in POOL. */ 8264static svn_error_t * 8265write_final_changed_path_info(apr_off_t *offset_p, 8266 apr_file_t *file, 8267 svn_fs_t *fs, 8268 const char *txn_id, 8269 apr_pool_t *pool) 8270{ 8271 apr_hash_t *changed_paths; 8272 apr_off_t offset; 8273 apr_pool_t *iterpool = svn_pool_create(pool); 8274 fs_fs_data_t *ffd = fs->fsap_data; 8275 svn_boolean_t include_node_kinds = 8276 ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT; 8277 apr_array_header_t *sorted_changed_paths; 8278 int i; 8279 8280 SVN_ERR(get_file_offset(&offset, file, pool)); 8281 8282 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool)); 8283 /* For the sake of the repository administrator sort the changes so 8284 that the final file is deterministic and repeatable, however the 8285 rest of the FSFS code doesn't require any particular order here. */ 8286 sorted_changed_paths = svn_sort__hash(changed_paths, 8287 svn_sort_compare_items_lexically, pool); 8288 8289 /* Iterate through the changed paths one at a time, and convert the 8290 temporary node-id into a permanent one for each change entry. */ 8291 for (i = 0; i < sorted_changed_paths->nelts; ++i) 8292 { 8293 node_revision_t *noderev; 8294 const svn_fs_id_t *id; 8295 svn_fs_path_change2_t *change; 8296 const char *path; 8297 8298 svn_pool_clear(iterpool); 8299 8300 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value; 8301 path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key; 8302 8303 id = change->node_rev_id; 8304 8305 /* If this was a delete of a mutable node, then it is OK to 8306 leave the change entry pointing to the non-existent temporary 8307 node, since it will never be used. */ 8308 if ((change->change_kind != svn_fs_path_change_delete) && 8309 (! svn_fs_fs__id_txn_id(id))) 8310 { 8311 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool)); 8312 8313 /* noderev has the permanent node-id at this point, so we just 8314 substitute it for the temporary one. */ 8315 change->node_rev_id = noderev->id; 8316 } 8317 8318 /* Write out the new entry into the final rev-file. */ 8319 SVN_ERR(write_change_entry(file, path, change, include_node_kinds, 8320 iterpool)); 8321 } 8322 8323 svn_pool_destroy(iterpool); 8324 8325 *offset_p = offset; 8326 8327 return SVN_NO_ERROR; 8328} 8329 8330/* Atomically update the 'current' file to hold the specifed REV, 8331 NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are 8332 ignored and may be NULL if the FS format does not use them.) 8333 Perform temporary allocations in POOL. */ 8334static svn_error_t * 8335write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id, 8336 const char *next_copy_id, apr_pool_t *pool) 8337{ 8338 char *buf; 8339 const char *tmp_name, *name; 8340 fs_fs_data_t *ffd = fs->fsap_data; 8341 8342 /* Now we can just write out this line. */ 8343 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8344 buf = apr_psprintf(pool, "%ld\n", rev); 8345 else 8346 buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id); 8347 8348 name = svn_fs_fs__path_current(fs, pool); 8349 SVN_ERR(svn_io_write_unique(&tmp_name, 8350 svn_dirent_dirname(name, pool), 8351 buf, strlen(buf), 8352 svn_io_file_del_none, pool)); 8353 8354 return move_into_place(tmp_name, name, name, pool); 8355} 8356 8357/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 8358 youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on 8359 NEW_REV's revision root. 8360 8361 Intended to be called as the very last step in a commit before 'current' 8362 is bumped. This implies that we are holding the write lock. */ 8363static svn_error_t * 8364verify_as_revision_before_current_plus_plus(svn_fs_t *fs, 8365 svn_revnum_t new_rev, 8366 apr_pool_t *pool) 8367{ 8368#ifdef SVN_DEBUG 8369 fs_fs_data_t *ffd = fs->fsap_data; 8370 svn_fs_t *ft; /* fs++ == ft */ 8371 svn_fs_root_t *root; 8372 fs_fs_data_t *ft_ffd; 8373 apr_hash_t *fs_config; 8374 8375 SVN_ERR_ASSERT(ffd->svn_fs_open_); 8376 8377 /* make sure FT does not simply return data cached by other instances 8378 * but actually retrieves it from disk at least once. 8379 */ 8380 fs_config = apr_hash_make(pool); 8381 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 8382 svn_uuid_generate(pool)); 8383 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 8384 fs_config, 8385 pool)); 8386 ft_ffd = ft->fsap_data; 8387 /* Don't let FT consult rep-cache.db, either. */ 8388 ft_ffd->rep_sharing_allowed = FALSE; 8389 8390 /* Time travel! */ 8391 ft_ffd->youngest_rev_cache = new_rev; 8392 8393 SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool)); 8394 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 8395 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 8396 SVN_ERR(svn_fs_fs__verify_root(root, pool)); 8397#endif /* SVN_DEBUG */ 8398 8399 return SVN_NO_ERROR; 8400} 8401 8402/* Update the 'current' file to hold the correct next node and copy_ids 8403 from transaction TXN_ID in filesystem FS. The current revision is 8404 set to REV. Perform temporary allocations in POOL. */ 8405static svn_error_t * 8406write_final_current(svn_fs_t *fs, 8407 const char *txn_id, 8408 svn_revnum_t rev, 8409 const char *start_node_id, 8410 const char *start_copy_id, 8411 apr_pool_t *pool) 8412{ 8413 const char *txn_node_id, *txn_copy_id; 8414 char new_node_id[MAX_KEY_SIZE + 2]; 8415 char new_copy_id[MAX_KEY_SIZE + 2]; 8416 fs_fs_data_t *ffd = fs->fsap_data; 8417 8418 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8419 return write_current(fs, rev, NULL, NULL, pool); 8420 8421 /* To find the next available ids, we add the id that used to be in 8422 the 'current' file, to the next ids from the transaction file. */ 8423 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool)); 8424 8425 svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id); 8426 svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id); 8427 8428 return write_current(fs, rev, new_node_id, new_copy_id, pool); 8429} 8430 8431/* Verify that the user registed with FS has all the locks necessary to 8432 permit all the changes associate with TXN_NAME. 8433 The FS write lock is assumed to be held by the caller. */ 8434static svn_error_t * 8435verify_locks(svn_fs_t *fs, 8436 const char *txn_name, 8437 apr_pool_t *pool) 8438{ 8439 apr_pool_t *subpool = svn_pool_create(pool); 8440 apr_hash_t *changes; 8441 apr_hash_index_t *hi; 8442 apr_array_header_t *changed_paths; 8443 svn_stringbuf_t *last_recursed = NULL; 8444 int i; 8445 8446 /* Fetch the changes for this transaction. */ 8447 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool)); 8448 8449 /* Make an array of the changed paths, and sort them depth-first-ily. */ 8450 changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1, 8451 sizeof(const char *)); 8452 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 8453 APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi); 8454 qsort(changed_paths->elts, changed_paths->nelts, 8455 changed_paths->elt_size, svn_sort_compare_paths); 8456 8457 /* Now, traverse the array of changed paths, verify locks. Note 8458 that if we need to do a recursive verification a path, we'll skip 8459 over children of that path when we get to them. */ 8460 for (i = 0; i < changed_paths->nelts; i++) 8461 { 8462 const char *path; 8463 svn_fs_path_change2_t *change; 8464 svn_boolean_t recurse = TRUE; 8465 8466 svn_pool_clear(subpool); 8467 path = APR_ARRAY_IDX(changed_paths, i, const char *); 8468 8469 /* If this path has already been verified as part of a recursive 8470 check of one of its parents, no need to do it again. */ 8471 if (last_recursed 8472 && svn_dirent_is_child(last_recursed->data, path, subpool)) 8473 continue; 8474 8475 /* Fetch the change associated with our path. */ 8476 change = svn_hash_gets(changes, path); 8477 8478 /* What does it mean to succeed at lock verification for a given 8479 path? For an existing file or directory getting modified 8480 (text, props), it means we hold the lock on the file or 8481 directory. For paths being added or removed, we need to hold 8482 the locks for that path and any children of that path. 8483 8484 WHEW! We have no reliable way to determine the node kind 8485 of deleted items, but fortunately we are going to do a 8486 recursive check on deleted paths regardless of their kind. */ 8487 if (change->change_kind == svn_fs_path_change_modify) 8488 recurse = FALSE; 8489 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE, 8490 subpool)); 8491 8492 /* If we just did a recursive check, remember the path we 8493 checked (so children can be skipped). */ 8494 if (recurse) 8495 { 8496 if (! last_recursed) 8497 last_recursed = svn_stringbuf_create(path, pool); 8498 else 8499 svn_stringbuf_set(last_recursed, path); 8500 } 8501 } 8502 svn_pool_destroy(subpool); 8503 return SVN_NO_ERROR; 8504} 8505 8506/* Baton used for commit_body below. */ 8507struct commit_baton { 8508 svn_revnum_t *new_rev_p; 8509 svn_fs_t *fs; 8510 svn_fs_txn_t *txn; 8511 apr_array_header_t *reps_to_cache; 8512 apr_hash_t *reps_hash; 8513 apr_pool_t *reps_pool; 8514}; 8515 8516/* The work-horse for svn_fs_fs__commit, called with the FS write lock. 8517 This implements the svn_fs_fs__with_write_lock() 'body' callback 8518 type. BATON is a 'struct commit_baton *'. */ 8519static svn_error_t * 8520commit_body(void *baton, apr_pool_t *pool) 8521{ 8522 struct commit_baton *cb = baton; 8523 fs_fs_data_t *ffd = cb->fs->fsap_data; 8524 const char *old_rev_filename, *rev_filename, *proto_filename; 8525 const char *revprop_filename, *final_revprop; 8526 const svn_fs_id_t *root_id, *new_root_id; 8527 const char *start_node_id = NULL, *start_copy_id = NULL; 8528 svn_revnum_t old_rev, new_rev; 8529 apr_file_t *proto_file; 8530 void *proto_file_lockcookie; 8531 apr_off_t initial_offset, changed_path_offset; 8532 char *buf; 8533 apr_hash_t *txnprops; 8534 apr_array_header_t *txnprop_list; 8535 svn_prop_t prop; 8536 svn_string_t date; 8537 8538 /* Get the current youngest revision. */ 8539 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool)); 8540 8541 /* Check to make sure this transaction is based off the most recent 8542 revision. */ 8543 if (cb->txn->base_rev != old_rev) 8544 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 8545 _("Transaction out of date")); 8546 8547 /* Locks may have been added (or stolen) between the calling of 8548 previous svn_fs.h functions and svn_fs_commit_txn(), so we need 8549 to re-examine every changed-path in the txn and re-verify all 8550 discovered locks. */ 8551 SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool)); 8552 8553 /* Get the next node_id and copy_id to use. */ 8554 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8555 SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs, 8556 pool)); 8557 8558 /* We are going to be one better than this puny old revision. */ 8559 new_rev = old_rev + 1; 8560 8561 /* Get a write handle on the proto revision file. */ 8562 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 8563 cb->fs, cb->txn->id, pool)); 8564 SVN_ERR(get_file_offset(&initial_offset, proto_file, pool)); 8565 8566 /* Write out all the node-revisions and directory contents. */ 8567 root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool); 8568 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id, 8569 start_node_id, start_copy_id, initial_offset, 8570 cb->reps_to_cache, cb->reps_hash, cb->reps_pool, 8571 TRUE, pool)); 8572 8573 /* Write the changed-path information. */ 8574 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 8575 cb->fs, cb->txn->id, pool)); 8576 8577 /* Write the final line. */ 8578 buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n", 8579 svn_fs_fs__id_offset(new_root_id), 8580 changed_path_offset); 8581 SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL, 8582 pool)); 8583 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool)); 8584 SVN_ERR(svn_io_file_close(proto_file, pool)); 8585 8586 /* We don't unlock the prototype revision file immediately to avoid a 8587 race with another caller writing to the prototype revision file 8588 before we commit it. */ 8589 8590 /* Remove any temporary txn props representing 'flags'. */ 8591 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool)); 8592 txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t)); 8593 prop.value = NULL; 8594 8595 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 8596 { 8597 prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 8598 APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8599 } 8600 8601 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 8602 { 8603 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 8604 APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8605 } 8606 8607 if (! apr_is_empty_array(txnprop_list)) 8608 SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool)); 8609 8610 /* Create the shard for the rev and revprop file, if we're sharding and 8611 this is the first revision of a new shard. We don't care if this 8612 fails because the shard already existed for some reason. */ 8613 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0) 8614 { 8615 /* Create the revs shard. */ 8616 { 8617 const char *new_dir = path_rev_shard(cb->fs, new_rev, pool); 8618 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8619 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8620 return svn_error_trace(err); 8621 svn_error_clear(err); 8622 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8623 PATH_REVS_DIR, 8624 pool), 8625 new_dir, pool)); 8626 } 8627 8628 /* Create the revprops shard. */ 8629 SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8630 { 8631 const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool); 8632 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8633 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8634 return svn_error_trace(err); 8635 svn_error_clear(err); 8636 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8637 PATH_REVPROPS_DIR, 8638 pool), 8639 new_dir, pool)); 8640 } 8641 } 8642 8643 /* Move the finished rev file into place. */ 8644 SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename, 8645 cb->fs, old_rev, pool)); 8646 rev_filename = path_rev(cb->fs, new_rev, pool); 8647 proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool); 8648 SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename, 8649 pool)); 8650 8651 /* Now that we've moved the prototype revision file out of the way, 8652 we can unlock it (since further attempts to write to the file 8653 will fail as it no longer exists). We must do this so that we can 8654 remove the transaction directory later. */ 8655 SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool)); 8656 8657 /* Update commit time to ensure that svn:date revprops remain ordered. */ 8658 date.data = svn_time_to_cstring(apr_time_now(), pool); 8659 date.len = strlen(date.data); 8660 8661 SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE, 8662 &date, pool)); 8663 8664 /* Move the revprops file into place. */ 8665 SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8666 revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool); 8667 final_revprop = path_revprops(cb->fs, new_rev, pool); 8668 SVN_ERR(move_into_place(revprop_filename, final_revprop, 8669 old_rev_filename, pool)); 8670 8671 /* Update the 'current' file. */ 8672 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool)); 8673 SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id, 8674 start_copy_id, pool)); 8675 8676 /* At this point the new revision is committed and globally visible 8677 so let the caller know it succeeded by giving it the new revision 8678 number, which fulfills svn_fs_commit_txn() contract. Any errors 8679 after this point do not change the fact that a new revision was 8680 created. */ 8681 *cb->new_rev_p = new_rev; 8682 8683 ffd->youngest_rev_cache = new_rev; 8684 8685 /* Remove this transaction directory. */ 8686 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool)); 8687 8688 return SVN_NO_ERROR; 8689} 8690 8691/* Add the representations in REPS_TO_CACHE (an array of representation_t *) 8692 * to the rep-cache database of FS. */ 8693static svn_error_t * 8694write_reps_to_cache(svn_fs_t *fs, 8695 const apr_array_header_t *reps_to_cache, 8696 apr_pool_t *scratch_pool) 8697{ 8698 int i; 8699 8700 for (i = 0; i < reps_to_cache->nelts; i++) 8701 { 8702 representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *); 8703 8704 /* FALSE because we don't care if another parallel commit happened to 8705 * collide with us. (Non-parallel collisions will not be detected.) */ 8706 SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool)); 8707 } 8708 8709 return SVN_NO_ERROR; 8710} 8711 8712svn_error_t * 8713svn_fs_fs__commit(svn_revnum_t *new_rev_p, 8714 svn_fs_t *fs, 8715 svn_fs_txn_t *txn, 8716 apr_pool_t *pool) 8717{ 8718 struct commit_baton cb; 8719 fs_fs_data_t *ffd = fs->fsap_data; 8720 8721 cb.new_rev_p = new_rev_p; 8722 cb.fs = fs; 8723 cb.txn = txn; 8724 8725 if (ffd->rep_sharing_allowed) 8726 { 8727 cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *)); 8728 cb.reps_hash = apr_hash_make(pool); 8729 cb.reps_pool = pool; 8730 } 8731 else 8732 { 8733 cb.reps_to_cache = NULL; 8734 cb.reps_hash = NULL; 8735 cb.reps_pool = NULL; 8736 } 8737 8738 SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool)); 8739 8740 /* At this point, *NEW_REV_P has been set, so errors below won't affect 8741 the success of the commit. (See svn_fs_commit_txn().) */ 8742 8743 if (ffd->rep_sharing_allowed) 8744 { 8745 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 8746 8747 /* Write new entries to the rep-sharing database. 8748 * 8749 * We use an sqlite transaction to speed things up; 8750 * see <http://www.sqlite.org/faq.html#q19>. 8751 */ 8752 SVN_SQLITE__WITH_TXN( 8753 write_reps_to_cache(fs, cb.reps_to_cache, pool), 8754 ffd->rep_cache_db); 8755 } 8756 8757 return SVN_NO_ERROR; 8758} 8759 8760 8761svn_error_t * 8762svn_fs_fs__reserve_copy_id(const char **copy_id_p, 8763 svn_fs_t *fs, 8764 const char *txn_id, 8765 apr_pool_t *pool) 8766{ 8767 const char *cur_node_id, *cur_copy_id; 8768 char *copy_id; 8769 apr_size_t len; 8770 8771 /* First read in the current next-ids file. */ 8772 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 8773 8774 copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2); 8775 8776 len = strlen(cur_copy_id); 8777 svn_fs_fs__next_key(cur_copy_id, &len, copy_id); 8778 8779 SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool)); 8780 8781 *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL); 8782 8783 return SVN_NO_ERROR; 8784} 8785 8786/* Write out the zeroth revision for filesystem FS. */ 8787static svn_error_t * 8788write_revision_zero(svn_fs_t *fs) 8789{ 8790 const char *path_revision_zero = path_rev(fs, 0, fs->pool); 8791 apr_hash_t *proplist; 8792 svn_string_t date; 8793 8794 /* Write out a rev file for revision 0. */ 8795 SVN_ERR(svn_io_file_create(path_revision_zero, 8796 "PLAIN\nEND\nENDREP\n" 8797 "id: 0.0.r0/17\n" 8798 "type: dir\n" 8799 "count: 0\n" 8800 "text: 0 0 4 4 " 8801 "2d2977d1c96f487abe4a1e202dd03b4e\n" 8802 "cpath: /\n" 8803 "\n\n17 107\n", fs->pool)); 8804 SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool)); 8805 8806 /* Set a date on revision 0. */ 8807 date.data = svn_time_to_cstring(apr_time_now(), fs->pool); 8808 date.len = strlen(date.data); 8809 proplist = apr_hash_make(fs->pool); 8810 svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date); 8811 return set_revision_proplist(fs, 0, proplist, fs->pool); 8812} 8813 8814svn_error_t * 8815svn_fs_fs__create(svn_fs_t *fs, 8816 const char *path, 8817 apr_pool_t *pool) 8818{ 8819 int format = SVN_FS_FS__FORMAT_NUMBER; 8820 fs_fs_data_t *ffd = fs->fsap_data; 8821 8822 fs->path = apr_pstrdup(pool, path); 8823 /* See if compatibility with older versions was explicitly requested. */ 8824 if (fs->config) 8825 { 8826 if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)) 8827 format = 1; 8828 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)) 8829 format = 2; 8830 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE)) 8831 format = 3; 8832 else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE)) 8833 format = 4; 8834 } 8835 ffd->format = format; 8836 8837 /* Override the default linear layout if this is a new-enough format. */ 8838 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 8839 ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR; 8840 8841 /* Create the revision data directories. */ 8842 if (ffd->max_files_per_dir) 8843 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool)); 8844 else 8845 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR, 8846 pool), 8847 pool)); 8848 8849 /* Create the revprops directory. */ 8850 if (ffd->max_files_per_dir) 8851 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool), 8852 pool)); 8853 else 8854 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, 8855 PATH_REVPROPS_DIR, 8856 pool), 8857 pool)); 8858 8859 /* Create the transaction directory. */ 8860 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR, 8861 pool), 8862 pool)); 8863 8864 /* Create the protorevs directory. */ 8865 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 8866 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR, 8867 pool), 8868 pool)); 8869 8870 /* Create the 'current' file. */ 8871 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool), 8872 (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 8873 ? "0\n" : "0 1 1\n"), 8874 pool)); 8875 SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool)); 8876 SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool)); 8877 8878 SVN_ERR(write_revision_zero(fs)); 8879 8880 /* Create the fsfs.conf file if supported. Older server versions would 8881 simply ignore the file but that might result in a different behavior 8882 than with the later releases. Also, hotcopy would ignore, i.e. not 8883 copy, a fsfs.conf with old formats. */ 8884 if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 8885 SVN_ERR(write_config(fs, pool)); 8886 8887 SVN_ERR(read_config(ffd, fs->path, pool)); 8888 8889 /* Create the min unpacked rev file. */ 8890 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 8891 SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool)); 8892 8893 /* Create the txn-current file if the repository supports 8894 the transaction sequence file. */ 8895 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 8896 { 8897 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool), 8898 "0\n", pool)); 8899 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool), 8900 "", pool)); 8901 } 8902 8903 /* This filesystem is ready. Stamp it with a format number. */ 8904 SVN_ERR(write_format(path_format(fs, pool), 8905 ffd->format, ffd->max_files_per_dir, FALSE, pool)); 8906 8907 ffd->youngest_rev_cache = 0; 8908 return SVN_NO_ERROR; 8909} 8910 8911/* Part of the recovery procedure. Return the largest revision *REV in 8912 filesystem FS. Use POOL for temporary allocation. */ 8913static svn_error_t * 8914recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool) 8915{ 8916 /* Discovering the largest revision in the filesystem would be an 8917 expensive operation if we did a readdir() or searched linearly, 8918 so we'll do a form of binary search. left is a revision that we 8919 know exists, right a revision that we know does not exist. */ 8920 apr_pool_t *iterpool; 8921 svn_revnum_t left, right = 1; 8922 8923 iterpool = svn_pool_create(pool); 8924 /* Keep doubling right, until we find a revision that doesn't exist. */ 8925 while (1) 8926 { 8927 svn_error_t *err; 8928 apr_file_t *file; 8929 8930 err = open_pack_or_rev_file(&file, fs, right, iterpool); 8931 svn_pool_clear(iterpool); 8932 8933 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8934 { 8935 svn_error_clear(err); 8936 break; 8937 } 8938 else 8939 SVN_ERR(err); 8940 8941 right <<= 1; 8942 } 8943 8944 left = right >> 1; 8945 8946 /* We know that left exists and right doesn't. Do a normal bsearch to find 8947 the last revision. */ 8948 while (left + 1 < right) 8949 { 8950 svn_revnum_t probe = left + ((right - left) / 2); 8951 svn_error_t *err; 8952 apr_file_t *file; 8953 8954 err = open_pack_or_rev_file(&file, fs, probe, iterpool); 8955 svn_pool_clear(iterpool); 8956 8957 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8958 { 8959 svn_error_clear(err); 8960 right = probe; 8961 } 8962 else 8963 { 8964 SVN_ERR(err); 8965 left = probe; 8966 } 8967 } 8968 8969 svn_pool_destroy(iterpool); 8970 8971 /* left is now the largest revision that exists. */ 8972 *rev = left; 8973 return SVN_NO_ERROR; 8974} 8975 8976/* A baton for reading a fixed amount from an open file. For 8977 recover_find_max_ids() below. */ 8978struct recover_read_from_file_baton 8979{ 8980 apr_file_t *file; 8981 apr_pool_t *pool; 8982 apr_off_t remaining; 8983}; 8984 8985/* A stream read handler used by recover_find_max_ids() below. 8986 Read and return at most BATON->REMAINING bytes from the stream, 8987 returning nothing after that to indicate EOF. */ 8988static svn_error_t * 8989read_handler_recover(void *baton, char *buffer, apr_size_t *len) 8990{ 8991 struct recover_read_from_file_baton *b = baton; 8992 svn_filesize_t bytes_to_read = *len; 8993 8994 if (b->remaining == 0) 8995 { 8996 /* Return a successful read of zero bytes to signal EOF. */ 8997 *len = 0; 8998 return SVN_NO_ERROR; 8999 } 9000 9001 if (bytes_to_read > b->remaining) 9002 bytes_to_read = b->remaining; 9003 b->remaining -= bytes_to_read; 9004 9005 return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read, 9006 len, NULL, b->pool); 9007} 9008 9009/* Part of the recovery procedure. Read the directory noderev at offset 9010 OFFSET of file REV_FILE (the revision file of revision REV of 9011 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id 9012 and copy-id of that node, if greater than the current value stored 9013 in either. Recurse into any child directories that were modified in 9014 this revision. 9015 9016 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE. 9017 9018 Perform temporary allocation in POOL. */ 9019static svn_error_t * 9020recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev, 9021 apr_file_t *rev_file, apr_off_t offset, 9022 char *max_node_id, char *max_copy_id, 9023 apr_pool_t *pool) 9024{ 9025 apr_hash_t *headers; 9026 char *value; 9027 representation_t *data_rep; 9028 struct rep_args *ra; 9029 struct recover_read_from_file_baton baton; 9030 svn_stream_t *stream; 9031 apr_hash_t *entries; 9032 apr_hash_index_t *hi; 9033 apr_pool_t *iterpool; 9034 9035 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9036 SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE, 9037 pool), 9038 pool)); 9039 9040 /* Check that this is a directory. It should be. */ 9041 value = svn_hash_gets(headers, HEADER_TYPE); 9042 if (value == NULL || strcmp(value, KIND_DIR) != 0) 9043 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9044 _("Recovery encountered a non-directory node")); 9045 9046 /* Get the data location. No data location indicates an empty directory. */ 9047 value = svn_hash_gets(headers, HEADER_TEXT); 9048 if (!value) 9049 return SVN_NO_ERROR; 9050 SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool)); 9051 9052 /* If the directory's data representation wasn't changed in this revision, 9053 we've already scanned the directory's contents for noderevs, so we don't 9054 need to again. This will occur if a property is changed on a directory 9055 without changing the directory's contents. */ 9056 if (data_rep->revision != rev) 9057 return SVN_NO_ERROR; 9058 9059 /* We could use get_dir_contents(), but this is much cheaper. It does 9060 rely on directory entries being stored as PLAIN reps, though. */ 9061 offset = data_rep->offset; 9062 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9063 SVN_ERR(read_rep_line(&ra, rev_file, pool)); 9064 if (ra->is_delta) 9065 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9066 _("Recovery encountered a deltified directory " 9067 "representation")); 9068 9069 /* Now create a stream that's allowed to read only as much data as is 9070 stored in the representation. */ 9071 baton.file = rev_file; 9072 baton.pool = pool; 9073 baton.remaining = data_rep->expanded_size; 9074 stream = svn_stream_create(&baton, pool); 9075 svn_stream_set_read(stream, read_handler_recover); 9076 9077 /* Now read the entries from that stream. */ 9078 entries = apr_hash_make(pool); 9079 SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool)); 9080 SVN_ERR(svn_stream_close(stream)); 9081 9082 /* Now check each of the entries in our directory to find new node and 9083 copy ids, and recurse into new subdirectories. */ 9084 iterpool = svn_pool_create(pool); 9085 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 9086 { 9087 char *str_val; 9088 char *str; 9089 svn_node_kind_t kind; 9090 svn_fs_id_t *id; 9091 const char *node_id, *copy_id; 9092 apr_off_t child_dir_offset; 9093 const svn_string_t *path = svn__apr_hash_index_val(hi); 9094 9095 svn_pool_clear(iterpool); 9096 9097 str_val = apr_pstrdup(iterpool, path->data); 9098 9099 str = svn_cstring_tokenize(" ", &str_val); 9100 if (str == NULL) 9101 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9102 _("Directory entry corrupt")); 9103 9104 if (strcmp(str, KIND_FILE) == 0) 9105 kind = svn_node_file; 9106 else if (strcmp(str, KIND_DIR) == 0) 9107 kind = svn_node_dir; 9108 else 9109 { 9110 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9111 _("Directory entry corrupt")); 9112 } 9113 9114 str = svn_cstring_tokenize(" ", &str_val); 9115 if (str == NULL) 9116 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9117 _("Directory entry corrupt")); 9118 9119 id = svn_fs_fs__id_parse(str, strlen(str), iterpool); 9120 9121 if (svn_fs_fs__id_rev(id) != rev) 9122 { 9123 /* If the node wasn't modified in this revision, we've already 9124 checked the node and copy id. */ 9125 continue; 9126 } 9127 9128 node_id = svn_fs_fs__id_node_id(id); 9129 copy_id = svn_fs_fs__id_copy_id(id); 9130 9131 if (svn_fs_fs__key_compare(node_id, max_node_id) > 0) 9132 { 9133 SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE); 9134 apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE); 9135 } 9136 if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0) 9137 { 9138 SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE); 9139 apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE); 9140 } 9141 9142 if (kind == svn_node_file) 9143 continue; 9144 9145 child_dir_offset = svn_fs_fs__id_offset(id); 9146 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset, 9147 max_node_id, max_copy_id, iterpool)); 9148 } 9149 svn_pool_destroy(iterpool); 9150 9151 return SVN_NO_ERROR; 9152} 9153 9154/* Return TRUE, if for REVISION in FS, we can find the revprop pack file. 9155 * Use POOL for temporary allocations. 9156 * Set *MISSING, if the reason is a missing manifest or pack file. 9157 */ 9158static svn_boolean_t 9159packed_revprop_available(svn_boolean_t *missing, 9160 svn_fs_t *fs, 9161 svn_revnum_t revision, 9162 apr_pool_t *pool) 9163{ 9164 fs_fs_data_t *ffd = fs->fsap_data; 9165 svn_stringbuf_t *content = NULL; 9166 9167 /* try to read the manifest file */ 9168 const char *folder = path_revprops_pack_shard(fs, revision, pool); 9169 const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool); 9170 9171 svn_error_t *err = try_stringbuf_from_file(&content, 9172 missing, 9173 manifest_path, 9174 FALSE, 9175 pool); 9176 9177 /* if the manifest cannot be read, consider the pack files inaccessible 9178 * even if the file itself exists. */ 9179 if (err) 9180 { 9181 svn_error_clear(err); 9182 return FALSE; 9183 } 9184 9185 if (*missing) 9186 return FALSE; 9187 9188 /* parse manifest content until we find the entry for REVISION. 9189 * Revision 0 is never packed. */ 9190 revision = revision < ffd->max_files_per_dir 9191 ? revision - 1 9192 : revision % ffd->max_files_per_dir; 9193 while (content->data) 9194 { 9195 char *next = strchr(content->data, '\n'); 9196 if (next) 9197 { 9198 *next = 0; 9199 ++next; 9200 } 9201 9202 if (revision-- == 0) 9203 { 9204 /* the respective pack file must exist (and be a file) */ 9205 svn_node_kind_t kind; 9206 err = svn_io_check_path(svn_dirent_join(folder, content->data, 9207 pool), 9208 &kind, pool); 9209 if (err) 9210 { 9211 svn_error_clear(err); 9212 return FALSE; 9213 } 9214 9215 *missing = kind == svn_node_none; 9216 return kind == svn_node_file; 9217 } 9218 9219 content->data = next; 9220 } 9221 9222 return FALSE; 9223} 9224 9225/* Baton used for recover_body below. */ 9226struct recover_baton { 9227 svn_fs_t *fs; 9228 svn_cancel_func_t cancel_func; 9229 void *cancel_baton; 9230}; 9231 9232/* The work-horse for svn_fs_fs__recover, called with the FS 9233 write lock. This implements the svn_fs_fs__with_write_lock() 9234 'body' callback type. BATON is a 'struct recover_baton *'. */ 9235static svn_error_t * 9236recover_body(void *baton, apr_pool_t *pool) 9237{ 9238 struct recover_baton *b = baton; 9239 svn_fs_t *fs = b->fs; 9240 fs_fs_data_t *ffd = fs->fsap_data; 9241 svn_revnum_t max_rev; 9242 char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE]; 9243 char *next_node_id = NULL, *next_copy_id = NULL; 9244 svn_revnum_t youngest_rev; 9245 svn_node_kind_t youngest_revprops_kind; 9246 9247 /* Lose potentially corrupted data in temp files */ 9248 SVN_ERR(cleanup_revprop_namespace(fs)); 9249 9250 /* We need to know the largest revision in the filesystem. */ 9251 SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool)); 9252 9253 /* Get the expected youngest revision */ 9254 SVN_ERR(get_youngest(&youngest_rev, fs->path, pool)); 9255 9256 /* Policy note: 9257 9258 Since the revprops file is written after the revs file, the true 9259 maximum available revision is the youngest one for which both are 9260 present. That's probably the same as the max_rev we just found, 9261 but if it's not, we could, in theory, repeatedly decrement 9262 max_rev until we find a revision that has both a revs and 9263 revprops file, then write db/current with that. 9264 9265 But we choose not to. If a repository is so corrupt that it's 9266 missing at least one revprops file, we shouldn't assume that the 9267 youngest revision for which both the revs and revprops files are 9268 present is healthy. In other words, we're willing to recover 9269 from a missing or out-of-date db/current file, because db/current 9270 is truly redundant -- it's basically a cache so we don't have to 9271 find max_rev each time, albeit a cache with unusual semantics, 9272 since it also officially defines when a revision goes live. But 9273 if we're missing more than the cache, it's time to back out and 9274 let the admin reconstruct things by hand: correctness at that 9275 point may depend on external things like checking a commit email 9276 list, looking in particular working copies, etc. 9277 9278 This policy matches well with a typical naive backup scenario. 9279 Say you're rsyncing your FSFS repository nightly to the same 9280 location. Once revs and revprops are written, you've got the 9281 maximum rev; if the backup should bomb before db/current is 9282 written, then db/current could stay arbitrarily out-of-date, but 9283 we can still recover. It's a small window, but we might as well 9284 do what we can. */ 9285 9286 /* Even if db/current were missing, it would be created with 0 by 9287 get_youngest(), so this conditional remains valid. */ 9288 if (youngest_rev > max_rev) 9289 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9290 _("Expected current rev to be <= %ld " 9291 "but found %ld"), max_rev, youngest_rev); 9292 9293 /* We only need to search for maximum IDs for old FS formats which 9294 se global ID counters. */ 9295 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 9296 { 9297 /* Next we need to find the maximum node id and copy id in use across the 9298 filesystem. Unfortunately, the only way we can get this information 9299 is to scan all the noderevs of all the revisions and keep track as 9300 we go along. */ 9301 svn_revnum_t rev; 9302 apr_pool_t *iterpool = svn_pool_create(pool); 9303 char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0"; 9304 apr_size_t len; 9305 9306 for (rev = 0; rev <= max_rev; rev++) 9307 { 9308 apr_file_t *rev_file; 9309 apr_off_t root_offset; 9310 9311 svn_pool_clear(iterpool); 9312 9313 if (b->cancel_func) 9314 SVN_ERR(b->cancel_func(b->cancel_baton)); 9315 9316 SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool)); 9317 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev, 9318 iterpool)); 9319 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset, 9320 max_node_id, max_copy_id, iterpool)); 9321 SVN_ERR(svn_io_file_close(rev_file, iterpool)); 9322 } 9323 svn_pool_destroy(iterpool); 9324 9325 /* Now that we finally have the maximum revision, node-id and copy-id, we 9326 can bump the two ids to get the next of each. */ 9327 len = strlen(max_node_id); 9328 svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf); 9329 next_node_id = next_node_id_buf; 9330 len = strlen(max_copy_id); 9331 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf); 9332 next_copy_id = next_copy_id_buf; 9333 } 9334 9335 /* Before setting current, verify that there is a revprops file 9336 for the youngest revision. (Issue #2992) */ 9337 SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool), 9338 &youngest_revprops_kind, pool)); 9339 if (youngest_revprops_kind == svn_node_none) 9340 { 9341 svn_boolean_t missing = TRUE; 9342 if (!packed_revprop_available(&missing, fs, max_rev, pool)) 9343 { 9344 if (missing) 9345 { 9346 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9347 _("Revision %ld has a revs file but no " 9348 "revprops file"), 9349 max_rev); 9350 } 9351 else 9352 { 9353 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9354 _("Revision %ld has a revs file but the " 9355 "revprops file is inaccessible"), 9356 max_rev); 9357 } 9358 } 9359 } 9360 else if (youngest_revprops_kind != svn_node_file) 9361 { 9362 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9363 _("Revision %ld has a non-file where its " 9364 "revprops file should be"), 9365 max_rev); 9366 } 9367 9368 /* Prune younger-than-(newfound-youngest) revisions from the rep 9369 cache if sharing is enabled taking care not to create the cache 9370 if it does not exist. */ 9371 if (ffd->rep_sharing_allowed) 9372 { 9373 svn_boolean_t rep_cache_exists; 9374 9375 SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool)); 9376 if (rep_cache_exists) 9377 SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool)); 9378 } 9379 9380 /* Now store the discovered youngest revision, and the next IDs if 9381 relevant, in a new 'current' file. */ 9382 return write_current(fs, max_rev, next_node_id, next_copy_id, pool); 9383} 9384 9385/* This implements the fs_library_vtable_t.recover() API. */ 9386svn_error_t * 9387svn_fs_fs__recover(svn_fs_t *fs, 9388 svn_cancel_func_t cancel_func, void *cancel_baton, 9389 apr_pool_t *pool) 9390{ 9391 struct recover_baton b; 9392 9393 /* We have no way to take out an exclusive lock in FSFS, so we're 9394 restricted as to the types of recovery we can do. Luckily, 9395 we just want to recreate the 'current' file, and we can do that just 9396 by blocking other writers. */ 9397 b.fs = fs; 9398 b.cancel_func = cancel_func; 9399 b.cancel_baton = cancel_baton; 9400 return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool); 9401} 9402 9403svn_error_t * 9404svn_fs_fs__set_uuid(svn_fs_t *fs, 9405 const char *uuid, 9406 apr_pool_t *pool) 9407{ 9408 char *my_uuid; 9409 apr_size_t my_uuid_len; 9410 const char *tmp_path; 9411 const char *uuid_path = path_uuid(fs, pool); 9412 9413 if (! uuid) 9414 uuid = svn_uuid_generate(pool); 9415 9416 /* Make sure we have a copy in FS->POOL, and append a newline. */ 9417 my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL); 9418 my_uuid_len = strlen(my_uuid); 9419 9420 SVN_ERR(svn_io_write_unique(&tmp_path, 9421 svn_dirent_dirname(uuid_path, pool), 9422 my_uuid, my_uuid_len, 9423 svn_io_file_del_none, pool)); 9424 9425 /* We use the permissions of the 'current' file, because the 'uuid' 9426 file does not exist during repository creation. */ 9427 SVN_ERR(move_into_place(tmp_path, uuid_path, 9428 svn_fs_fs__path_current(fs, pool), pool)); 9429 9430 /* Remove the newline we added, and stash the UUID. */ 9431 my_uuid[my_uuid_len - 1] = '\0'; 9432 fs->uuid = my_uuid; 9433 9434 return SVN_NO_ERROR; 9435} 9436 9437/** Node origin lazy cache. */ 9438 9439/* If directory PATH does not exist, create it and give it the same 9440 permissions as FS_path.*/ 9441svn_error_t * 9442svn_fs_fs__ensure_dir_exists(const char *path, 9443 const char *fs_path, 9444 apr_pool_t *pool) 9445{ 9446 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool); 9447 if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 9448 { 9449 svn_error_clear(err); 9450 return SVN_NO_ERROR; 9451 } 9452 SVN_ERR(err); 9453 9454 /* We successfully created a new directory. Dup the permissions 9455 from FS->path. */ 9456 return svn_io_copy_perms(fs_path, path, pool); 9457} 9458 9459/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to 9460 'svn_string_t *' node revision IDs. Use POOL for allocations. */ 9461static svn_error_t * 9462get_node_origins_from_file(svn_fs_t *fs, 9463 apr_hash_t **node_origins, 9464 const char *node_origins_file, 9465 apr_pool_t *pool) 9466{ 9467 apr_file_t *fd; 9468 svn_error_t *err; 9469 svn_stream_t *stream; 9470 9471 *node_origins = NULL; 9472 err = svn_io_file_open(&fd, node_origins_file, 9473 APR_READ, APR_OS_DEFAULT, pool); 9474 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 9475 { 9476 svn_error_clear(err); 9477 return SVN_NO_ERROR; 9478 } 9479 SVN_ERR(err); 9480 9481 stream = svn_stream_from_aprfile2(fd, FALSE, pool); 9482 *node_origins = apr_hash_make(pool); 9483 SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool)); 9484 return svn_stream_close(stream); 9485} 9486 9487svn_error_t * 9488svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id, 9489 svn_fs_t *fs, 9490 const char *node_id, 9491 apr_pool_t *pool) 9492{ 9493 apr_hash_t *node_origins; 9494 9495 *origin_id = NULL; 9496 SVN_ERR(get_node_origins_from_file(fs, &node_origins, 9497 path_node_origin(fs, node_id, pool), 9498 pool)); 9499 if (node_origins) 9500 { 9501 svn_string_t *origin_id_str = 9502 svn_hash_gets(node_origins, node_id); 9503 if (origin_id_str) 9504 *origin_id = svn_fs_fs__id_parse(origin_id_str->data, 9505 origin_id_str->len, pool); 9506 } 9507 return SVN_NO_ERROR; 9508} 9509 9510 9511/* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID 9512 pair and adds it to the NODE_ORIGINS_PATH file. */ 9513static svn_error_t * 9514set_node_origins_for_file(svn_fs_t *fs, 9515 const char *node_origins_path, 9516 const char *node_id, 9517 svn_string_t *node_rev_id, 9518 apr_pool_t *pool) 9519{ 9520 const char *path_tmp; 9521 svn_stream_t *stream; 9522 apr_hash_t *origins_hash; 9523 svn_string_t *old_node_rev_id; 9524 9525 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path, 9526 PATH_NODE_ORIGINS_DIR, 9527 pool), 9528 fs->path, pool)); 9529 9530 /* Read the previously existing origins (if any), and merge our 9531 update with it. */ 9532 SVN_ERR(get_node_origins_from_file(fs, &origins_hash, 9533 node_origins_path, pool)); 9534 if (! origins_hash) 9535 origins_hash = apr_hash_make(pool); 9536 9537 old_node_rev_id = svn_hash_gets(origins_hash, node_id); 9538 9539 if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id)) 9540 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9541 _("Node origin for '%s' exists with a different " 9542 "value (%s) than what we were about to store " 9543 "(%s)"), 9544 node_id, old_node_rev_id->data, node_rev_id->data); 9545 9546 svn_hash_sets(origins_hash, node_id, node_rev_id); 9547 9548 /* Sure, there's a race condition here. Two processes could be 9549 trying to add different cache elements to the same file at the 9550 same time, and the entries added by the first one to write will 9551 be lost. But this is just a cache of reconstructible data, so 9552 we'll accept this problem in return for not having to deal with 9553 locking overhead. */ 9554 9555 /* Create a temporary file, write out our hash, and close the file. */ 9556 SVN_ERR(svn_stream_open_unique(&stream, &path_tmp, 9557 svn_dirent_dirname(node_origins_path, pool), 9558 svn_io_file_del_none, pool, pool)); 9559 SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool)); 9560 SVN_ERR(svn_stream_close(stream)); 9561 9562 /* Rename the temp file as the real destination */ 9563 return svn_io_file_rename(path_tmp, node_origins_path, pool); 9564} 9565 9566 9567svn_error_t * 9568svn_fs_fs__set_node_origin(svn_fs_t *fs, 9569 const char *node_id, 9570 const svn_fs_id_t *node_rev_id, 9571 apr_pool_t *pool) 9572{ 9573 svn_error_t *err; 9574 const char *filename = path_node_origin(fs, node_id, pool); 9575 9576 err = set_node_origins_for_file(fs, filename, 9577 node_id, 9578 svn_fs_fs__id_unparse(node_rev_id, pool), 9579 pool); 9580 if (err && APR_STATUS_IS_EACCES(err->apr_err)) 9581 { 9582 /* It's just a cache; stop trying if I can't write. */ 9583 svn_error_clear(err); 9584 err = NULL; 9585 } 9586 return svn_error_trace(err); 9587} 9588 9589 9590svn_error_t * 9591svn_fs_fs__list_transactions(apr_array_header_t **names_p, 9592 svn_fs_t *fs, 9593 apr_pool_t *pool) 9594{ 9595 const char *txn_dir; 9596 apr_hash_t *dirents; 9597 apr_hash_index_t *hi; 9598 apr_array_header_t *names; 9599 apr_size_t ext_len = strlen(PATH_EXT_TXN); 9600 9601 names = apr_array_make(pool, 1, sizeof(const char *)); 9602 9603 /* Get the transactions directory. */ 9604 txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool); 9605 9606 /* Now find a listing of this directory. */ 9607 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 9608 9609 /* Loop through all the entries and return anything that ends with '.txn'. */ 9610 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 9611 { 9612 const char *name = svn__apr_hash_index_key(hi); 9613 apr_ssize_t klen = svn__apr_hash_index_klen(hi); 9614 const char *id; 9615 9616 /* The name must end with ".txn" to be considered a transaction. */ 9617 if ((apr_size_t) klen <= ext_len 9618 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 9619 continue; 9620 9621 /* Truncate the ".txn" extension and store the ID. */ 9622 id = apr_pstrndup(pool, name, strlen(name) - ext_len); 9623 APR_ARRAY_PUSH(names, const char *) = id; 9624 } 9625 9626 *names_p = names; 9627 9628 return SVN_NO_ERROR; 9629} 9630 9631svn_error_t * 9632svn_fs_fs__open_txn(svn_fs_txn_t **txn_p, 9633 svn_fs_t *fs, 9634 const char *name, 9635 apr_pool_t *pool) 9636{ 9637 svn_fs_txn_t *txn; 9638 svn_node_kind_t kind; 9639 transaction_t *local_txn; 9640 9641 /* First check to see if the directory exists. */ 9642 SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool)); 9643 9644 /* Did we find it? */ 9645 if (kind != svn_node_dir) 9646 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 9647 _("No such transaction '%s'"), 9648 name); 9649 9650 txn = apr_pcalloc(pool, sizeof(*txn)); 9651 9652 /* Read in the root node of this transaction. */ 9653 txn->id = apr_pstrdup(pool, name); 9654 txn->fs = fs; 9655 9656 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool)); 9657 9658 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id); 9659 9660 txn->vtable = &txn_vtable; 9661 *txn_p = txn; 9662 9663 return SVN_NO_ERROR; 9664} 9665 9666svn_error_t * 9667svn_fs_fs__txn_proplist(apr_hash_t **table_p, 9668 svn_fs_txn_t *txn, 9669 apr_pool_t *pool) 9670{ 9671 apr_hash_t *proplist = apr_hash_make(pool); 9672 SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool)); 9673 *table_p = proplist; 9674 9675 return SVN_NO_ERROR; 9676} 9677 9678svn_error_t * 9679svn_fs_fs__delete_node_revision(svn_fs_t *fs, 9680 const svn_fs_id_t *id, 9681 apr_pool_t *pool) 9682{ 9683 node_revision_t *noderev; 9684 9685 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 9686 9687 /* Delete any mutable property representation. */ 9688 if (noderev->prop_rep && noderev->prop_rep->txn_id) 9689 SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE, 9690 pool)); 9691 9692 /* Delete any mutable data representation. */ 9693 if (noderev->data_rep && noderev->data_rep->txn_id 9694 && noderev->kind == svn_node_dir) 9695 { 9696 fs_fs_data_t *ffd = fs->fsap_data; 9697 SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE, 9698 pool)); 9699 9700 /* remove the corresponding entry from the cache, if such exists */ 9701 if (ffd->txn_dir_cache) 9702 { 9703 const char *key = svn_fs_fs__id_unparse(id, pool)->data; 9704 SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool)); 9705 } 9706 } 9707 9708 return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool); 9709} 9710 9711 9712 9713/*** Revisions ***/ 9714 9715svn_error_t * 9716svn_fs_fs__revision_prop(svn_string_t **value_p, 9717 svn_fs_t *fs, 9718 svn_revnum_t rev, 9719 const char *propname, 9720 apr_pool_t *pool) 9721{ 9722 apr_hash_t *table; 9723 9724 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9725 SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool)); 9726 9727 *value_p = svn_hash_gets(table, propname); 9728 9729 return SVN_NO_ERROR; 9730} 9731 9732 9733/* Baton used for change_rev_prop_body below. */ 9734struct change_rev_prop_baton { 9735 svn_fs_t *fs; 9736 svn_revnum_t rev; 9737 const char *name; 9738 const svn_string_t *const *old_value_p; 9739 const svn_string_t *value; 9740}; 9741 9742/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS 9743 write lock. This implements the svn_fs_fs__with_write_lock() 9744 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */ 9745static svn_error_t * 9746change_rev_prop_body(void *baton, apr_pool_t *pool) 9747{ 9748 struct change_rev_prop_baton *cb = baton; 9749 apr_hash_t *table; 9750 9751 SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool)); 9752 9753 if (cb->old_value_p) 9754 { 9755 const svn_string_t *wanted_value = *cb->old_value_p; 9756 const svn_string_t *present_value = svn_hash_gets(table, cb->name); 9757 if ((!wanted_value != !present_value) 9758 || (wanted_value && present_value 9759 && !svn_string_compare(wanted_value, present_value))) 9760 { 9761 /* What we expected isn't what we found. */ 9762 return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, 9763 _("revprop '%s' has unexpected value in " 9764 "filesystem"), 9765 cb->name); 9766 } 9767 /* Fall through. */ 9768 } 9769 svn_hash_sets(table, cb->name, cb->value); 9770 9771 return set_revision_proplist(cb->fs, cb->rev, table, pool); 9772} 9773 9774svn_error_t * 9775svn_fs_fs__change_rev_prop(svn_fs_t *fs, 9776 svn_revnum_t rev, 9777 const char *name, 9778 const svn_string_t *const *old_value_p, 9779 const svn_string_t *value, 9780 apr_pool_t *pool) 9781{ 9782 struct change_rev_prop_baton cb; 9783 9784 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9785 9786 cb.fs = fs; 9787 cb.rev = rev; 9788 cb.name = name; 9789 cb.old_value_p = old_value_p; 9790 cb.value = value; 9791 9792 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool); 9793} 9794 9795 9796 9797/*** Transactions ***/ 9798 9799svn_error_t * 9800svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p, 9801 const svn_fs_id_t **base_root_id_p, 9802 svn_fs_t *fs, 9803 const char *txn_name, 9804 apr_pool_t *pool) 9805{ 9806 transaction_t *txn; 9807 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool)); 9808 *root_id_p = txn->root_id; 9809 *base_root_id_p = txn->base_id; 9810 return SVN_NO_ERROR; 9811} 9812 9813 9814/* Generic transaction operations. */ 9815 9816svn_error_t * 9817svn_fs_fs__txn_prop(svn_string_t **value_p, 9818 svn_fs_txn_t *txn, 9819 const char *propname, 9820 apr_pool_t *pool) 9821{ 9822 apr_hash_t *table; 9823 svn_fs_t *fs = txn->fs; 9824 9825 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9826 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool)); 9827 9828 *value_p = svn_hash_gets(table, propname); 9829 9830 return SVN_NO_ERROR; 9831} 9832 9833svn_error_t * 9834svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, 9835 svn_fs_t *fs, 9836 svn_revnum_t rev, 9837 apr_uint32_t flags, 9838 apr_pool_t *pool) 9839{ 9840 svn_string_t date; 9841 svn_prop_t prop; 9842 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t)); 9843 9844 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9845 9846 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool)); 9847 9848 /* Put a datestamp on the newly created txn, so we always know 9849 exactly how old it is. (This will help sysadmins identify 9850 long-abandoned txns that may need to be manually removed.) When 9851 a txn is promoted to a revision, this property will be 9852 automatically overwritten with a revision datestamp. */ 9853 date.data = svn_time_to_cstring(apr_time_now(), pool); 9854 date.len = strlen(date.data); 9855 9856 prop.name = SVN_PROP_REVISION_DATE; 9857 prop.value = &date; 9858 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9859 9860 /* Set temporary txn props that represent the requested 'flags' 9861 behaviors. */ 9862 if (flags & SVN_FS_TXN_CHECK_OOD) 9863 { 9864 prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 9865 prop.value = svn_string_create("true", pool); 9866 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9867 } 9868 9869 if (flags & SVN_FS_TXN_CHECK_LOCKS) 9870 { 9871 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 9872 prop.value = svn_string_create("true", pool); 9873 APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9874 } 9875 9876 return svn_fs_fs__change_txn_props(*txn_p, props, pool); 9877} 9878 9879 9880/****** Packing FSFS shards *********/ 9881 9882/* Write a file FILENAME in directory FS_PATH, containing a single line 9883 * with the number REVNUM in ASCII decimal. Move the file into place 9884 * atomically, overwriting any existing file. 9885 * 9886 * Similar to write_current(). */ 9887static svn_error_t * 9888write_revnum_file(const char *fs_path, 9889 const char *filename, 9890 svn_revnum_t revnum, 9891 apr_pool_t *scratch_pool) 9892{ 9893 const char *final_path, *tmp_path; 9894 svn_stream_t *tmp_stream; 9895 9896 final_path = svn_dirent_join(fs_path, filename, scratch_pool); 9897 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path, 9898 svn_io_file_del_none, 9899 scratch_pool, scratch_pool)); 9900 SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum)); 9901 SVN_ERR(svn_stream_close(tmp_stream)); 9902 SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool)); 9903 return SVN_NO_ERROR; 9904} 9905 9906/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions 9907 * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations. 9908 * CANCEL_FUNC and CANCEL_BATON are what you think they are. 9909 * 9910 * If for some reason we detect a partial packing already performed, we 9911 * remove the pack file and start again. 9912 */ 9913static svn_error_t * 9914pack_rev_shard(const char *pack_file_dir, 9915 const char *shard_path, 9916 apr_int64_t shard, 9917 int max_files_per_dir, 9918 svn_cancel_func_t cancel_func, 9919 void *cancel_baton, 9920 apr_pool_t *pool) 9921{ 9922 const char *pack_file_path, *manifest_file_path; 9923 svn_stream_t *pack_stream, *manifest_stream; 9924 svn_revnum_t start_rev, end_rev, rev; 9925 apr_off_t next_offset; 9926 apr_pool_t *iterpool; 9927 9928 /* Some useful paths. */ 9929 pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool); 9930 manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool); 9931 9932 /* Remove any existing pack file for this shard, since it is incomplete. */ 9933 SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 9934 pool)); 9935 9936 /* Create the new directory and pack and manifest files. */ 9937 SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool)); 9938 SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool, 9939 pool)); 9940 SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 9941 pool, pool)); 9942 9943 start_rev = (svn_revnum_t) (shard * max_files_per_dir); 9944 end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 9945 next_offset = 0; 9946 iterpool = svn_pool_create(pool); 9947 9948 /* Iterate over the revisions in this shard, squashing them together. */ 9949 for (rev = start_rev; rev <= end_rev; rev++) 9950 { 9951 svn_stream_t *rev_stream; 9952 apr_finfo_t finfo; 9953 const char *path; 9954 9955 svn_pool_clear(iterpool); 9956 9957 /* Get the size of the file. */ 9958 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 9959 iterpool); 9960 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 9961 9962 /* Update the manifest. */ 9963 SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT 9964 "\n", next_offset)); 9965 next_offset += finfo.size; 9966 9967 /* Copy all the bits from the rev file to the end of the pack file. */ 9968 SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool)); 9969 SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream, 9970 iterpool), 9971 cancel_func, cancel_baton, iterpool)); 9972 } 9973 9974 SVN_ERR(svn_stream_close(manifest_stream)); 9975 SVN_ERR(svn_stream_close(pack_stream)); 9976 SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 9977 SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool)); 9978 SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool)); 9979 9980 svn_pool_destroy(iterpool); 9981 9982 return SVN_NO_ERROR; 9983} 9984 9985/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH 9986 * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR. 9987 * 9988 * The file sizes have already been determined and written to SIZES. 9989 * Please note that this function will be executed while the filesystem 9990 * has been locked and that revprops files will therefore not be modified 9991 * while the pack is in progress. 9992 * 9993 * COMPRESSION_LEVEL defines how well the resulting pack file shall be 9994 * compressed or whether is shall be compressed at all. TOTAL_SIZE is 9995 * a hint on which initial buffer size we should use to hold the pack file 9996 * content. 9997 * 9998 * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations 9999 * are done in SCRATCH_POOL. 10000 */ 10001static svn_error_t * 10002copy_revprops(const char *pack_file_dir, 10003 const char *pack_filename, 10004 const char *shard_path, 10005 svn_revnum_t start_rev, 10006 svn_revnum_t end_rev, 10007 apr_array_header_t *sizes, 10008 apr_size_t total_size, 10009 int compression_level, 10010 svn_cancel_func_t cancel_func, 10011 void *cancel_baton, 10012 apr_pool_t *scratch_pool) 10013{ 10014 svn_stream_t *pack_stream; 10015 apr_file_t *pack_file; 10016 svn_revnum_t rev; 10017 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10018 svn_stream_t *stream; 10019 10020 /* create empty data buffer and a write stream on top of it */ 10021 svn_stringbuf_t *uncompressed 10022 = svn_stringbuf_create_ensure(total_size, scratch_pool); 10023 svn_stringbuf_t *compressed 10024 = svn_stringbuf_create_empty(scratch_pool); 10025 pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); 10026 10027 /* write the pack file header */ 10028 SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0, 10029 sizes->nelts, iterpool)); 10030 10031 /* Some useful paths. */ 10032 SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir, 10033 pack_filename, 10034 scratch_pool), 10035 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, 10036 scratch_pool)); 10037 10038 /* Iterate over the revisions in this shard, squashing them together. */ 10039 for (rev = start_rev; rev <= end_rev; rev++) 10040 { 10041 const char *path; 10042 10043 svn_pool_clear(iterpool); 10044 10045 /* Construct the file name. */ 10046 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10047 iterpool); 10048 10049 /* Copy all the bits from the non-packed revprop file to the end of 10050 * the pack file. */ 10051 SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool)); 10052 SVN_ERR(svn_stream_copy3(stream, pack_stream, 10053 cancel_func, cancel_baton, iterpool)); 10054 } 10055 10056 /* flush stream buffers to content buffer */ 10057 SVN_ERR(svn_stream_close(pack_stream)); 10058 10059 /* compress the content (or just store it for COMPRESSION_LEVEL 0) */ 10060 SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 10061 compressed, compression_level)); 10062 10063 /* write the pack file content to disk */ 10064 stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool); 10065 SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len)); 10066 SVN_ERR(svn_stream_close(stream)); 10067 10068 svn_pool_destroy(iterpool); 10069 10070 return SVN_NO_ERROR; 10071} 10072 10073/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR 10074 * revprop files in it, create a packed shared at PACK_FILE_DIR. 10075 * 10076 * COMPRESSION_LEVEL defines how well the resulting pack file shall be 10077 * compressed or whether is shall be compressed at all. Individual pack 10078 * file containing more than one revision will be limited to a size of 10079 * MAX_PACK_SIZE bytes before compression. 10080 * 10081 * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10082 * allocations are done in SCRATCH_POOL. 10083 */ 10084static svn_error_t * 10085pack_revprops_shard(const char *pack_file_dir, 10086 const char *shard_path, 10087 apr_int64_t shard, 10088 int max_files_per_dir, 10089 apr_off_t max_pack_size, 10090 int compression_level, 10091 svn_cancel_func_t cancel_func, 10092 void *cancel_baton, 10093 apr_pool_t *scratch_pool) 10094{ 10095 const char *manifest_file_path, *pack_filename = NULL; 10096 svn_stream_t *manifest_stream; 10097 svn_revnum_t start_rev, end_rev, rev; 10098 apr_off_t total_size; 10099 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10100 apr_array_header_t *sizes; 10101 10102 /* Some useful paths. */ 10103 manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, 10104 scratch_pool); 10105 10106 /* Remove any existing pack file for this shard, since it is incomplete. */ 10107 SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 10108 scratch_pool)); 10109 10110 /* Create the new directory and manifest file stream. */ 10111 SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool)); 10112 SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 10113 scratch_pool, scratch_pool)); 10114 10115 /* revisions to handle. Special case: revision 0 */ 10116 start_rev = (svn_revnum_t) (shard * max_files_per_dir); 10117 end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 10118 if (start_rev == 0) 10119 ++start_rev; 10120 10121 /* initialize the revprop size info */ 10122 sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t)); 10123 total_size = 2 * SVN_INT64_BUFFER_SIZE; 10124 10125 /* Iterate over the revisions in this shard, determine their size and 10126 * squashing them together into pack files. */ 10127 for (rev = start_rev; rev <= end_rev; rev++) 10128 { 10129 apr_finfo_t finfo; 10130 const char *path; 10131 10132 svn_pool_clear(iterpool); 10133 10134 /* Get the size of the file. */ 10135 path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10136 iterpool); 10137 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 10138 10139 /* if we already have started a pack file and this revprop cannot be 10140 * appended to it, write the previous pack file. */ 10141 if (sizes->nelts != 0 && 10142 total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size) 10143 { 10144 SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10145 start_rev, rev-1, sizes, (apr_size_t)total_size, 10146 compression_level, cancel_func, cancel_baton, 10147 iterpool)); 10148 10149 /* next pack file starts empty again */ 10150 apr_array_clear(sizes); 10151 total_size = 2 * SVN_INT64_BUFFER_SIZE; 10152 start_rev = rev; 10153 } 10154 10155 /* Update the manifest. Allocate a file name for the current pack 10156 * file if it is a new one */ 10157 if (sizes->nelts == 0) 10158 pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev); 10159 10160 SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n", 10161 pack_filename)); 10162 10163 /* add to list of files to put into the current pack file */ 10164 APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size; 10165 total_size += SVN_INT64_BUFFER_SIZE + finfo.size; 10166 } 10167 10168 /* write the last pack file */ 10169 if (sizes->nelts != 0) 10170 SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10171 start_rev, rev-1, sizes, (apr_size_t)total_size, 10172 compression_level, cancel_func, cancel_baton, 10173 iterpool)); 10174 10175 /* flush the manifest file and update permissions */ 10176 SVN_ERR(svn_stream_close(manifest_stream)); 10177 SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 10178 10179 svn_pool_destroy(iterpool); 10180 10181 return SVN_NO_ERROR; 10182} 10183 10184/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly 10185 * MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the 10186 * revprop file for revision 0. 10187 * 10188 * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10189 * allocations are done in SCRATCH_POOL. 10190 */ 10191static svn_error_t * 10192delete_revprops_shard(const char *shard_path, 10193 apr_int64_t shard, 10194 int max_files_per_dir, 10195 svn_cancel_func_t cancel_func, 10196 void *cancel_baton, 10197 apr_pool_t *scratch_pool) 10198{ 10199 if (shard == 0) 10200 { 10201 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10202 int i; 10203 10204 /* delete all files except the one for revision 0 */ 10205 for (i = 1; i < max_files_per_dir; ++i) 10206 { 10207 const char *path = svn_dirent_join(shard_path, 10208 apr_psprintf(iterpool, "%d", i), 10209 iterpool); 10210 if (cancel_func) 10211 SVN_ERR((*cancel_func)(cancel_baton)); 10212 10213 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 10214 svn_pool_clear(iterpool); 10215 } 10216 10217 svn_pool_destroy(iterpool); 10218 } 10219 else 10220 SVN_ERR(svn_io_remove_dir2(shard_path, TRUE, 10221 cancel_func, cancel_baton, scratch_pool)); 10222 10223 return SVN_NO_ERROR; 10224} 10225 10226/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and 10227 * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL 10228 * for allocations. REVPROPS_DIR will be NULL if revprop packing is not 10229 * supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that 10230 * case. 10231 * 10232 * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly 10233 * NOTIFY_FUNC and NOTIFY_BATON. 10234 * 10235 * If for some reason we detect a partial packing already performed, we 10236 * remove the pack file and start again. 10237 */ 10238static svn_error_t * 10239pack_shard(const char *revs_dir, 10240 const char *revsprops_dir, 10241 const char *fs_path, 10242 apr_int64_t shard, 10243 int max_files_per_dir, 10244 apr_off_t max_pack_size, 10245 int compression_level, 10246 svn_fs_pack_notify_t notify_func, 10247 void *notify_baton, 10248 svn_cancel_func_t cancel_func, 10249 void *cancel_baton, 10250 apr_pool_t *pool) 10251{ 10252 const char *rev_shard_path, *rev_pack_file_dir; 10253 const char *revprops_shard_path, *revprops_pack_file_dir; 10254 10255 /* Notify caller we're starting to pack this shard. */ 10256 if (notify_func) 10257 SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start, 10258 pool)); 10259 10260 /* Some useful paths. */ 10261 rev_pack_file_dir = svn_dirent_join(revs_dir, 10262 apr_psprintf(pool, 10263 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10264 shard), 10265 pool); 10266 rev_shard_path = svn_dirent_join(revs_dir, 10267 apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10268 pool); 10269 10270 /* pack the revision content */ 10271 SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path, 10272 shard, max_files_per_dir, 10273 cancel_func, cancel_baton, pool)); 10274 10275 /* if enabled, pack the revprops in an equivalent way */ 10276 if (revsprops_dir) 10277 { 10278 revprops_pack_file_dir = svn_dirent_join(revsprops_dir, 10279 apr_psprintf(pool, 10280 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10281 shard), 10282 pool); 10283 revprops_shard_path = svn_dirent_join(revsprops_dir, 10284 apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10285 pool); 10286 10287 SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path, 10288 shard, max_files_per_dir, 10289 (int)(0.9 * max_pack_size), 10290 compression_level, 10291 cancel_func, cancel_baton, pool)); 10292 } 10293 10294 /* Update the min-unpacked-rev file to reflect our newly packed shard. 10295 * (This doesn't update ffd->min_unpacked_rev. That will be updated by 10296 * update_min_unpacked_rev() when necessary.) */ 10297 SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV, 10298 (svn_revnum_t)((shard + 1) * max_files_per_dir), 10299 pool)); 10300 10301 /* Finally, remove the existing shard directories. */ 10302 SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE, 10303 cancel_func, cancel_baton, pool)); 10304 if (revsprops_dir) 10305 SVN_ERR(delete_revprops_shard(revprops_shard_path, 10306 shard, max_files_per_dir, 10307 cancel_func, cancel_baton, pool)); 10308 10309 /* Notify caller we're starting to pack this shard. */ 10310 if (notify_func) 10311 SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end, 10312 pool)); 10313 10314 return SVN_NO_ERROR; 10315} 10316 10317struct pack_baton 10318{ 10319 svn_fs_t *fs; 10320 svn_fs_pack_notify_t notify_func; 10321 void *notify_baton; 10322 svn_cancel_func_t cancel_func; 10323 void *cancel_baton; 10324}; 10325 10326 10327/* The work-horse for svn_fs_fs__pack, called with the FS write lock. 10328 This implements the svn_fs_fs__with_write_lock() 'body' callback 10329 type. BATON is a 'struct pack_baton *'. 10330 10331 WARNING: if you add a call to this function, please note: 10332 The code currently assumes that any piece of code running with 10333 the write-lock set can rely on the ffd->min_unpacked_rev and 10334 ffd->min_unpacked_revprop caches to be up-to-date (and, by 10335 extension, on not having to use a retry when calling 10336 svn_fs_fs__path_rev_absolute() and friends). If you add a call 10337 to this function, consider whether you have to call 10338 update_min_unpacked_rev(). 10339 See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith 10340 */ 10341static svn_error_t * 10342pack_body(void *baton, 10343 apr_pool_t *pool) 10344{ 10345 struct pack_baton *pb = baton; 10346 fs_fs_data_t ffd = {0}; 10347 apr_int64_t completed_shards; 10348 apr_int64_t i; 10349 svn_revnum_t youngest; 10350 apr_pool_t *iterpool; 10351 const char *rev_data_path; 10352 const char *revprops_data_path = NULL; 10353 10354 /* read repository settings */ 10355 SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir, 10356 path_format(pb->fs, pool), pool)); 10357 SVN_ERR(check_format(ffd.format)); 10358 SVN_ERR(read_config(&ffd, pb->fs->path, pool)); 10359 10360 /* If the repository isn't a new enough format, we don't support packing. 10361 Return a friendly error to that effect. */ 10362 if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT) 10363 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 10364 _("FSFS format (%d) too old to pack; please upgrade the filesystem."), 10365 ffd.format); 10366 10367 /* If we aren't using sharding, we can't do any packing, so quit. */ 10368 if (!ffd.max_files_per_dir) 10369 return SVN_NO_ERROR; 10370 10371 SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev, 10372 path_min_unpacked_rev(pb->fs, pool), 10373 pool)); 10374 10375 SVN_ERR(get_youngest(&youngest, pb->fs->path, pool)); 10376 completed_shards = (youngest + 1) / ffd.max_files_per_dir; 10377 10378 /* See if we've already completed all possible shards thus far. */ 10379 if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir)) 10380 return SVN_NO_ERROR; 10381 10382 rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool); 10383 if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 10384 revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR, 10385 pool); 10386 10387 iterpool = svn_pool_create(pool); 10388 for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir; 10389 i < completed_shards; 10390 i++) 10391 { 10392 svn_pool_clear(iterpool); 10393 10394 if (pb->cancel_func) 10395 SVN_ERR(pb->cancel_func(pb->cancel_baton)); 10396 10397 SVN_ERR(pack_shard(rev_data_path, revprops_data_path, 10398 pb->fs->path, i, ffd.max_files_per_dir, 10399 ffd.revprop_pack_size, 10400 ffd.compress_packed_revprops 10401 ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 10402 : SVN_DELTA_COMPRESSION_LEVEL_NONE, 10403 pb->notify_func, pb->notify_baton, 10404 pb->cancel_func, pb->cancel_baton, iterpool)); 10405 } 10406 10407 svn_pool_destroy(iterpool); 10408 return SVN_NO_ERROR; 10409} 10410 10411svn_error_t * 10412svn_fs_fs__pack(svn_fs_t *fs, 10413 svn_fs_pack_notify_t notify_func, 10414 void *notify_baton, 10415 svn_cancel_func_t cancel_func, 10416 void *cancel_baton, 10417 apr_pool_t *pool) 10418{ 10419 struct pack_baton pb = { 0 }; 10420 pb.fs = fs; 10421 pb.notify_func = notify_func; 10422 pb.notify_baton = notify_baton; 10423 pb.cancel_func = cancel_func; 10424 pb.cancel_baton = cancel_baton; 10425 return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool); 10426} 10427 10428 10429/** Verifying. **/ 10430 10431/* Baton type expected by verify_walker(). The purpose is to reuse open 10432 * rev / pack file handles between calls. Its contents need to be cleaned 10433 * periodically to limit resource usage. 10434 */ 10435typedef struct verify_walker_baton_t 10436{ 10437 /* number of calls to verify_walker() since the last clean */ 10438 int iteration_count; 10439 10440 /* number of files opened since the last clean */ 10441 int file_count; 10442 10443 /* progress notification callback to invoke periodically (may be NULL) */ 10444 svn_fs_progress_notify_func_t notify_func; 10445 10446 /* baton to use with NOTIFY_FUNC */ 10447 void *notify_baton; 10448 10449 /* remember the last revision for which we called notify_func */ 10450 svn_revnum_t last_notified_revision; 10451 10452 /* current file handle (or NULL) */ 10453 apr_file_t *file_hint; 10454 10455 /* corresponding revision (or SVN_INVALID_REVNUM) */ 10456 svn_revnum_t rev_hint; 10457 10458 /* pool to use for the file handles etc. */ 10459 apr_pool_t *pool; 10460} verify_walker_baton_t; 10461 10462/* Used by svn_fs_fs__verify(). 10463 Implements svn_fs_fs__walk_rep_reference().walker. */ 10464static svn_error_t * 10465verify_walker(representation_t *rep, 10466 void *baton, 10467 svn_fs_t *fs, 10468 apr_pool_t *scratch_pool) 10469{ 10470 struct rep_state *rs; 10471 struct rep_args *rep_args; 10472 10473 if (baton) 10474 { 10475 verify_walker_baton_t *walker_baton = baton; 10476 apr_file_t * previous_file; 10477 10478 /* notify and free resources periodically */ 10479 if ( walker_baton->iteration_count > 1000 10480 || walker_baton->file_count > 16) 10481 { 10482 if ( walker_baton->notify_func 10483 && rep->revision != walker_baton->last_notified_revision) 10484 { 10485 walker_baton->notify_func(rep->revision, 10486 walker_baton->notify_baton, 10487 scratch_pool); 10488 walker_baton->last_notified_revision = rep->revision; 10489 } 10490 10491 svn_pool_clear(walker_baton->pool); 10492 10493 walker_baton->iteration_count = 0; 10494 walker_baton->file_count = 0; 10495 walker_baton->file_hint = NULL; 10496 walker_baton->rev_hint = SVN_INVALID_REVNUM; 10497 } 10498 10499 /* access the repo data */ 10500 previous_file = walker_baton->file_hint; 10501 SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint, 10502 &walker_baton->rev_hint, rep, fs, 10503 walker_baton->pool)); 10504 10505 /* update resource usage counters */ 10506 walker_baton->iteration_count++; 10507 if (previous_file != walker_baton->file_hint) 10508 walker_baton->file_count++; 10509 } 10510 else 10511 { 10512 /* ### Should this be using read_rep_line() directly? */ 10513 SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs, 10514 scratch_pool)); 10515 } 10516 10517 return SVN_NO_ERROR; 10518} 10519 10520svn_error_t * 10521svn_fs_fs__verify(svn_fs_t *fs, 10522 svn_revnum_t start, 10523 svn_revnum_t end, 10524 svn_fs_progress_notify_func_t notify_func, 10525 void *notify_baton, 10526 svn_cancel_func_t cancel_func, 10527 void *cancel_baton, 10528 apr_pool_t *pool) 10529{ 10530 fs_fs_data_t *ffd = fs->fsap_data; 10531 svn_boolean_t exists; 10532 svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */ 10533 10534 if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) 10535 return SVN_NO_ERROR; 10536 10537 /* Input validation. */ 10538 if (! SVN_IS_VALID_REVNUM(start)) 10539 start = 0; 10540 if (! SVN_IS_VALID_REVNUM(end)) 10541 end = youngest; 10542 SVN_ERR(ensure_revision_exists(fs, start, pool)); 10543 SVN_ERR(ensure_revision_exists(fs, end, pool)); 10544 10545 /* rep-cache verification. */ 10546 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 10547 if (exists) 10548 { 10549 /* provide a baton to allow the reuse of open file handles between 10550 iterations (saves 2/3 of OS level file operations). */ 10551 verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); 10552 baton->rev_hint = SVN_INVALID_REVNUM; 10553 baton->pool = svn_pool_create(pool); 10554 baton->last_notified_revision = SVN_INVALID_REVNUM; 10555 baton->notify_func = notify_func; 10556 baton->notify_baton = notify_baton; 10557 10558 /* tell the user that we are now ready to do *something* */ 10559 if (notify_func) 10560 notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool); 10561 10562 /* Do not attempt to walk the rep-cache database if its file does 10563 not exist, since doing so would create it --- which may confuse 10564 the administrator. Don't take any lock. */ 10565 SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end, 10566 verify_walker, baton, 10567 cancel_func, cancel_baton, 10568 pool)); 10569 10570 /* walker resource cleanup */ 10571 svn_pool_destroy(baton->pool); 10572 } 10573 10574 return SVN_NO_ERROR; 10575} 10576 10577 10578/** Hotcopy. **/ 10579 10580/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at 10581 * the destination and do not differ in terms of kind, size, and mtime. */ 10582static svn_error_t * 10583hotcopy_io_dir_file_copy(const char *src_path, 10584 const char *dst_path, 10585 const char *file, 10586 apr_pool_t *scratch_pool) 10587{ 10588 const svn_io_dirent2_t *src_dirent; 10589 const svn_io_dirent2_t *dst_dirent; 10590 const char *src_target; 10591 const char *dst_target; 10592 10593 /* Does the destination already exist? If not, we must copy it. */ 10594 dst_target = svn_dirent_join(dst_path, file, scratch_pool); 10595 SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE, 10596 scratch_pool, scratch_pool)); 10597 if (dst_dirent->kind != svn_node_none) 10598 { 10599 /* If the destination's stat information indicates that the file 10600 * is equal to the source, don't bother copying the file again. */ 10601 src_target = svn_dirent_join(src_path, file, scratch_pool); 10602 SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE, 10603 scratch_pool, scratch_pool)); 10604 if (src_dirent->kind == dst_dirent->kind && 10605 src_dirent->special == dst_dirent->special && 10606 src_dirent->filesize == dst_dirent->filesize && 10607 src_dirent->mtime <= dst_dirent->mtime) 10608 return SVN_NO_ERROR; 10609 } 10610 10611 return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file, 10612 scratch_pool)); 10613} 10614 10615/* Set *NAME_P to the UTF-8 representation of directory entry NAME. 10616 * NAME is in the internal encoding used by APR; PARENT is in 10617 * UTF-8 and in internal (not local) style. 10618 * 10619 * Use PARENT only for generating an error string if the conversion 10620 * fails because NAME could not be represented in UTF-8. In that 10621 * case, return a two-level error in which the outer error's message 10622 * mentions PARENT, but the inner error's message does not mention 10623 * NAME (except possibly in hex) since NAME may not be printable. 10624 * Such a compound error at least allows the user to go looking in the 10625 * right directory for the problem. 10626 * 10627 * If there is any other error, just return that error directly. 10628 * 10629 * If there is any error, the effect on *NAME_P is undefined. 10630 * 10631 * *NAME_P and NAME may refer to the same storage. 10632 */ 10633static svn_error_t * 10634entry_name_to_utf8(const char **name_p, 10635 const char *name, 10636 const char *parent, 10637 apr_pool_t *pool) 10638{ 10639 svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); 10640 if (err && err->apr_err == APR_EINVAL) 10641 { 10642 return svn_error_createf(err->apr_err, err, 10643 _("Error converting entry " 10644 "in directory '%s' to UTF-8"), 10645 svn_dirent_local_style(parent, pool)); 10646 } 10647 return err; 10648} 10649 10650/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that 10651 * exist in the destination and do not differ from the source in terms of 10652 * kind, size, and mtime. */ 10653static svn_error_t * 10654hotcopy_io_copy_dir_recursively(const char *src, 10655 const char *dst_parent, 10656 const char *dst_basename, 10657 svn_boolean_t copy_perms, 10658 svn_cancel_func_t cancel_func, 10659 void *cancel_baton, 10660 apr_pool_t *pool) 10661{ 10662 svn_node_kind_t kind; 10663 apr_status_t status; 10664 const char *dst_path; 10665 apr_dir_t *this_dir; 10666 apr_finfo_t this_entry; 10667 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 10668 10669 /* Make a subpool for recursion */ 10670 apr_pool_t *subpool = svn_pool_create(pool); 10671 10672 /* The 'dst_path' is simply dst_parent/dst_basename */ 10673 dst_path = svn_dirent_join(dst_parent, dst_basename, pool); 10674 10675 /* Sanity checks: SRC and DST_PARENT are directories, and 10676 DST_BASENAME doesn't already exist in DST_PARENT. */ 10677 SVN_ERR(svn_io_check_path(src, &kind, subpool)); 10678 if (kind != svn_node_dir) 10679 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10680 _("Source '%s' is not a directory"), 10681 svn_dirent_local_style(src, pool)); 10682 10683 SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); 10684 if (kind != svn_node_dir) 10685 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10686 _("Destination '%s' is not a directory"), 10687 svn_dirent_local_style(dst_parent, pool)); 10688 10689 SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); 10690 10691 /* Create the new directory. */ 10692 /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ 10693 SVN_ERR(svn_io_make_dir_recursively(dst_path, pool)); 10694 10695 /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ 10696 SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); 10697 10698 for (status = apr_dir_read(&this_entry, flags, this_dir); 10699 status == APR_SUCCESS; 10700 status = apr_dir_read(&this_entry, flags, this_dir)) 10701 { 10702 if ((this_entry.name[0] == '.') 10703 && ((this_entry.name[1] == '\0') 10704 || ((this_entry.name[1] == '.') 10705 && (this_entry.name[2] == '\0')))) 10706 { 10707 continue; 10708 } 10709 else 10710 { 10711 const char *entryname_utf8; 10712 10713 if (cancel_func) 10714 SVN_ERR(cancel_func(cancel_baton)); 10715 10716 SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, 10717 src, subpool)); 10718 if (this_entry.filetype == APR_REG) /* regular file */ 10719 { 10720 SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8, 10721 subpool)); 10722 } 10723 else if (this_entry.filetype == APR_LNK) /* symlink */ 10724 { 10725 const char *src_target = svn_dirent_join(src, entryname_utf8, 10726 subpool); 10727 const char *dst_target = svn_dirent_join(dst_path, 10728 entryname_utf8, 10729 subpool); 10730 SVN_ERR(svn_io_copy_link(src_target, dst_target, 10731 subpool)); 10732 } 10733 else if (this_entry.filetype == APR_DIR) /* recurse */ 10734 { 10735 const char *src_target; 10736 10737 /* Prevent infinite recursion by filtering off our 10738 newly created destination path. */ 10739 if (strcmp(src, dst_parent) == 0 10740 && strcmp(entryname_utf8, dst_basename) == 0) 10741 continue; 10742 10743 src_target = svn_dirent_join(src, entryname_utf8, subpool); 10744 SVN_ERR(hotcopy_io_copy_dir_recursively(src_target, 10745 dst_path, 10746 entryname_utf8, 10747 copy_perms, 10748 cancel_func, 10749 cancel_baton, 10750 subpool)); 10751 } 10752 /* ### support other APR node types someday?? */ 10753 10754 } 10755 } 10756 10757 if (! (APR_STATUS_IS_ENOENT(status))) 10758 return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 10759 svn_dirent_local_style(src, pool)); 10760 10761 status = apr_dir_close(this_dir); 10762 if (status) 10763 return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 10764 svn_dirent_local_style(src, pool)); 10765 10766 /* Free any memory used by recursion */ 10767 svn_pool_destroy(subpool); 10768 10769 return SVN_NO_ERROR; 10770} 10771 10772/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR 10773 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR. 10774 * Use SCRATCH_POOL for temporary allocations. */ 10775static svn_error_t * 10776hotcopy_copy_shard_file(const char *src_subdir, 10777 const char *dst_subdir, 10778 svn_revnum_t rev, 10779 int max_files_per_dir, 10780 apr_pool_t *scratch_pool) 10781{ 10782 const char *src_subdir_shard = src_subdir, 10783 *dst_subdir_shard = dst_subdir; 10784 10785 if (max_files_per_dir) 10786 { 10787 const char *shard = apr_psprintf(scratch_pool, "%ld", 10788 rev / max_files_per_dir); 10789 src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool); 10790 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10791 10792 if (rev % max_files_per_dir == 0) 10793 { 10794 SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool)); 10795 SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard, 10796 scratch_pool)); 10797 } 10798 } 10799 10800 SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard, 10801 apr_psprintf(scratch_pool, "%ld", rev), 10802 scratch_pool)); 10803 return SVN_NO_ERROR; 10804} 10805 10806 10807/* Copy a packed shard containing revision REV, and which contains 10808 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS. 10809 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS. 10810 * Do not re-copy data which already exists in DST_FS. 10811 * Use SCRATCH_POOL for temporary allocations. */ 10812static svn_error_t * 10813hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev, 10814 svn_fs_t *src_fs, 10815 svn_fs_t *dst_fs, 10816 svn_revnum_t rev, 10817 int max_files_per_dir, 10818 apr_pool_t *scratch_pool) 10819{ 10820 const char *src_subdir; 10821 const char *dst_subdir; 10822 const char *packed_shard; 10823 const char *src_subdir_packed_shard; 10824 svn_revnum_t revprop_rev; 10825 apr_pool_t *iterpool; 10826 fs_fs_data_t *src_ffd = src_fs->fsap_data; 10827 10828 /* Copy the packed shard. */ 10829 src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool); 10830 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool); 10831 packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10832 rev / max_files_per_dir); 10833 src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10834 scratch_pool); 10835 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10836 dst_subdir, packed_shard, 10837 TRUE /* copy_perms */, 10838 NULL /* cancel_func */, NULL, 10839 scratch_pool)); 10840 10841 /* Copy revprops belonging to revisions in this pack. */ 10842 src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10843 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10844 10845 if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 10846 || src_ffd->min_unpacked_rev < rev + max_files_per_dir) 10847 { 10848 /* copy unpacked revprops rev by rev */ 10849 iterpool = svn_pool_create(scratch_pool); 10850 for (revprop_rev = rev; 10851 revprop_rev < rev + max_files_per_dir; 10852 revprop_rev++) 10853 { 10854 svn_pool_clear(iterpool); 10855 10856 SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10857 revprop_rev, max_files_per_dir, 10858 iterpool)); 10859 } 10860 svn_pool_destroy(iterpool); 10861 } 10862 else 10863 { 10864 /* revprop for revision 0 will never be packed */ 10865 if (rev == 0) 10866 SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10867 0, max_files_per_dir, 10868 scratch_pool)); 10869 10870 /* packed revprops folder */ 10871 packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10872 rev / max_files_per_dir); 10873 src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10874 scratch_pool); 10875 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10876 dst_subdir, packed_shard, 10877 TRUE /* copy_perms */, 10878 NULL /* cancel_func */, NULL, 10879 scratch_pool)); 10880 } 10881 10882 /* If necessary, update the min-unpacked rev file in the hotcopy. */ 10883 if (*dst_min_unpacked_rev < rev + max_files_per_dir) 10884 { 10885 *dst_min_unpacked_rev = rev + max_files_per_dir; 10886 SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV, 10887 *dst_min_unpacked_rev, 10888 scratch_pool)); 10889 } 10890 10891 return SVN_NO_ERROR; 10892} 10893 10894/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current' 10895 * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST. 10896 * Use SCRATCH_POOL for temporary allocations. */ 10897static svn_error_t * 10898hotcopy_update_current(svn_revnum_t *dst_youngest, 10899 svn_fs_t *dst_fs, 10900 svn_revnum_t new_youngest, 10901 apr_pool_t *scratch_pool) 10902{ 10903 char next_node_id[MAX_KEY_SIZE] = "0"; 10904 char next_copy_id[MAX_KEY_SIZE] = "0"; 10905 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 10906 10907 if (*dst_youngest >= new_youngest) 10908 return SVN_NO_ERROR; 10909 10910 /* If necessary, get new current next_node and next_copy IDs. */ 10911 if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 10912 { 10913 apr_off_t root_offset; 10914 apr_file_t *rev_file; 10915 10916 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 10917 SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool)); 10918 10919 SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest, 10920 scratch_pool)); 10921 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, 10922 dst_fs, new_youngest, scratch_pool)); 10923 SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file, 10924 root_offset, next_node_id, next_copy_id, 10925 scratch_pool)); 10926 SVN_ERR(svn_io_file_close(rev_file, scratch_pool)); 10927 } 10928 10929 /* Update 'current'. */ 10930 SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id, 10931 scratch_pool)); 10932 10933 *dst_youngest = new_youngest; 10934 10935 return SVN_NO_ERROR; 10936} 10937 10938 10939/* Remove revision or revprop files between START_REV (inclusive) and 10940 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume 10941 * sharding as per MAX_FILES_PER_DIR. 10942 * Use SCRATCH_POOL for temporary allocations. */ 10943static svn_error_t * 10944hotcopy_remove_files(svn_fs_t *dst_fs, 10945 const char *dst_subdir, 10946 svn_revnum_t start_rev, 10947 svn_revnum_t end_rev, 10948 int max_files_per_dir, 10949 apr_pool_t *scratch_pool) 10950{ 10951 const char *shard; 10952 const char *dst_subdir_shard; 10953 svn_revnum_t rev; 10954 apr_pool_t *iterpool; 10955 10956 /* Pre-compute paths for initial shard. */ 10957 shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir); 10958 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10959 10960 iterpool = svn_pool_create(scratch_pool); 10961 for (rev = start_rev; rev < end_rev; rev++) 10962 { 10963 const char *path; 10964 svn_pool_clear(iterpool); 10965 10966 /* If necessary, update paths for shard. */ 10967 if (rev != start_rev && rev % max_files_per_dir == 0) 10968 { 10969 shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir); 10970 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10971 } 10972 10973 /* remove files for REV */ 10974 path = svn_dirent_join(dst_subdir_shard, 10975 apr_psprintf(iterpool, "%ld", rev), 10976 iterpool); 10977 10978 /* Make the rev file writable and remove it. */ 10979 SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool)); 10980 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 10981 } 10982 10983 svn_pool_destroy(iterpool); 10984 10985 return SVN_NO_ERROR; 10986} 10987 10988/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive) 10989 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 10990 * Use SCRATCH_POOL for temporary allocations. */ 10991static svn_error_t * 10992hotcopy_remove_rev_files(svn_fs_t *dst_fs, 10993 svn_revnum_t start_rev, 10994 svn_revnum_t end_rev, 10995 int max_files_per_dir, 10996 apr_pool_t *scratch_pool) 10997{ 10998 SVN_ERR_ASSERT(start_rev <= end_rev); 10999 SVN_ERR(hotcopy_remove_files(dst_fs, 11000 svn_dirent_join(dst_fs->path, 11001 PATH_REVS_DIR, 11002 scratch_pool), 11003 start_rev, end_rev, 11004 max_files_per_dir, scratch_pool)); 11005 11006 return SVN_NO_ERROR; 11007} 11008 11009/* Remove revision properties between START_REV (inclusive) and END_REV 11010 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 11011 * Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will 11012 * not be deleted. */ 11013static svn_error_t * 11014hotcopy_remove_revprop_files(svn_fs_t *dst_fs, 11015 svn_revnum_t start_rev, 11016 svn_revnum_t end_rev, 11017 int max_files_per_dir, 11018 apr_pool_t *scratch_pool) 11019{ 11020 SVN_ERR_ASSERT(start_rev <= end_rev); 11021 11022 /* don't delete rev 0 props */ 11023 SVN_ERR(hotcopy_remove_files(dst_fs, 11024 svn_dirent_join(dst_fs->path, 11025 PATH_REVPROPS_DIR, 11026 scratch_pool), 11027 start_rev ? start_rev : 1, end_rev, 11028 max_files_per_dir, scratch_pool)); 11029 11030 return SVN_NO_ERROR; 11031} 11032 11033/* Verify that DST_FS is a suitable destination for an incremental 11034 * hotcopy from SRC_FS. */ 11035static svn_error_t * 11036hotcopy_incremental_check_preconditions(svn_fs_t *src_fs, 11037 svn_fs_t *dst_fs, 11038 apr_pool_t *pool) 11039{ 11040 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11041 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11042 11043 /* We only support incremental hotcopy between the same format. */ 11044 if (src_ffd->format != dst_ffd->format) 11045 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11046 _("The FSFS format (%d) of the hotcopy source does not match the " 11047 "FSFS format (%d) of the hotcopy destination; please upgrade " 11048 "both repositories to the same format"), 11049 src_ffd->format, dst_ffd->format); 11050 11051 /* Make sure the UUID of source and destination match up. 11052 * We don't want to copy over a different repository. */ 11053 if (strcmp(src_fs->uuid, dst_fs->uuid) != 0) 11054 return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL, 11055 _("The UUID of the hotcopy source does " 11056 "not match the UUID of the hotcopy " 11057 "destination")); 11058 11059 /* Also require same shard size. */ 11060 if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir) 11061 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11062 _("The sharding layout configuration " 11063 "of the hotcopy source does not match " 11064 "the sharding layout configuration of " 11065 "the hotcopy destination")); 11066 return SVN_NO_ERROR; 11067} 11068 11069/* Remove folder PATH. Ignore errors due to the sub-tree not being empty. 11070 * CANCEL_FUNC and CANCEL_BATON do the usual thing. 11071 * Use POOL for temporary allocations. 11072 */ 11073static svn_error_t * 11074remove_folder(const char *path, 11075 svn_cancel_func_t cancel_func, 11076 void *cancel_baton, 11077 apr_pool_t *pool) 11078{ 11079 svn_error_t *err = svn_io_remove_dir2(path, TRUE, 11080 cancel_func, cancel_baton, pool); 11081 11082 if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 11083 { 11084 svn_error_clear(err); 11085 err = SVN_NO_ERROR; 11086 } 11087 11088 return svn_error_trace(err); 11089} 11090 11091/* Baton for hotcopy_body(). */ 11092struct hotcopy_body_baton { 11093 svn_fs_t *src_fs; 11094 svn_fs_t *dst_fs; 11095 svn_boolean_t incremental; 11096 svn_cancel_func_t cancel_func; 11097 void *cancel_baton; 11098} hotcopy_body_baton; 11099 11100/* Perform a hotcopy, either normal or incremental. 11101 * 11102 * Normal hotcopy assumes that the destination exists as an empty 11103 * directory. It behaves like an incremental hotcopy except that 11104 * none of the copied files already exist in the destination. 11105 * 11106 * An incremental hotcopy copies only changed or new files to the destination, 11107 * and removes files from the destination no longer present in the source. 11108 * While the incremental hotcopy is running, readers should still be able 11109 * to access the destintation repository without error and should not see 11110 * revisions currently in progress of being copied. Readers are able to see 11111 * new fully copied revisions even if the entire incremental hotcopy procedure 11112 * has not yet completed. 11113 * 11114 * Writers are blocked out completely during the entire incremental hotcopy 11115 * process to ensure consistency. This function assumes that the repository 11116 * write-lock is held. 11117 */ 11118static svn_error_t * 11119hotcopy_body(void *baton, apr_pool_t *pool) 11120{ 11121 struct hotcopy_body_baton *hbb = baton; 11122 svn_fs_t *src_fs = hbb->src_fs; 11123 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11124 svn_fs_t *dst_fs = hbb->dst_fs; 11125 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11126 int max_files_per_dir = src_ffd->max_files_per_dir; 11127 svn_boolean_t incremental = hbb->incremental; 11128 svn_cancel_func_t cancel_func = hbb->cancel_func; 11129 void* cancel_baton = hbb->cancel_baton; 11130 svn_revnum_t src_youngest; 11131 svn_revnum_t dst_youngest; 11132 svn_revnum_t rev; 11133 svn_revnum_t src_min_unpacked_rev; 11134 svn_revnum_t dst_min_unpacked_rev; 11135 const char *src_subdir; 11136 const char *dst_subdir; 11137 const char *revprop_src_subdir; 11138 const char *revprop_dst_subdir; 11139 apr_pool_t *iterpool; 11140 svn_node_kind_t kind; 11141 11142 /* Try to copy the config. 11143 * 11144 * ### We try copying the config file before doing anything else, 11145 * ### because higher layers will abort the hotcopy if we throw 11146 * ### an error from this function, and that renders the hotcopy 11147 * ### unusable anyway. */ 11148 if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 11149 { 11150 svn_error_t *err; 11151 11152 err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG, 11153 pool); 11154 if (err) 11155 { 11156 if (APR_STATUS_IS_ENOENT(err->apr_err)) 11157 { 11158 /* 1.6.0 to 1.6.11 did not copy the configuration file during 11159 * hotcopy. So if we're hotcopying a repository which has been 11160 * created as a hotcopy itself, it's possible that fsfs.conf 11161 * does not exist. Ask the user to re-create it. 11162 * 11163 * ### It would be nice to make this a non-fatal error, 11164 * ### but this function does not get an svn_fs_t object 11165 * ### so we have no way of just printing a warning via 11166 * ### the fs->warning() callback. */ 11167 11168 const char *msg; 11169 const char *src_abspath; 11170 const char *dst_abspath; 11171 const char *config_relpath; 11172 svn_error_t *err2; 11173 11174 config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool); 11175 err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool); 11176 if (err2) 11177 return svn_error_trace(svn_error_compose_create(err, err2)); 11178 err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool); 11179 if (err2) 11180 return svn_error_trace(svn_error_compose_create(err, err2)); 11181 11182 /* ### hack: strip off the 'db/' directory from paths so 11183 * ### they make sense to the user */ 11184 src_abspath = svn_dirent_dirname(src_abspath, pool); 11185 dst_abspath = svn_dirent_dirname(dst_abspath, pool); 11186 11187 msg = apr_psprintf(pool, 11188 _("Failed to create hotcopy at '%s'. " 11189 "The file '%s' is missing from the source " 11190 "repository. Please create this file, for " 11191 "instance by running 'svnadmin upgrade %s'"), 11192 dst_abspath, config_relpath, src_abspath); 11193 return svn_error_quick_wrap(err, msg); 11194 } 11195 else 11196 return svn_error_trace(err); 11197 } 11198 } 11199 11200 if (cancel_func) 11201 SVN_ERR(cancel_func(cancel_baton)); 11202 11203 /* Find the youngest revision in the source and destination. 11204 * We only support hotcopies from sources with an equal or greater amount 11205 * of revisions than the destination. 11206 * This also catches the case where users accidentally swap the 11207 * source and destination arguments. */ 11208 SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool)); 11209 if (incremental) 11210 { 11211 SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool)); 11212 if (src_youngest < dst_youngest) 11213 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11214 _("The hotcopy destination already contains more revisions " 11215 "(%lu) than the hotcopy source contains (%lu); are source " 11216 "and destination swapped?"), 11217 dst_youngest, src_youngest); 11218 } 11219 else 11220 dst_youngest = 0; 11221 11222 if (cancel_func) 11223 SVN_ERR(cancel_func(cancel_baton)); 11224 11225 /* Copy the min unpacked rev, and read its value. */ 11226 if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11227 { 11228 const char *min_unpacked_rev_path; 11229 11230 min_unpacked_rev_path = svn_dirent_join(src_fs->path, 11231 PATH_MIN_UNPACKED_REV, 11232 pool); 11233 SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev, 11234 min_unpacked_rev_path, 11235 pool)); 11236 11237 min_unpacked_rev_path = svn_dirent_join(dst_fs->path, 11238 PATH_MIN_UNPACKED_REV, 11239 pool); 11240 SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev, 11241 min_unpacked_rev_path, 11242 pool)); 11243 11244 /* We only support packs coming from the hotcopy source. 11245 * The destination should not be packed independently from 11246 * the source. This also catches the case where users accidentally 11247 * swap the source and destination arguments. */ 11248 if (src_min_unpacked_rev < dst_min_unpacked_rev) 11249 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11250 _("The hotcopy destination already contains " 11251 "more packed revisions (%lu) than the " 11252 "hotcopy source contains (%lu)"), 11253 dst_min_unpacked_rev - 1, 11254 src_min_unpacked_rev - 1); 11255 11256 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11257 PATH_MIN_UNPACKED_REV, pool)); 11258 } 11259 else 11260 { 11261 src_min_unpacked_rev = 0; 11262 dst_min_unpacked_rev = 0; 11263 } 11264 11265 if (cancel_func) 11266 SVN_ERR(cancel_func(cancel_baton)); 11267 11268 /* 11269 * Copy the necessary rev files. 11270 */ 11271 11272 src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool); 11273 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool); 11274 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool)); 11275 11276 iterpool = svn_pool_create(pool); 11277 /* First, copy packed shards. */ 11278 for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir) 11279 { 11280 svn_pool_clear(iterpool); 11281 11282 if (cancel_func) 11283 SVN_ERR(cancel_func(cancel_baton)); 11284 11285 /* Copy the packed shard. */ 11286 SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11287 src_fs, dst_fs, 11288 rev, max_files_per_dir, 11289 iterpool)); 11290 11291 /* If necessary, update 'current' to the most recent packed rev, 11292 * so readers can see new revisions which arrived in this pack. */ 11293 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, 11294 rev + max_files_per_dir - 1, 11295 iterpool)); 11296 11297 /* Remove revision files which are now packed. */ 11298 if (incremental) 11299 { 11300 SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev, 11301 rev + max_files_per_dir, 11302 max_files_per_dir, iterpool)); 11303 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11304 SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev, 11305 rev + max_files_per_dir, 11306 max_files_per_dir, 11307 iterpool)); 11308 } 11309 11310 /* Now that all revisions have moved into the pack, the original 11311 * rev dir can be removed. */ 11312 SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool), 11313 cancel_func, cancel_baton, iterpool)); 11314 if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11315 SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool), 11316 cancel_func, cancel_baton, iterpool)); 11317 } 11318 11319 if (cancel_func) 11320 SVN_ERR(cancel_func(cancel_baton)); 11321 11322 /* Now, copy pairs of non-packed revisions and revprop files. 11323 * If necessary, update 'current' after copying all files from a shard. */ 11324 SVN_ERR_ASSERT(rev == src_min_unpacked_rev); 11325 SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev); 11326 revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool); 11327 revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool); 11328 SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool)); 11329 for (; rev <= src_youngest; rev++) 11330 { 11331 svn_error_t *err; 11332 11333 svn_pool_clear(iterpool); 11334 11335 if (cancel_func) 11336 SVN_ERR(cancel_func(cancel_baton)); 11337 11338 /* Copy the rev file. */ 11339 err = hotcopy_copy_shard_file(src_subdir, dst_subdir, 11340 rev, max_files_per_dir, 11341 iterpool); 11342 if (err) 11343 { 11344 if (APR_STATUS_IS_ENOENT(err->apr_err) && 11345 src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11346 { 11347 svn_error_clear(err); 11348 11349 /* The source rev file does not exist. This can happen if the 11350 * source repository is being packed concurrently with this 11351 * hotcopy operation. 11352 * 11353 * If the new revision is now packed, and the youngest revision 11354 * we're interested in is not inside this pack, try to copy the 11355 * pack instead. 11356 * 11357 * If the youngest revision ended up being packed, don't try 11358 * to be smart and work around this. Just abort the hotcopy. */ 11359 SVN_ERR(update_min_unpacked_rev(src_fs, pool)); 11360 if (is_packed_rev(src_fs, rev)) 11361 { 11362 if (is_packed_rev(src_fs, src_youngest)) 11363 return svn_error_createf( 11364 SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11365 _("The assumed HEAD revision (%lu) of the " 11366 "hotcopy source has been packed while the " 11367 "hotcopy was in progress; please restart " 11368 "the hotcopy operation"), 11369 src_youngest); 11370 11371 SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11372 src_fs, dst_fs, 11373 rev, max_files_per_dir, 11374 iterpool)); 11375 rev = dst_min_unpacked_rev; 11376 continue; 11377 } 11378 else 11379 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11380 _("Revision %lu disappeared from the " 11381 "hotcopy source while hotcopy was " 11382 "in progress"), rev); 11383 } 11384 else 11385 return svn_error_trace(err); 11386 } 11387 11388 /* Copy the revprop file. */ 11389 SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir, 11390 revprop_dst_subdir, 11391 rev, max_files_per_dir, 11392 iterpool)); 11393 11394 /* After completing a full shard, update 'current'. */ 11395 if (max_files_per_dir && rev % max_files_per_dir == 0) 11396 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool)); 11397 } 11398 svn_pool_destroy(iterpool); 11399 11400 if (cancel_func) 11401 SVN_ERR(cancel_func(cancel_baton)); 11402 11403 /* We assume that all revisions were copied now, i.e. we didn't exit the 11404 * above loop early. 'rev' was last incremented during exit of the loop. */ 11405 SVN_ERR_ASSERT(rev == src_youngest + 1); 11406 11407 /* All revisions were copied. Update 'current'. */ 11408 SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool)); 11409 11410 /* Replace the locks tree. 11411 * This is racy in case readers are currently trying to list locks in 11412 * the destination. However, we need to get rid of stale locks. 11413 * This is the simplest way of doing this, so we accept this small race. */ 11414 dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool); 11415 SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton, 11416 pool)); 11417 src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool); 11418 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11419 if (kind == svn_node_dir) 11420 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path, 11421 PATH_LOCKS_DIR, TRUE, 11422 cancel_func, cancel_baton, pool)); 11423 11424 /* Now copy the node-origins cache tree. */ 11425 src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool); 11426 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11427 if (kind == svn_node_dir) 11428 SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path, 11429 PATH_NODE_ORIGINS_DIR, TRUE, 11430 cancel_func, cancel_baton, pool)); 11431 11432 /* 11433 * NB: Data copied below is only read by writers, not readers. 11434 * Writers are still locked out at this point. 11435 */ 11436 11437 if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 11438 { 11439 /* Copy the rep cache and then remove entries for revisions 11440 * younger than the destination's youngest revision. */ 11441 src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool); 11442 dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool); 11443 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11444 if (kind == svn_node_file) 11445 { 11446 SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool)); 11447 SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool)); 11448 } 11449 } 11450 11451 /* Copy the txn-current file. */ 11452 if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11453 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11454 PATH_TXN_CURRENT, pool)); 11455 11456 /* If a revprop generation file exists in the source filesystem, 11457 * reset it to zero (since this is on a different path, it will not 11458 * overlap with data already in cache). Also, clean up stale files 11459 * used for the named atomics implementation. */ 11460 SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool), 11461 &kind, pool)); 11462 if (kind == svn_node_file) 11463 SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool)); 11464 11465 SVN_ERR(cleanup_revprop_namespace(dst_fs)); 11466 11467 /* Hotcopied FS is complete. Stamp it with a format file. */ 11468 SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool), 11469 dst_ffd->format, max_files_per_dir, TRUE, pool)); 11470 11471 return SVN_NO_ERROR; 11472} 11473 11474 11475/* Set up shared data between SRC_FS and DST_FS. */ 11476static void 11477hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs) 11478{ 11479 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11480 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11481 11482 /* The common pool and mutexes are shared between src and dst filesystems. 11483 * During hotcopy we only grab the mutexes for the destination, so there 11484 * is no risk of dead-lock. We don't write to the src filesystem. Shared 11485 * data for the src_fs has already been initialised in fs_hotcopy(). */ 11486 dst_ffd->shared = src_ffd->shared; 11487} 11488 11489/* Create an empty filesystem at DST_FS at DST_PATH with the same 11490 * configuration as SRC_FS (uuid, format, and other parameters). 11491 * After creation DST_FS has no revisions, not even revision zero. */ 11492static svn_error_t * 11493hotcopy_create_empty_dest(svn_fs_t *src_fs, 11494 svn_fs_t *dst_fs, 11495 const char *dst_path, 11496 apr_pool_t *pool) 11497{ 11498 fs_fs_data_t *src_ffd = src_fs->fsap_data; 11499 fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11500 11501 dst_fs->path = apr_pstrdup(pool, dst_path); 11502 11503 dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir; 11504 dst_ffd->config = src_ffd->config; 11505 dst_ffd->format = src_ffd->format; 11506 11507 /* Create the revision data directories. */ 11508 if (dst_ffd->max_files_per_dir) 11509 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool), 11510 pool)); 11511 else 11512 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11513 PATH_REVS_DIR, pool), 11514 pool)); 11515 11516 /* Create the revprops directory. */ 11517 if (src_ffd->max_files_per_dir) 11518 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool), 11519 pool)); 11520 else 11521 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11522 PATH_REVPROPS_DIR, 11523 pool), 11524 pool)); 11525 11526 /* Create the transaction directory. */ 11527 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR, 11528 pool), 11529 pool)); 11530 11531 /* Create the protorevs directory. */ 11532 if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 11533 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11534 PATH_TXN_PROTOS_DIR, 11535 pool), 11536 pool)); 11537 11538 /* Create the 'current' file. */ 11539 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool), 11540 (dst_ffd->format >= 11541 SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 11542 ? "0\n" : "0 1 1\n"), 11543 pool)); 11544 11545 /* Create lock file and UUID. */ 11546 SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool)); 11547 SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool)); 11548 11549 /* Create the min unpacked rev file. */ 11550 if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11551 SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool), 11552 "0\n", pool)); 11553 /* Create the txn-current file if the repository supports 11554 the transaction sequence file. */ 11555 if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11556 { 11557 SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool), 11558 "0\n", pool)); 11559 SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool), 11560 "", pool)); 11561 } 11562 11563 dst_ffd->youngest_rev_cache = 0; 11564 11565 hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11566 SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11567 11568 return SVN_NO_ERROR; 11569} 11570 11571svn_error_t * 11572svn_fs_fs__hotcopy(svn_fs_t *src_fs, 11573 svn_fs_t *dst_fs, 11574 const char *src_path, 11575 const char *dst_path, 11576 svn_boolean_t incremental, 11577 svn_cancel_func_t cancel_func, 11578 void *cancel_baton, 11579 apr_pool_t *pool) 11580{ 11581 struct hotcopy_body_baton hbb; 11582 11583 if (cancel_func) 11584 SVN_ERR(cancel_func(cancel_baton)); 11585 11586 SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool)); 11587 11588 if (incremental) 11589 { 11590 const char *dst_format_abspath; 11591 svn_node_kind_t dst_format_kind; 11592 11593 /* Check destination format to be sure we know how to incrementally 11594 * hotcopy to the destination FS. */ 11595 dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool); 11596 SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool)); 11597 if (dst_format_kind == svn_node_none) 11598 { 11599 /* Destination doesn't exist yet. Perform a normal hotcopy to a 11600 * empty destination using the same configuration as the source. */ 11601 SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11602 } 11603 else 11604 { 11605 /* Check the existing repository. */ 11606 SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool)); 11607 SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs, 11608 pool)); 11609 hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11610 SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11611 } 11612 } 11613 else 11614 { 11615 /* Start out with an empty destination using the same configuration 11616 * as the source. */ 11617 SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11618 } 11619 11620 if (cancel_func) 11621 SVN_ERR(cancel_func(cancel_baton)); 11622 11623 hbb.src_fs = src_fs; 11624 hbb.dst_fs = dst_fs; 11625 hbb.incremental = incremental; 11626 hbb.cancel_func = cancel_func; 11627 hbb.cancel_baton = cancel_baton; 11628 SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool)); 11629 11630 return SVN_NO_ERROR; 11631} 11632