1/* cached_data.c --- cached (read) access to FSFS data 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 "cached_data.h" 24 25#include <assert.h> 26 27#include "svn_hash.h" 28#include "svn_ctype.h" 29#include "svn_sorts.h" 30#include "private/svn_delta_private.h" 31#include "private/svn_io_private.h" 32#include "private/svn_sorts_private.h" 33#include "private/svn_subr_private.h" 34#include "private/svn_temp_serializer.h" 35 36#include "fs_fs.h" 37#include "id.h" 38#include "index.h" 39#include "low_level.h" 40#include "pack.h" 41#include "util.h" 42#include "temp_serializer.h" 43 44#include "../libsvn_fs/fs-loader.h" 45#include "../libsvn_delta/delta.h" /* for SVN_DELTA_WINDOW_SIZE */ 46 47#include "svn_private_config.h" 48 49/* forward-declare. See implementation for the docstring */ 50static svn_error_t * 51block_read(void **result, 52 svn_fs_t *fs, 53 svn_revnum_t revision, 54 apr_uint64_t item_index, 55 svn_fs_fs__revision_file_t *revision_file, 56 apr_pool_t *result_pool, 57 apr_pool_t *scratch_pool); 58 59 60/* Define this to enable access logging via dbg_log_access 61#define SVN_FS_FS__LOG_ACCESS 62 */ 63 64/* When SVN_FS_FS__LOG_ACCESS has been defined, write a line to console 65 * showing where REVISION, ITEM_INDEX is located in FS and use ITEM to 66 * show details on it's contents if not NULL. To support format 6 and 67 * earlier repos, ITEM_TYPE (SVN_FS_FS__ITEM_TYPE_*) must match ITEM. 68 * Use SCRATCH_POOL for temporary allocations. 69 * 70 * For pre-format7 repos, the display will be restricted. 71 */ 72static svn_error_t * 73dbg_log_access(svn_fs_t *fs, 74 svn_revnum_t revision, 75 apr_uint64_t item_index, 76 void *item, 77 apr_uint32_t item_type, 78 apr_pool_t *scratch_pool) 79{ 80 /* no-op if this macro is not defined */ 81#ifdef SVN_FS_FS__LOG_ACCESS 82 fs_fs_data_t *ffd = fs->fsap_data; 83 apr_off_t end_offset = 0; 84 svn_fs_fs__p2l_entry_t *entry = NULL; 85 static const char *types[] = {"<n/a>", "frep ", "drep ", "fprop", "dprop", 86 "node ", "chgs ", "rep "}; 87 const char *description = ""; 88 const char *type = types[item_type]; 89 const char *pack = ""; 90 apr_off_t offset; 91 svn_fs_fs__revision_file_t *rev_file; 92 93 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision, 94 scratch_pool, scratch_pool)); 95 96 /* determine rev / pack file offset */ 97 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, revision, NULL, 98 item_index, scratch_pool)); 99 100 /* constructing the pack file description */ 101 if (revision < ffd->min_unpacked_rev) 102 pack = apr_psprintf(scratch_pool, "%4ld|", 103 revision / ffd->max_files_per_dir); 104 105 /* construct description if possible */ 106 if (item_type == SVN_FS_FS__ITEM_TYPE_NODEREV && item != NULL) 107 { 108 node_revision_t *node = item; 109 const char *data_rep 110 = node->data_rep 111 ? apr_psprintf(scratch_pool, " d=%ld/%" APR_UINT64_T_FMT, 112 node->data_rep->revision, 113 node->data_rep->item_index) 114 : ""; 115 const char *prop_rep 116 = node->prop_rep 117 ? apr_psprintf(scratch_pool, " p=%ld/%" APR_UINT64_T_FMT, 118 node->prop_rep->revision, 119 node->prop_rep->item_index) 120 : ""; 121 description = apr_psprintf(scratch_pool, "%s (pc=%d%s%s)", 122 node->created_path, 123 node->predecessor_count, 124 data_rep, 125 prop_rep); 126 } 127 else if (item_type == SVN_FS_FS__ITEM_TYPE_ANY_REP) 128 { 129 svn_fs_fs__rep_header_t *header = item; 130 if (header == NULL) 131 description = " (txdelta window)"; 132 else if (header->type == svn_fs_fs__rep_plain) 133 description = " PLAIN"; 134 else if (header->type == svn_fs_fs__rep_self_delta) 135 description = " DELTA"; 136 else 137 description = apr_psprintf(scratch_pool, 138 " DELTA against %ld/%" APR_UINT64_T_FMT, 139 header->base_revision, 140 header->base_item_index); 141 } 142 else if (item_type == SVN_FS_FS__ITEM_TYPE_CHANGES && item != NULL) 143 { 144 apr_array_header_t *changes = item; 145 switch (changes->nelts) 146 { 147 case 0: description = " no change"; 148 break; 149 case 1: description = " 1 change"; 150 break; 151 default: description = apr_psprintf(scratch_pool, " %d changes", 152 changes->nelts); 153 } 154 } 155 156 /* some info is only available in format7 repos */ 157 if (svn_fs_fs__use_log_addressing(fs)) 158 { 159 /* reverse index lookup: get item description in ENTRY */ 160 SVN_ERR(svn_fs_fs__p2l_entry_lookup(&entry, fs, rev_file, revision, 161 offset, scratch_pool, 162 scratch_pool)); 163 if (entry) 164 { 165 /* more details */ 166 end_offset = offset + entry->size; 167 type = types[entry->type]; 168 } 169 170 /* line output */ 171 printf("%5s%4lx:%04lx -%4lx:%04lx %s %7ld %5"APR_UINT64_T_FMT" %s\n", 172 pack, (long)(offset / ffd->block_size), 173 (long)(offset % ffd->block_size), 174 (long)(end_offset / ffd->block_size), 175 (long)(end_offset % ffd->block_size), 176 type, revision, item_index, description); 177 } 178 else 179 { 180 /* reduced logging for format 6 and earlier */ 181 printf("%5s%10" APR_UINT64_T_HEX_FMT " %s %7ld %7" APR_UINT64_T_FMT \ 182 " %s\n", 183 pack, (apr_uint64_t)(offset), type, revision, item_index, 184 description); 185 } 186 187 /* We don't know when SCRATCH_POOL will be cleared, so close the rev file 188 explicitly. */ 189 SVN_ERR(svn_fs_fs__close_revision_file(rev_file)); 190 191#endif 192 193 return SVN_NO_ERROR; 194} 195 196/* Convenience wrapper around svn_io_file_aligned_seek, taking filesystem 197 FS instead of a block size. */ 198static svn_error_t * 199aligned_seek(svn_fs_t *fs, 200 apr_file_t *file, 201 apr_off_t *buffer_start, 202 apr_off_t offset, 203 apr_pool_t *pool) 204{ 205 fs_fs_data_t *ffd = fs->fsap_data; 206 return svn_error_trace(svn_io_file_aligned_seek(file, ffd->block_size, 207 buffer_start, offset, 208 pool)); 209} 210 211/* Open the revision file for revision REV in filesystem FS and store 212 the newly opened file in FILE. Seek to location OFFSET before 213 returning. Perform temporary allocations in POOL. */ 214static svn_error_t * 215open_and_seek_revision(svn_fs_fs__revision_file_t **file, 216 svn_fs_t *fs, 217 svn_revnum_t rev, 218 apr_uint64_t item, 219 apr_pool_t *pool) 220{ 221 svn_fs_fs__revision_file_t *rev_file; 222 apr_off_t offset = -1; 223 224 SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool)); 225 226 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool, pool)); 227 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL, item, 228 pool)); 229 230 SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, pool)); 231 232 *file = rev_file; 233 234 return SVN_NO_ERROR; 235} 236 237/* Open the representation REP for a node-revision in filesystem FS, seek 238 to its position and store the newly opened file in FILE. Perform 239 temporary allocations in POOL. */ 240static svn_error_t * 241open_and_seek_transaction(svn_fs_fs__revision_file_t **file, 242 svn_fs_t *fs, 243 representation_t *rep, 244 apr_pool_t *pool) 245{ 246 apr_off_t offset; 247 248 SVN_ERR(svn_fs_fs__open_proto_rev_file(file, fs, &rep->txn_id, pool, pool)); 249 250 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, NULL, SVN_INVALID_REVNUM, 251 &rep->txn_id, rep->item_index, pool)); 252 SVN_ERR(aligned_seek(fs, (*file)->file, NULL, offset, pool)); 253 254 return SVN_NO_ERROR; 255} 256 257/* Given a node-id ID, and a representation REP in filesystem FS, open 258 the correct file and seek to the correction location. Store this 259 file in *FILE_P. Perform any allocations in POOL. */ 260static svn_error_t * 261open_and_seek_representation(svn_fs_fs__revision_file_t **file_p, 262 svn_fs_t *fs, 263 representation_t *rep, 264 apr_pool_t *pool) 265{ 266 if (! svn_fs_fs__id_txn_used(&rep->txn_id)) 267 return open_and_seek_revision(file_p, fs, rep->revision, rep->item_index, 268 pool); 269 else 270 return open_and_seek_transaction(file_p, fs, rep, pool); 271} 272 273 274 275static svn_error_t * 276err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id) 277{ 278 svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool); 279 return svn_error_createf 280 (SVN_ERR_FS_ID_NOT_FOUND, 0, 281 _("Reference to non-existent node '%s' in filesystem '%s'"), 282 id_str->data, fs->path); 283} 284 285/* Return TRUE, if FS is of a format that supports block-read and the 286 feature has been enabled. */ 287static svn_boolean_t 288use_block_read(svn_fs_t *fs) 289{ 290 fs_fs_data_t *ffd = fs->fsap_data; 291 return svn_fs_fs__use_log_addressing(fs) && ffd->use_block_read; 292} 293 294svn_error_t * 295svn_fs_fs__fixup_expanded_size(svn_fs_t *fs, 296 representation_t *rep, 297 apr_pool_t *scratch_pool) 298{ 299 svn_checksum_t checksum; 300 svn_checksum_t *empty_md5; 301 svn_fs_fs__revision_file_t *revision_file; 302 svn_fs_fs__rep_header_t *rep_header; 303 304 /* Anything to do at all? 305 * 306 * Note that a 0 SIZE is only possible for PLAIN reps due to the SVN\1 307 * magic prefix in any DELTA rep. */ 308 if (!rep || rep->expanded_size != 0 || rep->size == 0) 309 return SVN_NO_ERROR; 310 311 /* This function may only be called for committed data. */ 312 assert(!svn_fs_fs__id_txn_used(&rep->txn_id)); 313 314 /* EXPANDED_SIZE is 0. If the MD5 does not match the one for empty 315 * contents, we know that EXPANDED_SIZE == 0 is wrong and needs to 316 * be set to the actual value given by SIZE. 317 * 318 * Using svn_checksum_match() will also accept all-zero values for 319 * the MD5 digest and only report a mismatch if the MD5 has actually 320 * been given. */ 321 empty_md5 = svn_checksum_empty_checksum(svn_checksum_md5, scratch_pool); 322 323 checksum.digest = rep->md5_digest; 324 checksum.kind = svn_checksum_md5; 325 if (!svn_checksum_match(empty_md5, &checksum)) 326 { 327 rep->expanded_size = rep->size; 328 return SVN_NO_ERROR; 329 } 330 331 /* Data in the rep-cache.db does not have MD5 checksums (all zero) on it. 332 * Compare SHA1 instead. */ 333 if (rep->has_sha1) 334 { 335 svn_checksum_t *empty_sha1 336 = svn_checksum_empty_checksum(svn_checksum_sha1, scratch_pool); 337 338 checksum.digest = rep->sha1_digest; 339 checksum.kind = svn_checksum_sha1; 340 if (!svn_checksum_match(empty_sha1, &checksum)) 341 { 342 rep->expanded_size = rep->size; 343 return SVN_NO_ERROR; 344 } 345 } 346 347 /* Only two cases are left here. 348 * (1) A non-empty PLAIN rep with a MD5 collision on EMPTY_MD5. 349 * (2) A DELTA rep with zero-length output. */ 350 351 /* SVN always stores a DELTA rep with zero-length output as an empty 352 * sequence of txdelta windows, i.e. as "SVN\1". In that case, SIZE is 353 * 4 bytes. There is no other possible DELTA rep of that size and any 354 * PLAIN rep of 4 bytes would produce a different MD5. Hence, if SIZE is 355 * actually 4 here, we know that this is an empty DELTA rep. 356 * 357 * Note that it is technically legal to have DELTA reps with a 0 length 358 * output window. Their on-disk size would be longer. We handle that 359 * case later together with the equally unlikely MD5 collision. */ 360 if (rep->size == 4) 361 { 362 /* EXPANDED_SIZE is already 0. */ 363 return SVN_NO_ERROR; 364 } 365 366 /* We still have the two options, PLAIN or DELTA rep. At this point, we 367 * are in an extremely unlikely case and can spend some time to figure it 368 * out. So, let's just look at the representation header. */ 369 SVN_ERR(open_and_seek_revision(&revision_file, fs, rep->revision, 370 rep->item_index, scratch_pool)); 371 SVN_ERR(svn_fs_fs__read_rep_header(&rep_header, revision_file->stream, 372 scratch_pool, scratch_pool)); 373 SVN_ERR(svn_fs_fs__close_revision_file(revision_file)); 374 375 /* Only for PLAIN reps do we have to correct EXPANDED_SIZE. */ 376 if (rep_header->type == svn_fs_fs__rep_plain) 377 rep->expanded_size = rep->size; 378 379 return SVN_NO_ERROR; 380} 381 382/* Correct known issues with committed NODEREV in FS. 383 * Uses SCRATCH_POOL for temporaries. 384 */ 385static svn_error_t * 386fixup_node_revision(svn_fs_t *fs, 387 node_revision_t *noderev, 388 apr_pool_t *scratch_pool) 389{ 390 /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 391 noderev->is_fresh_txn_root = FALSE; 392 393 /* Make sure EXPANDED_SIZE has the correct value for every rep. */ 394 SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->data_rep, 395 scratch_pool)); 396 SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->prop_rep, 397 scratch_pool)); 398 399 return SVN_NO_ERROR; 400} 401 402/* Get the node-revision for the node ID in FS. 403 Set *NODEREV_P to the new node-revision structure, allocated in POOL. 404 See svn_fs_fs__get_node_revision, which wraps this and adds another 405 error. */ 406static svn_error_t * 407get_node_revision_body(node_revision_t **noderev_p, 408 svn_fs_t *fs, 409 const svn_fs_id_t *id, 410 apr_pool_t *result_pool, 411 apr_pool_t *scratch_pool) 412{ 413 svn_error_t *err; 414 svn_boolean_t is_cached = FALSE; 415 fs_fs_data_t *ffd = fs->fsap_data; 416 417 if (svn_fs_fs__id_is_txn(id)) 418 { 419 apr_file_t *file; 420 421 /* This is a transaction node-rev. Its storage logic is very 422 different from that of rev / pack files. */ 423 err = svn_io_file_open(&file, 424 svn_fs_fs__path_txn_node_rev(fs, id, 425 scratch_pool), 426 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, 427 scratch_pool); 428 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 429 { 430 svn_error_clear(err); 431 return svn_error_trace(err_dangling_id(fs, id)); 432 } 433 else if (err) 434 { 435 return svn_error_trace(err); 436 } 437 438 SVN_ERR(svn_fs_fs__read_noderev(noderev_p, 439 svn_stream_from_aprfile2(file, 440 FALSE, 441 scratch_pool), 442 result_pool, scratch_pool)); 443 } 444 else 445 { 446 svn_fs_fs__revision_file_t *revision_file; 447 448 /* noderevs in rev / pack files can be cached */ 449 const svn_fs_fs__id_part_t *rev_item = svn_fs_fs__id_rev_item(id); 450 pair_cache_key_t key = { 0 }; 451 key.revision = rev_item->revision; 452 key.second = rev_item->number; 453 454 /* Not found or not applicable. Try a noderev cache lookup. 455 * If that succeeds, we are done here. */ 456 if (ffd->node_revision_cache) 457 { 458 SVN_ERR(svn_cache__get((void **) noderev_p, 459 &is_cached, 460 ffd->node_revision_cache, 461 &key, 462 result_pool)); 463 if (is_cached) 464 return SVN_NO_ERROR; 465 } 466 467 /* read the data from disk */ 468 SVN_ERR(open_and_seek_revision(&revision_file, fs, 469 rev_item->revision, 470 rev_item->number, 471 scratch_pool)); 472 473 if (use_block_read(fs)) 474 { 475 /* block-read will parse the whole block and will also return 476 the one noderev that we need right now. */ 477 SVN_ERR(block_read((void **)noderev_p, fs, 478 rev_item->revision, 479 rev_item->number, 480 revision_file, 481 result_pool, 482 scratch_pool)); 483 } 484 else 485 { 486 /* physical addressing mode reading, parsing and caching */ 487 SVN_ERR(svn_fs_fs__read_noderev(noderev_p, 488 revision_file->stream, 489 result_pool, 490 scratch_pool)); 491 SVN_ERR(fixup_node_revision(fs, *noderev_p, scratch_pool)); 492 493 /* The noderev is not in cache, yet. Add it, if caching has been enabled. */ 494 if (ffd->node_revision_cache) 495 SVN_ERR(svn_cache__set(ffd->node_revision_cache, 496 &key, 497 *noderev_p, 498 scratch_pool)); 499 } 500 501 SVN_ERR(svn_fs_fs__close_revision_file(revision_file)); 502 } 503 504 return SVN_NO_ERROR; 505} 506 507svn_error_t * 508svn_fs_fs__get_node_revision(node_revision_t **noderev_p, 509 svn_fs_t *fs, 510 const svn_fs_id_t *id, 511 apr_pool_t *result_pool, 512 apr_pool_t *scratch_pool) 513{ 514 const svn_fs_fs__id_part_t *rev_item = svn_fs_fs__id_rev_item(id); 515 516 svn_error_t *err = get_node_revision_body(noderev_p, fs, id, 517 result_pool, scratch_pool); 518 if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 519 { 520 svn_string_t *id_string = svn_fs_fs__id_unparse(id, scratch_pool); 521 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 522 "Corrupt node-revision '%s'", 523 id_string->data); 524 } 525 526 SVN_ERR(dbg_log_access(fs, 527 rev_item->revision, 528 rev_item->number, 529 *noderev_p, 530 SVN_FS_FS__ITEM_TYPE_NODEREV, 531 scratch_pool)); 532 533 return svn_error_trace(err); 534} 535 536 537/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID 538 of the header located at OFFSET and store it in *ID_P. Allocate 539 temporary variables from POOL. */ 540static svn_error_t * 541get_fs_id_at_offset(svn_fs_id_t **id_p, 542 svn_fs_fs__revision_file_t *rev_file, 543 svn_fs_t *fs, 544 svn_revnum_t rev, 545 apr_off_t offset, 546 apr_pool_t *pool) 547{ 548 node_revision_t *noderev; 549 550 SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, pool)); 551 SVN_ERR(svn_fs_fs__read_noderev(&noderev, 552 rev_file->stream, 553 pool, pool)); 554 555 /* noderev->id is const, get rid of that */ 556 *id_p = svn_fs_fs__id_copy(noderev->id, pool); 557 558 /* assert that the txn_id is REV 559 * (asserting on offset would be harder because we the rev_offset is not 560 * known here) */ 561 assert(svn_fs_fs__id_rev(*id_p) == rev); 562 563 return SVN_NO_ERROR; 564} 565 566 567/* Given an open revision file REV_FILE in FS for REV, locate the trailer that 568 specifies the offset to the root node-id and to the changed path 569 information. Store the root node offset in *ROOT_OFFSET and the 570 changed path offset in *CHANGES_OFFSET. If either of these 571 pointers is NULL, do nothing with it. 572 573 Allocate temporary variables from POOL. */ 574static svn_error_t * 575get_root_changes_offset(apr_off_t *root_offset, 576 apr_off_t *changes_offset, 577 svn_fs_fs__revision_file_t *rev_file, 578 svn_fs_t *fs, 579 svn_revnum_t rev, 580 apr_pool_t *pool) 581{ 582 fs_fs_data_t *ffd = fs->fsap_data; 583 apr_off_t rev_offset; 584 apr_seek_where_t seek_relative; 585 svn_stringbuf_t *trailer; 586 char buffer[64]; 587 apr_off_t start; 588 apr_off_t end; 589 apr_size_t len; 590 591 /* Determine where to seek to in the file. 592 593 If we've got a pack file, we want to seek to the end of the desired 594 revision. But we don't track that, so we seek to the beginning of the 595 next revision. 596 597 Unless the next revision is in a different file, in which case, we can 598 just seek to the end of the pack file -- just like we do in the 599 non-packed case. */ 600 if (rev_file->is_packed && ((rev + 1) % ffd->max_files_per_dir != 0)) 601 { 602 SVN_ERR(svn_fs_fs__get_packed_offset(&end, fs, rev + 1, pool)); 603 seek_relative = APR_SET; 604 } 605 else 606 { 607 seek_relative = APR_END; 608 end = 0; 609 } 610 611 /* Offset of the revision from the start of the pack file, if applicable. */ 612 if (rev_file->is_packed) 613 SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, rev, pool)); 614 else 615 rev_offset = 0; 616 617 /* We will assume that the last line containing the two offsets 618 will never be longer than 64 characters. */ 619 SVN_ERR(svn_io_file_seek(rev_file->file, seek_relative, &end, pool)); 620 621 if (end < sizeof(buffer)) 622 { 623 len = (apr_size_t)end; 624 start = 0; 625 } 626 else 627 { 628 len = sizeof(buffer); 629 start = end - sizeof(buffer); 630 } 631 632 /* Read in this last block, from which we will identify the last line. */ 633 SVN_ERR(aligned_seek(fs, rev_file->file, NULL, start, pool)); 634 SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, len, NULL, NULL, 635 pool)); 636 637 /* Parse the last line. */ 638 trailer = svn_stringbuf_ncreate(buffer, len, pool); 639 SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset, 640 changes_offset, 641 trailer, 642 rev)); 643 644 /* return absolute offsets */ 645 if (root_offset) 646 *root_offset += rev_offset; 647 if (changes_offset) 648 *changes_offset += rev_offset; 649 650 return SVN_NO_ERROR; 651} 652 653svn_error_t * 654svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p, 655 svn_fs_t *fs, 656 svn_revnum_t rev, 657 apr_pool_t *result_pool, 658 apr_pool_t *scratch_pool) 659{ 660 fs_fs_data_t *ffd = fs->fsap_data; 661 SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool)); 662 663 if (svn_fs_fs__use_log_addressing(fs)) 664 { 665 *root_id_p = svn_fs_fs__id_create_root(rev, result_pool); 666 } 667 else 668 { 669 svn_fs_fs__revision_file_t *revision_file; 670 apr_off_t root_offset; 671 svn_fs_id_t *root_id = NULL; 672 svn_boolean_t is_cached; 673 674 SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached, 675 ffd->rev_root_id_cache, &rev, result_pool)); 676 if (is_cached) 677 return SVN_NO_ERROR; 678 679 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev, 680 scratch_pool, scratch_pool)); 681 SVN_ERR(get_root_changes_offset(&root_offset, NULL, 682 revision_file, fs, rev, 683 scratch_pool)); 684 685 SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev, 686 root_offset, result_pool)); 687 688 SVN_ERR(svn_fs_fs__close_revision_file(revision_file)); 689 690 SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, 691 scratch_pool)); 692 693 *root_id_p = root_id; 694 } 695 696 return SVN_NO_ERROR; 697} 698 699/* Describes a lazily opened rev / pack file. Instances will be shared 700 between multiple instances of rep_state_t. */ 701typedef struct shared_file_t 702{ 703 /* The opened file. NULL while file is not open, yet. */ 704 svn_fs_fs__revision_file_t *rfile; 705 706 /* file system to open the file in */ 707 svn_fs_t *fs; 708 709 /* a revision contained in the FILE. Since this file may be shared, 710 that value may be different from REP_STATE_T->REVISION. */ 711 svn_revnum_t revision; 712 713 /* pool to use when creating the FILE. This guarantees that the file 714 remains open / valid beyond the respective local context that required 715 the file to be opened eventually. */ 716 apr_pool_t *pool; 717} shared_file_t; 718 719/* Represents where in the current svndiff data block each 720 representation is. */ 721typedef struct rep_state_t 722{ 723 /* shared lazy-open rev/pack file structure */ 724 shared_file_t *sfile; 725 /* The txdelta window cache to use or NULL. */ 726 svn_cache__t *raw_window_cache; 727 /* Caches raw (unparsed) windows. May be NULL. */ 728 svn_cache__t *window_cache; 729 /* Caches un-deltified windows. May be NULL. */ 730 svn_cache__t *combined_cache; 731 /* revision containing the representation */ 732 svn_revnum_t revision; 733 /* representation's item index in REVISION */ 734 apr_uint64_t item_index; 735 /* length of the header at the start of the rep. 736 0 iff this is rep is stored in a container 737 (i.e. does not have a header) */ 738 apr_size_t header_size; 739 apr_off_t start; /* The starting offset for the raw 740 svndiff/plaintext data minus header. 741 -1 if the offset is yet unknown. */ 742 apr_off_t current;/* The current offset relative to START. */ 743 apr_off_t size; /* The on-disk size of the representation. */ 744 int ver; /* If a delta, what svndiff version? 745 -1 for unknown delta version. */ 746 int chunk_index; /* number of the window to read */ 747} rep_state_t; 748 749/* Simple wrapper around svn_io_file_get_offset to simplify callers. */ 750static svn_error_t * 751get_file_offset(apr_off_t *offset, 752 rep_state_t *rs, 753 apr_pool_t *pool) 754{ 755 return svn_error_trace(svn_io_file_get_offset(offset, 756 rs->sfile->rfile->file, 757 pool)); 758} 759 760/* Simple wrapper around svn_io_file_aligned_seek to simplify callers. */ 761static svn_error_t * 762rs_aligned_seek(rep_state_t *rs, 763 apr_off_t *buffer_start, 764 apr_off_t offset, 765 apr_pool_t *pool) 766{ 767 fs_fs_data_t *ffd = rs->sfile->fs->fsap_data; 768 return svn_error_trace(svn_io_file_aligned_seek(rs->sfile->rfile->file, 769 ffd->block_size, 770 buffer_start, offset, 771 pool)); 772} 773 774/* Open FILE->FILE and FILE->STREAM if they haven't been opened, yet. */ 775static svn_error_t* 776auto_open_shared_file(shared_file_t *file) 777{ 778 if (file->rfile == NULL) 779 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file->rfile, file->fs, 780 file->revision, file->pool, 781 file->pool)); 782 783 return SVN_NO_ERROR; 784} 785 786/* Set RS->START to the begin of the representation raw in RS->FILE->FILE, 787 if that hasn't been done yet. Use POOL for temporary allocations. */ 788static svn_error_t* 789auto_set_start_offset(rep_state_t *rs, apr_pool_t *pool) 790{ 791 if (rs->start == -1) 792 { 793 SVN_ERR(svn_fs_fs__item_offset(&rs->start, rs->sfile->fs, 794 rs->sfile->rfile, rs->revision, NULL, 795 rs->item_index, pool)); 796 rs->start += rs->header_size; 797 } 798 799 return SVN_NO_ERROR; 800} 801 802/* Set RS->VER depending on what is found in the already open RS->FILE->FILE 803 if the diff version is still unknown. Use POOL for temporary allocations. 804 */ 805static svn_error_t* 806auto_read_diff_version(rep_state_t *rs, apr_pool_t *pool) 807{ 808 if (rs->ver == -1) 809 { 810 char buf[4]; 811 SVN_ERR(rs_aligned_seek(rs, NULL, rs->start, pool)); 812 SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, buf, 813 sizeof(buf), NULL, NULL, pool)); 814 815 /* ### Layering violation */ 816 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N'))) 817 return svn_error_create 818 (SVN_ERR_FS_CORRUPT, NULL, 819 _("Malformed svndiff data in representation")); 820 rs->ver = buf[3]; 821 822 rs->chunk_index = 0; 823 rs->current = 4; 824 } 825 826 return SVN_NO_ERROR; 827} 828 829/* See create_rep_state, which wraps this and adds another error. */ 830static svn_error_t * 831create_rep_state_body(rep_state_t **rep_state, 832 svn_fs_fs__rep_header_t **rep_header, 833 shared_file_t **shared_file, 834 representation_t *rep, 835 svn_fs_t *fs, 836 apr_pool_t *result_pool, 837 apr_pool_t *scratch_pool) 838{ 839 fs_fs_data_t *ffd = fs->fsap_data; 840 rep_state_t *rs = apr_pcalloc(result_pool, sizeof(*rs)); 841 svn_fs_fs__rep_header_t *rh; 842 svn_boolean_t is_cached = FALSE; 843 apr_uint64_t estimated_window_storage; 844 845 /* If the hint is 846 * - given, 847 * - refers to a valid revision, 848 * - refers to a packed revision, 849 * - as does the rep we want to read, and 850 * - refers to the same pack file as the rep 851 * we can re-use the same, already open file object 852 */ 853 svn_boolean_t reuse_shared_file 854 = shared_file && *shared_file && (*shared_file)->rfile 855 && SVN_IS_VALID_REVNUM((*shared_file)->revision) 856 && (*shared_file)->revision < ffd->min_unpacked_rev 857 && rep->revision < ffd->min_unpacked_rev 858 && ( ((*shared_file)->revision / ffd->max_files_per_dir) 859 == (rep->revision / ffd->max_files_per_dir)); 860 861 pair_cache_key_t key; 862 key.revision = rep->revision; 863 key.second = rep->item_index; 864 865 /* continue constructing RS and RA */ 866 rs->size = rep->size; 867 rs->revision = rep->revision; 868 rs->item_index = rep->item_index; 869 rs->raw_window_cache = use_block_read(fs) ? ffd->raw_window_cache : NULL; 870 rs->ver = -1; 871 rs->start = -1; 872 873 /* Very long files stored as self-delta will produce a huge number of 874 delta windows. Don't cache them lest we don't thrash the cache. 875 Since we don't know the depth of the delta chain, let's assume, the 876 whole contents get rewritten 3 times. 877 */ 878 estimated_window_storage = 4 * (rep->expanded_size + SVN_DELTA_WINDOW_SIZE); 879 estimated_window_storage = MIN(estimated_window_storage, APR_SIZE_MAX); 880 881 rs->window_cache = ffd->txdelta_window_cache 882 && svn_cache__is_cachable(ffd->txdelta_window_cache, 883 (apr_size_t)estimated_window_storage) 884 ? ffd->txdelta_window_cache 885 : NULL; 886 rs->combined_cache = ffd->combined_window_cache 887 && svn_cache__is_cachable(ffd->combined_window_cache, 888 (apr_size_t)estimated_window_storage) 889 ? ffd->combined_window_cache 890 : NULL; 891 892 /* cache lookup, i.e. skip reading the rep header if possible */ 893 if (ffd->rep_header_cache && !svn_fs_fs__id_txn_used(&rep->txn_id)) 894 SVN_ERR(svn_cache__get((void **) &rh, &is_cached, 895 ffd->rep_header_cache, &key, result_pool)); 896 897 /* initialize the (shared) FILE member in RS */ 898 if (reuse_shared_file) 899 { 900 rs->sfile = *shared_file; 901 } 902 else 903 { 904 shared_file_t *file = apr_pcalloc(result_pool, sizeof(*file)); 905 file->revision = rep->revision; 906 file->pool = result_pool; 907 file->fs = fs; 908 rs->sfile = file; 909 910 /* remember the current file, if suggested by the caller */ 911 if (shared_file) 912 *shared_file = file; 913 } 914 915 /* read rep header, if necessary */ 916 if (!is_cached) 917 { 918 /* ensure file is open and navigate to the start of rep header */ 919 if (reuse_shared_file) 920 { 921 apr_off_t offset; 922 923 /* ... we can re-use the same, already open file object. 924 * This implies that we don't read from a txn. 925 */ 926 rs->sfile = *shared_file; 927 SVN_ERR(auto_open_shared_file(rs->sfile)); 928 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rs->sfile->rfile, 929 rep->revision, NULL, rep->item_index, 930 scratch_pool)); 931 SVN_ERR(rs_aligned_seek(rs, NULL, offset, scratch_pool)); 932 } 933 else 934 { 935 /* otherwise, create a new file object. May or may not be 936 * an in-txn file. 937 */ 938 SVN_ERR(open_and_seek_representation(&rs->sfile->rfile, fs, rep, 939 result_pool)); 940 } 941 942 SVN_ERR(svn_fs_fs__read_rep_header(&rh, rs->sfile->rfile->stream, 943 result_pool, scratch_pool)); 944 SVN_ERR(get_file_offset(&rs->start, rs, result_pool)); 945 946 /* populate the cache if appropriate */ 947 if (! svn_fs_fs__id_txn_used(&rep->txn_id)) 948 { 949 if (use_block_read(fs)) 950 SVN_ERR(block_read(NULL, fs, rep->revision, rep->item_index, 951 rs->sfile->rfile, result_pool, scratch_pool)); 952 else 953 if (ffd->rep_header_cache) 954 SVN_ERR(svn_cache__set(ffd->rep_header_cache, &key, rh, 955 scratch_pool)); 956 } 957 } 958 959 /* finalize */ 960 SVN_ERR(dbg_log_access(fs, rep->revision, rep->item_index, rh, 961 SVN_FS_FS__ITEM_TYPE_ANY_REP, scratch_pool)); 962 963 rs->header_size = rh->header_size; 964 *rep_state = rs; 965 *rep_header = rh; 966 967 if (rh->type == svn_fs_fs__rep_plain) 968 /* This is a plaintext, so just return the current rep_state. */ 969 return SVN_NO_ERROR; 970 971 /* skip "SVNx" diff marker */ 972 rs->current = 4; 973 974 return SVN_NO_ERROR; 975} 976 977/* Read the rep args for REP in filesystem FS and create a rep_state 978 for reading the representation. Return the rep_state in *REP_STATE 979 and the rep header in *REP_HEADER, both allocated in POOL. 980 981 When reading multiple reps, i.e. a skip delta chain, you may provide 982 non-NULL SHARED_FILE. (If SHARED_FILE is not NULL, in the first 983 call it should be a pointer to NULL.) The function will use this 984 variable to store the previous call results and tries to re-use it. 985 This may result in significant savings in I/O for packed files and 986 number of open file handles. 987 */ 988static svn_error_t * 989create_rep_state(rep_state_t **rep_state, 990 svn_fs_fs__rep_header_t **rep_header, 991 shared_file_t **shared_file, 992 representation_t *rep, 993 svn_fs_t *fs, 994 apr_pool_t *result_pool, 995 apr_pool_t *scratch_pool) 996{ 997 svn_error_t *err = create_rep_state_body(rep_state, rep_header, 998 shared_file, rep, fs, 999 result_pool, scratch_pool); 1000 if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 1001 { 1002 fs_fs_data_t *ffd = fs->fsap_data; 1003 const char *rep_str; 1004 1005 /* ### This always returns "-1" for transaction reps, because 1006 ### this particular bit of code doesn't know if the rep is 1007 ### stored in the protorev or in the mutable area (for props 1008 ### or dir contents). It is pretty rare for FSFS to *read* 1009 ### from the protorev file, though, so this is probably OK. 1010 ### And anyone going to debug corruption errors is probably 1011 ### going to jump straight to this comment anyway! */ 1012 rep_str = rep 1013 ? svn_fs_fs__unparse_representation 1014 (rep, ffd->format, TRUE, scratch_pool, scratch_pool)->data 1015 : "(null)"; 1016 1017 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 1018 "Corrupt representation '%s'", 1019 rep_str); 1020 } 1021 /* ### Call representation_string() ? */ 1022 return svn_error_trace(err); 1023} 1024 1025svn_error_t * 1026svn_fs_fs__check_rep(representation_t *rep, 1027 svn_fs_t *fs, 1028 void **hint, 1029 apr_pool_t *scratch_pool) 1030{ 1031 if (svn_fs_fs__use_log_addressing(fs)) 1032 { 1033 apr_off_t offset; 1034 svn_fs_fs__p2l_entry_t *entry; 1035 svn_fs_fs__revision_file_t *rev_file = NULL; 1036 1037 /* Reuse the revision file provided by *HINT, if it is given and 1038 * actually the rev / pack file that we want. */ 1039 svn_revnum_t start_rev = svn_fs_fs__packed_base_rev(fs, rep->revision); 1040 if (hint) 1041 rev_file = *(svn_fs_fs__revision_file_t **)hint; 1042 1043 if (rev_file == NULL || rev_file->start_revision != start_rev) 1044 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rep->revision, 1045 scratch_pool, scratch_pool)); 1046 1047 if (hint) 1048 *hint = rev_file; 1049 1050 /* This will auto-retry if there was a background pack. */ 1051 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rep->revision, 1052 NULL, rep->item_index, scratch_pool)); 1053 1054 /* This may fail if there is a background pack operation (can't auto- 1055 retry because the item offset lookup has to be redone as well). */ 1056 SVN_ERR(svn_fs_fs__p2l_entry_lookup(&entry, fs, rev_file, 1057 rep->revision, offset, 1058 scratch_pool, scratch_pool)); 1059 1060 if ( entry == NULL 1061 || entry->type < SVN_FS_FS__ITEM_TYPE_FILE_REP 1062 || entry->type > SVN_FS_FS__ITEM_TYPE_DIR_PROPS) 1063 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1064 _("No representation found at offset %s " 1065 "for item %s in revision %ld"), 1066 apr_off_t_toa(scratch_pool, offset), 1067 apr_psprintf(scratch_pool, 1068 "%" APR_UINT64_T_FMT, 1069 rep->item_index), 1070 rep->revision); 1071 } 1072 else 1073 { 1074 rep_state_t *rs; 1075 svn_fs_fs__rep_header_t *rep_header; 1076 1077 /* ### Should this be using read_rep_line() directly? */ 1078 SVN_ERR(create_rep_state(&rs, &rep_header, (shared_file_t**)hint, 1079 rep, fs, scratch_pool, scratch_pool)); 1080 } 1081 1082 return SVN_NO_ERROR; 1083} 1084 1085svn_error_t * 1086svn_fs_fs__rep_chain_length(int *chain_length, 1087 int *shard_count, 1088 representation_t *rep, 1089 svn_fs_t *fs, 1090 apr_pool_t *scratch_pool) 1091{ 1092 fs_fs_data_t *ffd = fs->fsap_data; 1093 svn_revnum_t shard_size = ffd->max_files_per_dir 1094 ? ffd->max_files_per_dir 1095 : 1; 1096 apr_pool_t *subpool = svn_pool_create(scratch_pool); 1097 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1098 svn_boolean_t is_delta = FALSE; 1099 int count = 0; 1100 int shards = 1; 1101 svn_revnum_t last_shard = rep->revision / shard_size; 1102 1103 /* Check whether the length of the deltification chain is acceptable. 1104 * Otherwise, shared reps may form a non-skipping delta chain in 1105 * extreme cases. */ 1106 representation_t base_rep = *rep; 1107 1108 /* re-use open files between iterations */ 1109 shared_file_t *file_hint = NULL; 1110 1111 svn_fs_fs__rep_header_t *header; 1112 1113 /* follow the delta chain towards the end but for at most 1114 * MAX_CHAIN_LENGTH steps. */ 1115 do 1116 { 1117 rep_state_t *rep_state; 1118 1119 svn_pool_clear(iterpool); 1120 1121 if (base_rep.revision / shard_size != last_shard) 1122 { 1123 last_shard = base_rep.revision / shard_size; 1124 ++shards; 1125 } 1126 1127 SVN_ERR(create_rep_state_body(&rep_state, 1128 &header, 1129 &file_hint, 1130 &base_rep, 1131 fs, 1132 subpool, 1133 iterpool)); 1134 1135 base_rep.revision = header->base_revision; 1136 base_rep.item_index = header->base_item_index; 1137 base_rep.size = header->base_length; 1138 svn_fs_fs__id_txn_reset(&base_rep.txn_id); 1139 is_delta = header->type == svn_fs_fs__rep_delta; 1140 1141 /* Clear it the SUBPOOL once in a while. Doing it too frequently 1142 * renders the FILE_HINT ineffective. Doing too infrequently, may 1143 * leave us with too many open file handles. 1144 * 1145 * Note that this is mostly about efficiency, with larger values 1146 * being more efficient, and any non-zero value is legal here. When 1147 * reading deltified contents, we may keep 10s of rev files open at 1148 * the same time and the system has to cope with that. Thus, the 1149 * limit of 16 chosen below is in the same ballpark. 1150 */ 1151 ++count; 1152 if (count % 16 == 0) 1153 { 1154 file_hint = NULL; 1155 svn_pool_clear(subpool); 1156 } 1157 } 1158 while (is_delta && base_rep.revision); 1159 1160 *chain_length = count; 1161 *shard_count = shards; 1162 svn_pool_destroy(subpool); 1163 svn_pool_destroy(iterpool); 1164 1165 return SVN_NO_ERROR; 1166} 1167 1168struct rep_read_baton 1169{ 1170 /* The FS from which we're reading. */ 1171 svn_fs_t *fs; 1172 1173 /* Representation to read. */ 1174 representation_t rep; 1175 1176 /* If not NULL, this is the base for the first delta window in rs_list */ 1177 svn_stringbuf_t *base_window; 1178 1179 /* The state of all prior delta representations. */ 1180 apr_array_header_t *rs_list; 1181 1182 /* The plaintext state, if there is a plaintext. */ 1183 rep_state_t *src_state; 1184 1185 /* The index of the current delta chunk, if we are reading a delta. */ 1186 int chunk_index; 1187 1188 /* The buffer where we store undeltified data. */ 1189 char *buf; 1190 apr_size_t buf_pos; 1191 apr_size_t buf_len; 1192 1193 /* A checksum context for summing the data read in order to verify it. 1194 Note: we don't need to use the sha1 checksum because we're only doing 1195 data verification, for which md5 is perfectly safe. */ 1196 svn_checksum_ctx_t *md5_checksum_ctx; 1197 1198 svn_boolean_t checksum_finalized; 1199 1200 /* The stored checksum of the representation we are reading, its 1201 length, and the amount we've read so far. Some of this 1202 information is redundant with rs_list and src_state, but it's 1203 convenient for the checksumming code to have it here. */ 1204 unsigned char md5_digest[APR_MD5_DIGESTSIZE]; 1205 1206 svn_filesize_t len; 1207 svn_filesize_t off; 1208 1209 /* The key for the fulltext cache for this rep, if there is a 1210 fulltext cache. */ 1211 pair_cache_key_t fulltext_cache_key; 1212 /* The text we've been reading, if we're going to cache it. */ 1213 svn_stringbuf_t *current_fulltext; 1214 1215 /* If not NULL, attempt to read the data from this cache. 1216 Once that lookup fails, reset it to NULL. */ 1217 svn_cache__t *fulltext_cache; 1218 1219 /* Bytes delivered from the FULLTEXT_CACHE so far. If the next 1220 lookup fails, we need to skip that much data from the reconstructed 1221 window stream before we continue normal operation. */ 1222 svn_filesize_t fulltext_delivered; 1223 1224 /* Used for temporary allocations during the read. */ 1225 apr_pool_t *pool; 1226 1227 /* Pool used to store file handles and other data that is persistant 1228 for the entire stream read. */ 1229 apr_pool_t *filehandle_pool; 1230}; 1231 1232/* Set window key in *KEY to address the window described by RS. 1233 For convenience, return the KEY. */ 1234static window_cache_key_t * 1235get_window_key(window_cache_key_t *key, rep_state_t *rs) 1236{ 1237 assert(rs->revision <= APR_UINT32_MAX); 1238 key->revision = (apr_uint32_t)rs->revision; 1239 key->item_index = rs->item_index; 1240 key->chunk_index = rs->chunk_index; 1241 1242 return key; 1243} 1244 1245/* Implement svn_cache__partial_getter_func_t for raw txdelta windows. 1246 * Parse the raw data and return a svn_fs_fs__txdelta_cached_window_t. 1247 */ 1248static svn_error_t * 1249parse_raw_window(void **out, 1250 const void *data, 1251 apr_size_t data_len, 1252 void *baton, 1253 apr_pool_t *result_pool) 1254{ 1255 svn_string_t raw_window; 1256 svn_stream_t *stream; 1257 1258 /* unparsed and parsed window */ 1259 const svn_fs_fs__raw_cached_window_t *window 1260 = (const svn_fs_fs__raw_cached_window_t *)data; 1261 svn_fs_fs__txdelta_cached_window_t *result 1262 = apr_pcalloc(result_pool, sizeof(*result)); 1263 1264 /* create a read stream taking the raw window as input */ 1265 raw_window.data = svn_temp_deserializer__ptr(window, 1266 (const void * const *)&window->window.data); 1267 raw_window.len = window->window.len; 1268 stream = svn_stream_from_string(&raw_window, result_pool); 1269 1270 /* parse it */ 1271 SVN_ERR(svn_txdelta_read_svndiff_window(&result->window, stream, window->ver, 1272 result_pool)); 1273 1274 /* complete the window and return it */ 1275 result->end_offset = window->end_offset; 1276 *out = result; 1277 1278 return SVN_NO_ERROR; 1279} 1280 1281 1282/* Read the WINDOW_P number CHUNK_INDEX for the representation given in 1283 * rep state RS from the current FSFS session's cache. This will be a 1284 * no-op and IS_CACHED will be set to FALSE if no cache has been given. 1285 * If a cache is available IS_CACHED will inform the caller about the 1286 * success of the lookup. Allocations of the window in will be made 1287 * from RESULT_POOL. Use SCRATCH_POOL for temporary allocations. 1288 * 1289 * If the information could be found, put RS to CHUNK_INDEX. 1290 */ 1291static svn_error_t * 1292get_cached_window(svn_txdelta_window_t **window_p, 1293 rep_state_t *rs, 1294 int chunk_index, 1295 svn_boolean_t *is_cached, 1296 apr_pool_t *result_pool, 1297 apr_pool_t *scratch_pool) 1298{ 1299 if (! rs->window_cache) 1300 { 1301 /* txdelta window has not been enabled */ 1302 *is_cached = FALSE; 1303 } 1304 else 1305 { 1306 /* ask the cache for the desired txdelta window */ 1307 svn_fs_fs__txdelta_cached_window_t *cached_window; 1308 window_cache_key_t key = { 0 }; 1309 get_window_key(&key, rs); 1310 key.chunk_index = chunk_index; 1311 SVN_ERR(svn_cache__get((void **) &cached_window, 1312 is_cached, 1313 rs->window_cache, 1314 &key, 1315 result_pool)); 1316 1317 /* If we did not find a parsed txdelta window, we might have a raw 1318 version of it in our cache. If so, read, parse and re-cache it. */ 1319 if (!*is_cached && rs->raw_window_cache) 1320 { 1321 SVN_ERR(svn_cache__get_partial((void **) &cached_window, is_cached, 1322 rs->raw_window_cache, &key, 1323 parse_raw_window, NULL, result_pool)); 1324 if (*is_cached) 1325 SVN_ERR(svn_cache__set(rs->window_cache, &key, cached_window, 1326 scratch_pool)); 1327 } 1328 1329 /* Return cached information. */ 1330 if (*is_cached) 1331 { 1332 /* found it. Pass it back to the caller. */ 1333 *window_p = cached_window->window; 1334 1335 /* manipulate the RS as if we just read the data */ 1336 rs->current = cached_window->end_offset; 1337 rs->chunk_index = chunk_index; 1338 } 1339 } 1340 1341 return SVN_NO_ERROR; 1342} 1343 1344/* Store the WINDOW read for the rep state RS in the current FSFS 1345 * session's cache. This will be a no-op if no cache has been given. 1346 * Temporary allocations will be made from SCRATCH_POOL. */ 1347static svn_error_t * 1348set_cached_window(svn_txdelta_window_t *window, 1349 rep_state_t *rs, 1350 apr_pool_t *scratch_pool) 1351{ 1352 if (rs->window_cache) 1353 { 1354 /* store the window and the first offset _past_ it */ 1355 svn_fs_fs__txdelta_cached_window_t cached_window; 1356 window_cache_key_t key = {0}; 1357 1358 cached_window.window = window; 1359 cached_window.end_offset = rs->current; 1360 1361 /* but key it with the start offset because that is the known state 1362 * when we will look it up */ 1363 SVN_ERR(svn_cache__set(rs->window_cache, 1364 get_window_key(&key, rs), 1365 &cached_window, 1366 scratch_pool)); 1367 } 1368 1369 return SVN_NO_ERROR; 1370} 1371 1372/* Read the WINDOW_P for the rep state RS from the current FSFS session's 1373 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 1374 * cache has been given. If a cache is available IS_CACHED will inform 1375 * the caller about the success of the lookup. Allocations (of the window 1376 * in particular) will be made from POOL. 1377 */ 1378static svn_error_t * 1379get_cached_combined_window(svn_stringbuf_t **window_p, 1380 rep_state_t *rs, 1381 svn_boolean_t *is_cached, 1382 apr_pool_t *pool) 1383{ 1384 if (! rs->combined_cache) 1385 { 1386 /* txdelta window has not been enabled */ 1387 *is_cached = FALSE; 1388 } 1389 else 1390 { 1391 /* ask the cache for the desired txdelta window */ 1392 window_cache_key_t key = { 0 }; 1393 return svn_cache__get((void **)window_p, 1394 is_cached, 1395 rs->combined_cache, 1396 get_window_key(&key, rs), 1397 pool); 1398 } 1399 1400 return SVN_NO_ERROR; 1401} 1402 1403/* Store the WINDOW read for the rep state RS in the current FSFS session's 1404 * cache. This will be a no-op if no cache has been given. 1405 * Temporary allocations will be made from SCRATCH_POOL. */ 1406static svn_error_t * 1407set_cached_combined_window(svn_stringbuf_t *window, 1408 rep_state_t *rs, 1409 apr_pool_t *scratch_pool) 1410{ 1411 if (rs->combined_cache) 1412 { 1413 /* but key it with the start offset because that is the known state 1414 * when we will look it up */ 1415 window_cache_key_t key = { 0 }; 1416 return svn_cache__set(rs->combined_cache, 1417 get_window_key(&key, rs), 1418 window, 1419 scratch_pool); 1420 } 1421 1422 return SVN_NO_ERROR; 1423} 1424 1425/* Build an array of rep_state structures in *LIST giving the delta 1426 reps from first_rep to a plain-text or self-compressed rep. Set 1427 *SRC_STATE to the plain-text rep we find at the end of the chain, 1428 or to NULL if the final delta representation is self-compressed. 1429 The representation to start from is designated by filesystem FS, id 1430 ID, and representation REP. 1431 Also, set *WINDOW_P to the base window content for *LIST, if it 1432 could be found in cache. Otherwise, *LIST will contain the base 1433 representation for the whole delta chain. */ 1434static svn_error_t * 1435build_rep_list(apr_array_header_t **list, 1436 svn_stringbuf_t **window_p, 1437 rep_state_t **src_state, 1438 svn_fs_t *fs, 1439 representation_t *first_rep, 1440 apr_pool_t *pool) 1441{ 1442 representation_t rep; 1443 rep_state_t *rs = NULL; 1444 svn_fs_fs__rep_header_t *rep_header; 1445 svn_boolean_t is_cached = FALSE; 1446 shared_file_t *shared_file = NULL; 1447 apr_pool_t *iterpool = svn_pool_create(pool); 1448 1449 *list = apr_array_make(pool, 1, sizeof(rep_state_t *)); 1450 rep = *first_rep; 1451 1452 /* for the top-level rep, we need the rep_args */ 1453 SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, &rep, fs, pool, 1454 iterpool)); 1455 while (1) 1456 { 1457 svn_pool_clear(iterpool); 1458 1459 /* fetch state, if that has not been done already */ 1460 if (!rs) 1461 SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, 1462 &rep, fs, pool, iterpool)); 1463 1464 /* for txn reps, there won't be a cached combined window */ 1465 if ( !svn_fs_fs__id_txn_used(&rep.txn_id) 1466 && rep.expanded_size < SVN_DELTA_WINDOW_SIZE) 1467 SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool)); 1468 1469 if (is_cached) 1470 { 1471 /* We already have a reconstructed window in our cache. 1472 Write a pseudo rep_state with the full length. */ 1473 rs->start = 0; 1474 rs->current = 0; 1475 rs->size = (*window_p)->len; 1476 *src_state = rs; 1477 break; 1478 } 1479 1480 if (rep_header->type == svn_fs_fs__rep_plain) 1481 { 1482 /* This is a plaintext, so just return the current rep_state. */ 1483 *src_state = rs; 1484 break; 1485 } 1486 1487 /* Push this rep onto the list. If it's self-compressed, we're done. */ 1488 APR_ARRAY_PUSH(*list, rep_state_t *) = rs; 1489 if (rep_header->type == svn_fs_fs__rep_self_delta) 1490 { 1491 *src_state = NULL; 1492 break; 1493 } 1494 1495 rep.revision = rep_header->base_revision; 1496 rep.item_index = rep_header->base_item_index; 1497 rep.size = rep_header->base_length; 1498 svn_fs_fs__id_txn_reset(&rep.txn_id); 1499 1500 rs = NULL; 1501 } 1502 svn_pool_destroy(iterpool); 1503 1504 return SVN_NO_ERROR; 1505} 1506 1507 1508/* Create a rep_read_baton structure for node revision NODEREV in 1509 filesystem FS and store it in *RB_P. Perform all allocations in 1510 POOL. If rep is mutable, it must be for file contents. */ 1511static svn_error_t * 1512rep_read_get_baton(struct rep_read_baton **rb_p, 1513 svn_fs_t *fs, 1514 representation_t *rep, 1515 pair_cache_key_t fulltext_cache_key, 1516 apr_pool_t *pool) 1517{ 1518 struct rep_read_baton *b; 1519 1520 b = apr_pcalloc(pool, sizeof(*b)); 1521 b->fs = fs; 1522 b->rep = *rep; 1523 b->base_window = NULL; 1524 b->chunk_index = 0; 1525 b->buf = NULL; 1526 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 1527 b->checksum_finalized = FALSE; 1528 memcpy(b->md5_digest, rep->md5_digest, sizeof(rep->md5_digest)); 1529 b->len = rep->expanded_size; 1530 b->off = 0; 1531 b->fulltext_cache_key = fulltext_cache_key; 1532 b->pool = svn_pool_create(pool); 1533 b->filehandle_pool = svn_pool_create(pool); 1534 b->fulltext_cache = NULL; 1535 b->fulltext_delivered = 0; 1536 b->current_fulltext = NULL; 1537 1538 /* Save our output baton. */ 1539 *rb_p = b; 1540 1541 return SVN_NO_ERROR; 1542} 1543 1544/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta 1545 window into *NWIN. Note that RS->CHUNK_INDEX will be THIS_CHUNK rather 1546 than THIS_CHUNK + 1 when this function returns. */ 1547static svn_error_t * 1548read_delta_window(svn_txdelta_window_t **nwin, int this_chunk, 1549 rep_state_t *rs, apr_pool_t *result_pool, 1550 apr_pool_t *scratch_pool) 1551{ 1552 svn_boolean_t is_cached; 1553 apr_off_t start_offset; 1554 apr_off_t end_offset; 1555 apr_pool_t *iterpool; 1556 1557 SVN_ERR_ASSERT(rs->chunk_index <= this_chunk); 1558 1559 SVN_ERR(dbg_log_access(rs->sfile->fs, rs->revision, rs->item_index, 1560 NULL, SVN_FS_FS__ITEM_TYPE_ANY_REP, scratch_pool)); 1561 1562 /* Read the next window. But first, try to find it in the cache. */ 1563 SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached, 1564 result_pool, scratch_pool)); 1565 if (is_cached) 1566 return SVN_NO_ERROR; 1567 1568 /* someone has to actually read the data from file. Open it */ 1569 SVN_ERR(auto_open_shared_file(rs->sfile)); 1570 1571 /* invoke the 'block-read' feature for non-txn data. 1572 However, don't do that if we are in the middle of some representation, 1573 because the block is unlikely to contain other data. */ 1574 if ( rs->chunk_index == 0 1575 && SVN_IS_VALID_REVNUM(rs->revision) 1576 && use_block_read(rs->sfile->fs) 1577 && rs->raw_window_cache) 1578 { 1579 SVN_ERR(block_read(NULL, rs->sfile->fs, rs->revision, rs->item_index, 1580 rs->sfile->rfile, result_pool, scratch_pool)); 1581 1582 /* reading the whole block probably also provided us with the 1583 desired txdelta window */ 1584 SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached, 1585 result_pool, scratch_pool)); 1586 if (is_cached) 1587 return SVN_NO_ERROR; 1588 } 1589 1590 /* data is still not cached -> we need to read it. 1591 Make sure we have all the necessary info. */ 1592 SVN_ERR(auto_set_start_offset(rs, scratch_pool)); 1593 SVN_ERR(auto_read_diff_version(rs, scratch_pool)); 1594 1595 /* RS->FILE may be shared between RS instances -> make sure we point 1596 * to the right data. */ 1597 start_offset = rs->start + rs->current; 1598 SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, scratch_pool)); 1599 1600 /* Skip windows to reach the current chunk if we aren't there yet. */ 1601 iterpool = svn_pool_create(scratch_pool); 1602 while (rs->chunk_index < this_chunk) 1603 { 1604 svn_pool_clear(iterpool); 1605 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->sfile->rfile->file, 1606 rs->ver, iterpool)); 1607 rs->chunk_index++; 1608 SVN_ERR(get_file_offset(&start_offset, rs, iterpool)); 1609 rs->current = start_offset - rs->start; 1610 if (rs->current >= rs->size) 1611 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1612 _("Reading one svndiff window read " 1613 "beyond the end of the " 1614 "representation")); 1615 } 1616 svn_pool_destroy(iterpool); 1617 1618 /* Actually read the next window. */ 1619 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, rs->sfile->rfile->stream, 1620 rs->ver, result_pool)); 1621 SVN_ERR(get_file_offset(&end_offset, rs, scratch_pool)); 1622 rs->current = end_offset - rs->start; 1623 if (rs->current > rs->size) 1624 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1625 _("Reading one svndiff window read beyond " 1626 "the end of the representation")); 1627 1628 /* the window has not been cached before, thus cache it now 1629 * (if caching is used for them at all) */ 1630 if (SVN_IS_VALID_REVNUM(rs->revision)) 1631 SVN_ERR(set_cached_window(*nwin, rs, scratch_pool)); 1632 1633 return SVN_NO_ERROR; 1634} 1635 1636/* Read SIZE bytes from the representation RS and return it in *NWIN. */ 1637static svn_error_t * 1638read_plain_window(svn_stringbuf_t **nwin, rep_state_t *rs, 1639 apr_size_t size, apr_pool_t *result_pool, 1640 apr_pool_t *scratch_pool) 1641{ 1642 apr_off_t offset; 1643 1644 /* RS->FILE may be shared between RS instances -> make sure we point 1645 * to the right data. */ 1646 SVN_ERR(auto_open_shared_file(rs->sfile)); 1647 SVN_ERR(auto_set_start_offset(rs, scratch_pool)); 1648 1649 offset = rs->start + rs->current; 1650 SVN_ERR(rs_aligned_seek(rs, NULL, offset, scratch_pool)); 1651 1652 /* Read the plain data. */ 1653 *nwin = svn_stringbuf_create_ensure(size, result_pool); 1654 SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, (*nwin)->data, size, 1655 NULL, NULL, result_pool)); 1656 (*nwin)->data[size] = 0; 1657 1658 /* Update RS. */ 1659 rs->current += (apr_off_t)size; 1660 1661 return SVN_NO_ERROR; 1662} 1663 1664/* Skip SIZE bytes from the PLAIN representation RS. */ 1665static svn_error_t * 1666skip_plain_window(rep_state_t *rs, 1667 apr_size_t size) 1668{ 1669 /* Update RS. */ 1670 rs->current += (apr_off_t)size; 1671 1672 return SVN_NO_ERROR; 1673} 1674 1675/* Get the undeltified window that is a result of combining all deltas 1676 from the current desired representation identified in *RB with its 1677 base representation. Store the window in *RESULT. */ 1678static svn_error_t * 1679get_combined_window(svn_stringbuf_t **result, 1680 struct rep_read_baton *rb) 1681{ 1682 apr_pool_t *pool, *new_pool, *window_pool; 1683 int i; 1684 apr_array_header_t *windows; 1685 svn_stringbuf_t *source, *buf = rb->base_window; 1686 rep_state_t *rs; 1687 apr_pool_t *iterpool; 1688 1689 /* Read all windows that we need to combine. This is fine because 1690 the size of each window is relatively small (100kB) and skip- 1691 delta limits the number of deltas in a chain to well under 100. 1692 Stop early if one of them does not depend on its predecessors. */ 1693 window_pool = svn_pool_create(rb->pool); 1694 windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *)); 1695 iterpool = svn_pool_create(rb->pool); 1696 for (i = 0; i < rb->rs_list->nelts; ++i) 1697 { 1698 svn_txdelta_window_t *window; 1699 1700 svn_pool_clear(iterpool); 1701 1702 rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *); 1703 SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool, 1704 iterpool)); 1705 1706 APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window; 1707 if (window->src_ops == 0) 1708 { 1709 ++i; 1710 break; 1711 } 1712 } 1713 1714 /* Combine in the windows from the other delta reps. */ 1715 pool = svn_pool_create(rb->pool); 1716 for (--i; i >= 0; --i) 1717 { 1718 svn_txdelta_window_t *window; 1719 1720 svn_pool_clear(iterpool); 1721 1722 rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *); 1723 window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *); 1724 1725 /* Maybe, we've got a PLAIN start representation. If we do, read 1726 as much data from it as the needed for the txdelta window's source 1727 view. 1728 Note that BUF / SOURCE may only be NULL in the first iteration. 1729 Also note that we may have short-cut reading the delta chain -- 1730 in which case SRC_OPS is 0 and it might not be a PLAIN rep. */ 1731 source = buf; 1732 if (source == NULL && rb->src_state != NULL) 1733 { 1734 /* Even if we don't need the source rep now, we still must keep 1735 * its read offset in sync with what we might need for the next 1736 * window. */ 1737 if (window->src_ops) 1738 SVN_ERR(read_plain_window(&source, rb->src_state, 1739 window->sview_len, 1740 pool, iterpool)); 1741 else 1742 SVN_ERR(skip_plain_window(rb->src_state, window->sview_len)); 1743 } 1744 1745 /* Combine this window with the current one. */ 1746 new_pool = svn_pool_create(rb->pool); 1747 buf = svn_stringbuf_create_ensure(window->tview_len, new_pool); 1748 buf->len = window->tview_len; 1749 1750 svn_txdelta_apply_instructions(window, source ? source->data : NULL, 1751 buf->data, &buf->len); 1752 if (buf->len != window->tview_len) 1753 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1754 _("svndiff window length is " 1755 "corrupt")); 1756 1757 /* Cache windows only if the whole rep content could be read as a 1758 single chunk. Only then will no other chunk need a deeper RS 1759 list than the cached chunk. */ 1760 if ( (rb->chunk_index == 0) && (rs->current == rs->size) 1761 && SVN_IS_VALID_REVNUM(rs->revision)) 1762 SVN_ERR(set_cached_combined_window(buf, rs, new_pool)); 1763 1764 rs->chunk_index++; 1765 1766 /* Cycle pools so that we only need to hold three windows at a time. */ 1767 svn_pool_destroy(pool); 1768 pool = new_pool; 1769 } 1770 svn_pool_destroy(iterpool); 1771 1772 svn_pool_destroy(window_pool); 1773 1774 *result = buf; 1775 return SVN_NO_ERROR; 1776} 1777 1778/* Returns whether or not the expanded fulltext of the file is cachable 1779 * based on its size SIZE. The decision depends on the cache used by FFD. 1780 */ 1781static svn_boolean_t 1782fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size) 1783{ 1784 return (size < APR_SIZE_MAX) 1785 && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size); 1786} 1787 1788/* Close method used on streams returned by read_representation(). 1789 */ 1790static svn_error_t * 1791rep_read_contents_close(void *baton) 1792{ 1793 struct rep_read_baton *rb = baton; 1794 1795 svn_pool_destroy(rb->pool); 1796 svn_pool_destroy(rb->filehandle_pool); 1797 1798 return SVN_NO_ERROR; 1799} 1800 1801/* Return the next *LEN bytes of the rep from our plain / delta windows 1802 and store them in *BUF. */ 1803static svn_error_t * 1804get_contents_from_windows(struct rep_read_baton *rb, 1805 char *buf, 1806 apr_size_t *len) 1807{ 1808 apr_size_t copy_len, remaining = *len; 1809 char *cur = buf; 1810 rep_state_t *rs; 1811 1812 /* Special case for when there are no delta reps, only a plain 1813 text. */ 1814 if (rb->rs_list->nelts == 0) 1815 { 1816 copy_len = remaining; 1817 rs = rb->src_state; 1818 1819 if (rb->base_window != NULL) 1820 { 1821 /* We got the desired rep directly from the cache. 1822 This is where we need the pseudo rep_state created 1823 by build_rep_list(). */ 1824 apr_size_t offset = (apr_size_t)rs->current; 1825 if (offset >= rb->base_window->len) 1826 copy_len = 0ul; 1827 else if (copy_len > rb->base_window->len - offset) 1828 copy_len = rb->base_window->len - offset; 1829 1830 memcpy (cur, rb->base_window->data + offset, copy_len); 1831 } 1832 else 1833 { 1834 apr_off_t offset; 1835 if (((apr_off_t) copy_len) > rs->size - rs->current) 1836 copy_len = (apr_size_t) (rs->size - rs->current); 1837 1838 SVN_ERR(auto_open_shared_file(rs->sfile)); 1839 SVN_ERR(auto_set_start_offset(rs, rb->pool)); 1840 1841 offset = rs->start + rs->current; 1842 SVN_ERR(rs_aligned_seek(rs, NULL, offset, rb->pool)); 1843 SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, cur, 1844 copy_len, NULL, NULL, rb->pool)); 1845 } 1846 1847 rs->current += copy_len; 1848 *len = copy_len; 1849 return SVN_NO_ERROR; 1850 } 1851 1852 while (remaining > 0) 1853 { 1854 /* If we have buffered data from a previous chunk, use that. */ 1855 if (rb->buf) 1856 { 1857 /* Determine how much to copy from the buffer. */ 1858 copy_len = rb->buf_len - rb->buf_pos; 1859 if (copy_len > remaining) 1860 copy_len = remaining; 1861 1862 /* Actually copy the data. */ 1863 memcpy(cur, rb->buf + rb->buf_pos, copy_len); 1864 rb->buf_pos += copy_len; 1865 cur += copy_len; 1866 remaining -= copy_len; 1867 1868 /* If the buffer is all used up, clear it and empty the 1869 local pool. */ 1870 if (rb->buf_pos == rb->buf_len) 1871 { 1872 svn_pool_clear(rb->pool); 1873 rb->buf = NULL; 1874 } 1875 } 1876 else 1877 { 1878 svn_stringbuf_t *sbuf = NULL; 1879 1880 rs = APR_ARRAY_IDX(rb->rs_list, 0, rep_state_t *); 1881 if (rs->current == rs->size) 1882 break; 1883 1884 /* Get more buffered data by evaluating a chunk. */ 1885 SVN_ERR(get_combined_window(&sbuf, rb)); 1886 1887 rb->chunk_index++; 1888 rb->buf_len = sbuf->len; 1889 rb->buf = sbuf->data; 1890 rb->buf_pos = 0; 1891 } 1892 } 1893 1894 *len = cur - buf; 1895 1896 return SVN_NO_ERROR; 1897} 1898 1899/* Baton type for get_fulltext_partial. */ 1900typedef struct fulltext_baton_t 1901{ 1902 /* Target buffer to write to; of at least LEN bytes. */ 1903 char *buffer; 1904 1905 /* Offset within the respective fulltext at which we shall start to 1906 copy data into BUFFER. */ 1907 apr_size_t start; 1908 1909 /* Number of bytes to copy. The actual amount may be less in case 1910 the fulltext is short(er). */ 1911 apr_size_t len; 1912 1913 /* Number of bytes actually copied into BUFFER. */ 1914 apr_size_t read; 1915} fulltext_baton_t; 1916 1917/* Implement svn_cache__partial_getter_func_t for fulltext caches. 1918 * From the fulltext in DATA, we copy the range specified by the 1919 * fulltext_baton_t* BATON into the buffer provided by that baton. 1920 * OUT and RESULT_POOL are not used. 1921 */ 1922static svn_error_t * 1923get_fulltext_partial(void **out, 1924 const void *data, 1925 apr_size_t data_len, 1926 void *baton, 1927 apr_pool_t *result_pool) 1928{ 1929 fulltext_baton_t *fulltext_baton = baton; 1930 1931 /* We cached the fulltext with an NUL appended to it. */ 1932 apr_size_t fulltext_len = data_len - 1; 1933 1934 /* Clip the copy range to what the fulltext size allows. */ 1935 apr_size_t start = MIN(fulltext_baton->start, fulltext_len); 1936 fulltext_baton->read = MIN(fulltext_len - start, fulltext_baton->len); 1937 1938 /* Copy the data to the output buffer and be done. */ 1939 memcpy(fulltext_baton->buffer, (const char *)data + start, 1940 fulltext_baton->read); 1941 1942 return SVN_NO_ERROR; 1943} 1944 1945/* Find the fulltext specified in BATON in the fulltext cache given 1946 * as well by BATON. If that succeeds, set *CACHED to TRUE and copy 1947 * up to the next *LEN bytes into BUFFER. Set *LEN to the actual 1948 * number of bytes copied. 1949 */ 1950static svn_error_t * 1951get_contents_from_fulltext(svn_boolean_t *cached, 1952 struct rep_read_baton *baton, 1953 char *buffer, 1954 apr_size_t *len) 1955{ 1956 void *dummy; 1957 fulltext_baton_t fulltext_baton; 1958 1959 SVN_ERR_ASSERT((apr_size_t)baton->fulltext_delivered 1960 == baton->fulltext_delivered); 1961 fulltext_baton.buffer = buffer; 1962 fulltext_baton.start = (apr_size_t)baton->fulltext_delivered; 1963 fulltext_baton.len = *len; 1964 fulltext_baton.read = 0; 1965 1966 SVN_ERR(svn_cache__get_partial(&dummy, cached, baton->fulltext_cache, 1967 &baton->fulltext_cache_key, 1968 get_fulltext_partial, &fulltext_baton, 1969 baton->pool)); 1970 1971 if (*cached) 1972 { 1973 baton->fulltext_delivered += fulltext_baton.read; 1974 *len = fulltext_baton.read; 1975 } 1976 1977 return SVN_NO_ERROR; 1978} 1979 1980/* Determine the optimal size of a string buf that shall receive a 1981 * (full-) text of NEEDED bytes. 1982 * 1983 * The critical point is that those buffers may be very large and 1984 * can cause memory fragmentation. We apply simple heuristics to 1985 * make fragmentation less likely. 1986 */ 1987static apr_size_t 1988optimimal_allocation_size(apr_size_t needed) 1989{ 1990 /* For all allocations, assume some overhead that is shared between 1991 * OS memory managemnt, APR memory management and svn_stringbuf_t. */ 1992 const apr_size_t overhead = 0x400; 1993 apr_size_t optimal; 1994 1995 /* If an allocation size if safe for other ephemeral buffers, it should 1996 * be safe for ours. */ 1997 if (needed <= SVN__STREAM_CHUNK_SIZE) 1998 return needed; 1999 2000 /* Paranoia edge case: 2001 * Skip our heuristics if they created arithmetical overflow. 2002 * Beware to make this test work for NEEDED = APR_SIZE_MAX as well! */ 2003 if (needed >= APR_SIZE_MAX / 2 - overhead) 2004 return needed; 2005 2006 /* As per definition SVN__STREAM_CHUNK_SIZE is a power of two. 2007 * Since we know NEEDED to be larger than that, use it as the 2008 * starting point. 2009 * 2010 * Heuristics: Allocate a power-of-two number of bytes that fit 2011 * NEEDED plus some OVERHEAD. The APR allocator 2012 * will round it up to the next full page size. 2013 */ 2014 optimal = SVN__STREAM_CHUNK_SIZE; 2015 while (optimal - overhead < needed) 2016 optimal *= 2; 2017 2018 /* This is above or equal to NEEDED. */ 2019 return optimal - overhead; 2020} 2021 2022/* After a fulltext cache lookup failure, we will continue to read from 2023 * combined delta or plain windows. However, we must first make that data 2024 * stream in BATON catch up tho the position LEN already delivered from the 2025 * fulltext cache. Also, we need to store the reconstructed fulltext if we 2026 * want to cache it at the end. 2027 */ 2028static svn_error_t * 2029skip_contents(struct rep_read_baton *baton, 2030 svn_filesize_t len) 2031{ 2032 svn_error_t *err = SVN_NO_ERROR; 2033 2034 /* Do we want to cache the reconstructed fulltext? */ 2035 if (SVN_IS_VALID_REVNUM(baton->fulltext_cache_key.revision)) 2036 { 2037 char *buffer; 2038 svn_filesize_t to_alloc = MAX(len, baton->len); 2039 2040 /* This should only be happening if BATON->LEN and LEN are 2041 * cacheable, implying they fit into memory. */ 2042 SVN_ERR_ASSERT((apr_size_t)to_alloc == to_alloc); 2043 2044 /* Allocate the fulltext buffer. */ 2045 baton->current_fulltext = svn_stringbuf_create_ensure( 2046 optimimal_allocation_size((apr_size_t)to_alloc), 2047 baton->filehandle_pool); 2048 2049 /* Read LEN bytes from the window stream and store the data 2050 * in the fulltext buffer (will be filled by further reads later). */ 2051 baton->current_fulltext->len = (apr_size_t)len; 2052 baton->current_fulltext->data[(apr_size_t)len] = 0; 2053 2054 buffer = baton->current_fulltext->data; 2055 while (len > 0 && !err) 2056 { 2057 apr_size_t to_read = (apr_size_t)len; 2058 err = get_contents_from_windows(baton, buffer, &to_read); 2059 len -= to_read; 2060 buffer += to_read; 2061 } 2062 2063 /* Make the MD5 calculation catch up with the data delivered 2064 * (we did not run MD5 on the data that we took from the cache). */ 2065 if (!err) 2066 { 2067 SVN_ERR(svn_checksum_update(baton->md5_checksum_ctx, 2068 baton->current_fulltext->data, 2069 baton->current_fulltext->len)); 2070 baton->off += baton->current_fulltext->len; 2071 } 2072 } 2073 else if (len > 0) 2074 { 2075 /* Simply drain LEN bytes from the window stream. */ 2076 apr_pool_t *subpool = svn_pool_create(baton->pool); 2077 char *buffer = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE); 2078 2079 while (len > 0 && !err) 2080 { 2081 apr_size_t to_read = len > SVN__STREAM_CHUNK_SIZE 2082 ? SVN__STREAM_CHUNK_SIZE 2083 : (apr_size_t)len; 2084 2085 err = get_contents_from_windows(baton, buffer, &to_read); 2086 len -= to_read; 2087 2088 /* Make the MD5 calculation catch up with the data delivered 2089 * (we did not run MD5 on the data that we took from the cache). */ 2090 if (!err) 2091 { 2092 SVN_ERR(svn_checksum_update(baton->md5_checksum_ctx, 2093 buffer, to_read)); 2094 baton->off += to_read; 2095 } 2096 } 2097 2098 svn_pool_destroy(subpool); 2099 } 2100 2101 return svn_error_trace(err); 2102} 2103 2104/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the 2105 representation and store them in *BUF. Sum as we read and verify 2106 the MD5 sum at the end. This is a READ_FULL_FN for svn_stream_t. */ 2107static svn_error_t * 2108rep_read_contents(void *baton, 2109 char *buf, 2110 apr_size_t *len) 2111{ 2112 struct rep_read_baton *rb = baton; 2113 apr_size_t len_requested = *len; 2114 2115 /* Get data from the fulltext cache for as long as we can. */ 2116 if (rb->fulltext_cache) 2117 { 2118 svn_boolean_t cached; 2119 SVN_ERR(get_contents_from_fulltext(&cached, rb, buf, len)); 2120 if (cached) 2121 return SVN_NO_ERROR; 2122 2123 /* Cache miss. From now on, we will never read from the fulltext 2124 * cache for this representation anymore. */ 2125 rb->fulltext_cache = NULL; 2126 } 2127 2128 /* No fulltext cache to help us. We must read from the window stream. */ 2129 if (!rb->rs_list) 2130 { 2131 /* Window stream not initialized, yet. Do it now. */ 2132 rb->len = rb->rep.expanded_size; 2133 SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window, 2134 &rb->src_state, rb->fs, &rb->rep, 2135 rb->filehandle_pool)); 2136 2137 /* In case we did read from the fulltext cache before, make the 2138 * window stream catch up. Also, initialize the fulltext buffer 2139 * if we want to cache the fulltext at the end. */ 2140 SVN_ERR(skip_contents(rb, rb->fulltext_delivered)); 2141 } 2142 2143 /* Get the next block of data. 2144 * Keep in mind that the representation might be empty and leave us 2145 * already positioned at the end of the rep. */ 2146 if (rb->off == rb->len) 2147 *len = 0; 2148 else 2149 SVN_ERR(get_contents_from_windows(rb, buf, len)); 2150 2151 if (rb->current_fulltext) 2152 svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); 2153 2154 /* This is a FULL_READ_FN so a short read implies EOF and we can 2155 verify the length. */ 2156 rb->off += *len; 2157 if (*len < len_requested && rb->off != rb->len) 2158 { 2159 /* A warning rather than an error to allow the data to be 2160 retrieved when the length is wrong but the data is 2161 present, i.e. if repository corruption has stored the wrong 2162 expanded length. */ 2163 svn_error_t *err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2164 _("Length mismatch while reading representation:" 2165 " expected %s," 2166 " got %s"), 2167 apr_psprintf(rb->pool, "%" SVN_FILESIZE_T_FMT, 2168 rb->len), 2169 apr_psprintf(rb->pool, "%" SVN_FILESIZE_T_FMT, 2170 rb->off)); 2171 2172 rb->fs->warning(rb->fs->warning_baton, err); 2173 svn_error_clear(err); 2174 } 2175 2176 /* Perform checksumming. We want to check the checksum as soon as 2177 the last byte of data is read, in case the caller never performs 2178 a short read, but we don't want to finalize the MD5 context 2179 twice. */ 2180 if (!rb->checksum_finalized) 2181 { 2182 SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len)); 2183 if (rb->off == rb->len) 2184 { 2185 svn_checksum_t *md5_checksum; 2186 svn_checksum_t expected; 2187 expected.kind = svn_checksum_md5; 2188 expected.digest = rb->md5_digest; 2189 2190 rb->checksum_finalized = TRUE; 2191 SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx, 2192 rb->pool)); 2193 if (!svn_checksum_match(md5_checksum, &expected)) 2194 return svn_error_create(SVN_ERR_FS_CORRUPT, 2195 svn_checksum_mismatch_err(&expected, md5_checksum, 2196 rb->pool, 2197 _("Checksum mismatch while reading representation")), 2198 NULL); 2199 } 2200 } 2201 2202 if (rb->off == rb->len && rb->current_fulltext) 2203 { 2204 fs_fs_data_t *ffd = rb->fs->fsap_data; 2205 SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key, 2206 rb->current_fulltext, rb->pool)); 2207 rb->current_fulltext = NULL; 2208 } 2209 2210 return SVN_NO_ERROR; 2211} 2212 2213svn_error_t * 2214svn_fs_fs__get_contents(svn_stream_t **contents_p, 2215 svn_fs_t *fs, 2216 representation_t *rep, 2217 svn_boolean_t cache_fulltext, 2218 apr_pool_t *pool) 2219{ 2220 if (! rep) 2221 { 2222 *contents_p = svn_stream_empty(pool); 2223 } 2224 else 2225 { 2226 fs_fs_data_t *ffd = fs->fsap_data; 2227 struct rep_read_baton *rb; 2228 2229 pair_cache_key_t fulltext_cache_key = { 0 }; 2230 fulltext_cache_key.revision = rep->revision; 2231 fulltext_cache_key.second = rep->item_index; 2232 2233 /* Initialize the reader baton. Some members may added lazily 2234 * while reading from the stream */ 2235 SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool)); 2236 2237 /* Make the stream attempt fulltext cache lookups if the fulltext 2238 * is cacheable. If it is not, then also don't try to buffer and 2239 * cache it. */ 2240 if (ffd->fulltext_cache && cache_fulltext 2241 && SVN_IS_VALID_REVNUM(rep->revision) 2242 && fulltext_size_is_cachable(ffd, rep->expanded_size)) 2243 { 2244 rb->fulltext_cache = ffd->fulltext_cache; 2245 } 2246 else 2247 { 2248 /* This will also prevent the reconstructed fulltext from being 2249 put into the cache. */ 2250 rb->fulltext_cache_key.revision = SVN_INVALID_REVNUM; 2251 } 2252 2253 *contents_p = svn_stream_create(rb, pool); 2254 svn_stream_set_read2(*contents_p, NULL /* only full read support */, 2255 rep_read_contents); 2256 svn_stream_set_close(*contents_p, rep_read_contents_close); 2257 } 2258 2259 return SVN_NO_ERROR; 2260} 2261 2262svn_error_t * 2263svn_fs_fs__get_contents_from_file(svn_stream_t **contents_p, 2264 svn_fs_t *fs, 2265 representation_t *rep, 2266 apr_file_t *file, 2267 apr_off_t offset, 2268 apr_pool_t *pool) 2269{ 2270 struct rep_read_baton *rb; 2271 pair_cache_key_t fulltext_cache_key = { SVN_INVALID_REVNUM, 0 }; 2272 rep_state_t *rs = apr_pcalloc(pool, sizeof(*rs)); 2273 svn_fs_fs__rep_header_t *rh; 2274 2275 /* Initialize the reader baton. Some members may added lazily 2276 * while reading from the stream. */ 2277 SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool)); 2278 2279 /* Continue constructing RS. Leave caches as NULL. */ 2280 rs->size = rep->size; 2281 rs->revision = SVN_INVALID_REVNUM; 2282 rs->item_index = 0; 2283 rs->ver = -1; 2284 rs->start = -1; 2285 2286 /* Provide just enough file access info to allow for a basic read from 2287 * FILE but leave all index / footer info with empty values b/c FILE 2288 * probably is not a complete revision file. */ 2289 rs->sfile = apr_pcalloc(pool, sizeof(*rs->sfile)); 2290 rs->sfile->revision = rep->revision; 2291 rs->sfile->pool = pool; 2292 rs->sfile->fs = fs; 2293 rs->sfile->rfile = apr_pcalloc(pool, sizeof(*rs->sfile->rfile)); 2294 rs->sfile->rfile->start_revision = SVN_INVALID_REVNUM; 2295 rs->sfile->rfile->file = file; 2296 rs->sfile->rfile->stream = svn_stream_from_aprfile2(file, TRUE, pool); 2297 2298 /* Read the rep header. */ 2299 SVN_ERR(aligned_seek(fs, file, NULL, offset, pool)); 2300 SVN_ERR(svn_fs_fs__read_rep_header(&rh, rs->sfile->rfile->stream, 2301 pool, pool)); 2302 SVN_ERR(get_file_offset(&rs->start, rs, pool)); 2303 rs->header_size = rh->header_size; 2304 2305 /* Log the access. */ 2306 SVN_ERR(dbg_log_access(fs, SVN_INVALID_REVNUM, 0, rh, 2307 SVN_FS_FS__ITEM_TYPE_ANY_REP, pool)); 2308 2309 /* Build the representation list (delta chain). */ 2310 if (rh->type == svn_fs_fs__rep_plain) 2311 { 2312 rb->rs_list = apr_array_make(pool, 0, sizeof(rep_state_t *)); 2313 rb->src_state = rs; 2314 } 2315 else if (rh->type == svn_fs_fs__rep_self_delta) 2316 { 2317 rb->rs_list = apr_array_make(pool, 1, sizeof(rep_state_t *)); 2318 APR_ARRAY_PUSH(rb->rs_list, rep_state_t *) = rs; 2319 rb->src_state = NULL; 2320 } 2321 else 2322 { 2323 representation_t next_rep = { 0 }; 2324 2325 /* skip "SVNx" diff marker */ 2326 rs->current = 4; 2327 2328 /* REP's base rep is inside a proper revision. 2329 * It can be reconstructed in the usual way. */ 2330 next_rep.revision = rh->base_revision; 2331 next_rep.item_index = rh->base_item_index; 2332 next_rep.size = rh->base_length; 2333 svn_fs_fs__id_txn_reset(&next_rep.txn_id); 2334 2335 SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window, 2336 &rb->src_state, rb->fs, &next_rep, 2337 rb->filehandle_pool)); 2338 2339 /* Insert the access to REP as the first element of the delta chain. */ 2340 SVN_ERR(svn_sort__array_insert2(rb->rs_list, &rs, 0)); 2341 } 2342 2343 /* Now, the baton is complete and we can assemble the stream around it. */ 2344 *contents_p = svn_stream_create(rb, pool); 2345 svn_stream_set_read2(*contents_p, NULL /* only full read support */, 2346 rep_read_contents); 2347 svn_stream_set_close(*contents_p, rep_read_contents_close); 2348 2349 return SVN_NO_ERROR; 2350} 2351 2352/* Baton for cache_access_wrapper. Wraps the original parameters of 2353 * svn_fs_fs__try_process_file_content(). 2354 */ 2355typedef struct cache_access_wrapper_baton_t 2356{ 2357 svn_fs_process_contents_func_t func; 2358 void* baton; 2359} cache_access_wrapper_baton_t; 2360 2361/* Wrapper to translate between svn_fs_process_contents_func_t and 2362 * svn_cache__partial_getter_func_t. 2363 */ 2364static svn_error_t * 2365cache_access_wrapper(void **out, 2366 const void *data, 2367 apr_size_t data_len, 2368 void *baton, 2369 apr_pool_t *pool) 2370{ 2371 cache_access_wrapper_baton_t *wrapper_baton = baton; 2372 2373 SVN_ERR(wrapper_baton->func((const unsigned char *)data, 2374 data_len - 1, /* cache adds terminating 0 */ 2375 wrapper_baton->baton, 2376 pool)); 2377 2378 /* non-NULL value to signal the calling cache that all went well */ 2379 *out = baton; 2380 2381 return SVN_NO_ERROR; 2382} 2383 2384svn_error_t * 2385svn_fs_fs__try_process_file_contents(svn_boolean_t *success, 2386 svn_fs_t *fs, 2387 node_revision_t *noderev, 2388 svn_fs_process_contents_func_t processor, 2389 void* baton, 2390 apr_pool_t *pool) 2391{ 2392 representation_t *rep = noderev->data_rep; 2393 if (rep) 2394 { 2395 fs_fs_data_t *ffd = fs->fsap_data; 2396 pair_cache_key_t fulltext_cache_key = { 0 }; 2397 2398 fulltext_cache_key.revision = rep->revision; 2399 fulltext_cache_key.second = rep->item_index; 2400 if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 2401 && fulltext_size_is_cachable(ffd, rep->expanded_size)) 2402 { 2403 cache_access_wrapper_baton_t wrapper_baton; 2404 void *dummy = NULL; 2405 2406 wrapper_baton.func = processor; 2407 wrapper_baton.baton = baton; 2408 return svn_cache__get_partial(&dummy, success, 2409 ffd->fulltext_cache, 2410 &fulltext_cache_key, 2411 cache_access_wrapper, 2412 &wrapper_baton, 2413 pool); 2414 } 2415 } 2416 2417 *success = FALSE; 2418 return SVN_NO_ERROR; 2419} 2420 2421 2422/* Baton used when reading delta windows. */ 2423struct delta_read_baton 2424{ 2425 rep_state_t *rs; 2426 unsigned char md5_digest[APR_MD5_DIGESTSIZE]; 2427}; 2428 2429/* This implements the svn_txdelta_next_window_fn_t interface. */ 2430static svn_error_t * 2431delta_read_next_window(svn_txdelta_window_t **window, void *baton, 2432 apr_pool_t *pool) 2433{ 2434 struct delta_read_baton *drb = baton; 2435 apr_pool_t *scratch_pool = svn_pool_create(pool); 2436 2437 *window = NULL; 2438 if (drb->rs->current < drb->rs->size) 2439 { 2440 SVN_ERR(read_delta_window(window, drb->rs->chunk_index, drb->rs, pool, 2441 scratch_pool)); 2442 drb->rs->chunk_index++; 2443 } 2444 2445 svn_pool_destroy(scratch_pool); 2446 2447 return SVN_NO_ERROR; 2448} 2449 2450/* This implements the svn_txdelta_md5_digest_fn_t interface. */ 2451static const unsigned char * 2452delta_read_md5_digest(void *baton) 2453{ 2454 struct delta_read_baton *drb = baton; 2455 return drb->md5_digest; 2456} 2457 2458/* Return a txdelta stream for on-disk representation REP_STATE 2459 * of TARGET. Allocate the result in POOL. 2460 */ 2461static svn_txdelta_stream_t * 2462get_storaged_delta_stream(rep_state_t *rep_state, 2463 node_revision_t *target, 2464 apr_pool_t *pool) 2465{ 2466 /* Create the delta read baton. */ 2467 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb)); 2468 drb->rs = rep_state; 2469 memcpy(drb->md5_digest, target->data_rep->md5_digest, 2470 sizeof(drb->md5_digest)); 2471 return svn_txdelta_stream_create(drb, delta_read_next_window, 2472 delta_read_md5_digest, pool); 2473} 2474 2475svn_error_t * 2476svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p, 2477 svn_fs_t *fs, 2478 node_revision_t *source, 2479 node_revision_t *target, 2480 apr_pool_t *pool) 2481{ 2482 svn_stream_t *source_stream, *target_stream; 2483 rep_state_t *rep_state; 2484 svn_fs_fs__rep_header_t *rep_header; 2485 fs_fs_data_t *ffd = fs->fsap_data; 2486 2487 /* Try a shortcut: if the target is stored as a delta against the source, 2488 then just use that delta. However, prefer using the fulltext cache 2489 whenever that is available. */ 2490 if (target->data_rep && (source || ! ffd->fulltext_cache)) 2491 { 2492 /* Read target's base rep if any. */ 2493 SVN_ERR(create_rep_state(&rep_state, &rep_header, NULL, 2494 target->data_rep, fs, pool, pool)); 2495 2496 if (source && source->data_rep && target->data_rep) 2497 { 2498 /* If that matches source, then use this delta as is. 2499 Note that we want an actual delta here. E.g. a self-delta would 2500 not be good enough. */ 2501 if (rep_header->type == svn_fs_fs__rep_delta 2502 && rep_header->base_revision == source->data_rep->revision 2503 && rep_header->base_item_index == source->data_rep->item_index) 2504 { 2505 *stream_p = get_storaged_delta_stream(rep_state, target, pool); 2506 return SVN_NO_ERROR; 2507 } 2508 } 2509 else if (!source) 2510 { 2511 /* We want a self-delta. There is a fair chance that TARGET got 2512 added in this revision and is already stored in the requested 2513 format. */ 2514 if (rep_header->type == svn_fs_fs__rep_self_delta) 2515 { 2516 *stream_p = get_storaged_delta_stream(rep_state, target, pool); 2517 return SVN_NO_ERROR; 2518 } 2519 } 2520 2521 /* Don't keep file handles open for longer than necessary. */ 2522 if (rep_state->sfile->rfile) 2523 { 2524 SVN_ERR(svn_fs_fs__close_revision_file(rep_state->sfile->rfile)); 2525 rep_state->sfile->rfile = NULL; 2526 } 2527 } 2528 2529 /* Read both fulltexts and construct a delta. */ 2530 if (source) 2531 SVN_ERR(svn_fs_fs__get_contents(&source_stream, fs, source->data_rep, 2532 TRUE, pool)); 2533 else 2534 source_stream = svn_stream_empty(pool); 2535 SVN_ERR(svn_fs_fs__get_contents(&target_stream, fs, target->data_rep, 2536 TRUE, pool)); 2537 2538 /* Because source and target stream will already verify their content, 2539 * there is no need to do this once more. In particular if the stream 2540 * content is being fetched from cache. */ 2541 svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool); 2542 2543 return SVN_NO_ERROR; 2544} 2545 2546/* Return TRUE when all svn_fs_dirent_t* in ENTRIES are already sorted 2547 by their respective name. */ 2548static svn_boolean_t 2549sorted(apr_array_header_t *entries) 2550{ 2551 int i; 2552 2553 const svn_fs_dirent_t * const *dirents = (const void *)entries->elts; 2554 for (i = 0; i < entries->nelts-1; ++i) 2555 if (strcmp(dirents[i]->name, dirents[i+1]->name) > 0) 2556 return FALSE; 2557 2558 return TRUE; 2559} 2560 2561/* Compare the names of the two dirents given in **A and **B. */ 2562static int 2563compare_dirents(const void *a, const void *b) 2564{ 2565 const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a); 2566 const svn_fs_dirent_t *rhs = *((const svn_fs_dirent_t * const *) b); 2567 2568 return strcmp(lhs->name, rhs->name); 2569} 2570 2571/* Compare the name of the dirents given in **A with the C string in *B. */ 2572static int 2573compare_dirent_name(const void *a, const void *b) 2574{ 2575 const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a); 2576 const char *rhs = b; 2577 2578 return strcmp(lhs->name, rhs); 2579} 2580 2581/* Into *ENTRIES_P, read all directories entries from the key-value text in 2582 * STREAM. If INCREMENTAL is TRUE, read until the end of the STREAM and 2583 * update the data. ID is provided for nicer error messages. 2584 */ 2585static svn_error_t * 2586read_dir_entries(apr_array_header_t **entries_p, 2587 svn_stream_t *stream, 2588 svn_boolean_t incremental, 2589 const svn_fs_id_t *id, 2590 apr_pool_t *result_pool, 2591 apr_pool_t *scratch_pool) 2592{ 2593 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2594 apr_hash_t *hash = NULL; 2595 const char *terminator = SVN_HASH_TERMINATOR; 2596 apr_array_header_t *entries = NULL; 2597 2598 if (incremental) 2599 hash = svn_hash__make(scratch_pool); 2600 else 2601 entries = apr_array_make(result_pool, 16, sizeof(svn_fs_dirent_t *)); 2602 2603 /* Read until the terminator (non-incremental) or the end of STREAM 2604 (incremental mode). In the latter mode, we use a temporary HASH 2605 to make updating and removing entries cheaper. */ 2606 while (1) 2607 { 2608 svn_hash__entry_t entry; 2609 svn_fs_dirent_t *dirent; 2610 char *str; 2611 2612 svn_pool_clear(iterpool); 2613 SVN_ERR_W(svn_hash__read_entry(&entry, stream, terminator, 2614 incremental, iterpool), 2615 apr_psprintf(iterpool, 2616 _("Directory representation corrupt in '%s'"), 2617 svn_fs_fs__id_unparse(id, scratch_pool)->data)); 2618 2619 /* End of directory? */ 2620 if (entry.key == NULL) 2621 { 2622 /* In incremental mode, we skip the terminator and read the 2623 increments following it until the end of the stream. */ 2624 if (incremental && terminator) 2625 terminator = NULL; 2626 else 2627 break; 2628 } 2629 2630 /* Deleted entry? */ 2631 if (entry.val == NULL) 2632 { 2633 /* We must be in incremental mode */ 2634 assert(hash); 2635 apr_hash_set(hash, entry.key, entry.keylen, NULL); 2636 continue; 2637 } 2638 2639 /* Add a new directory entry. */ 2640 dirent = apr_pcalloc(result_pool, sizeof(*dirent)); 2641 dirent->name = apr_pstrmemdup(result_pool, entry.key, entry.keylen); 2642 2643 str = svn_cstring_tokenize(" ", &entry.val); 2644 if (str == NULL) 2645 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2646 _("Directory entry corrupt in '%s'"), 2647 svn_fs_fs__id_unparse(id, scratch_pool)->data); 2648 2649 if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0) 2650 { 2651 dirent->kind = svn_node_file; 2652 } 2653 else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0) 2654 { 2655 dirent->kind = svn_node_dir; 2656 } 2657 else 2658 { 2659 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2660 _("Directory entry corrupt in '%s'"), 2661 svn_fs_fs__id_unparse(id, scratch_pool)->data); 2662 } 2663 2664 str = svn_cstring_tokenize(" ", &entry.val); 2665 if (str == NULL) 2666 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2667 _("Directory entry corrupt in '%s'"), 2668 svn_fs_fs__id_unparse(id, scratch_pool)->data); 2669 2670 SVN_ERR(svn_fs_fs__id_parse(&dirent->id, str, result_pool)); 2671 2672 /* In incremental mode, update the hash; otherwise, write to the 2673 * final array. Be sure to use hash keys that survive this iteration. 2674 */ 2675 if (incremental) 2676 apr_hash_set(hash, dirent->name, entry.keylen, dirent); 2677 else 2678 APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = dirent; 2679 } 2680 2681 /* Convert container to a sorted array. */ 2682 if (incremental) 2683 { 2684 apr_hash_index_t *hi; 2685 2686 entries = apr_array_make(result_pool, apr_hash_count(hash), 2687 sizeof(svn_fs_dirent_t *)); 2688 for (hi = apr_hash_first(iterpool, hash); hi; hi = apr_hash_next(hi)) 2689 APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = apr_hash_this_val(hi); 2690 } 2691 2692 if (!sorted(entries)) 2693 svn_sort__array(entries, compare_dirents); 2694 2695 svn_pool_destroy(iterpool); 2696 2697 *entries_p = entries; 2698 return SVN_NO_ERROR; 2699} 2700 2701/* For directory NODEREV in FS, return the *FILESIZE of its in-txn 2702 * representation. If the directory representation is comitted data, 2703 * set *FILESIZE to SVN_INVALID_FILESIZE. Use SCRATCH_POOL for temporaries. 2704 */ 2705static svn_error_t * 2706get_txn_dir_info(svn_filesize_t *filesize, 2707 svn_fs_t *fs, 2708 node_revision_t *noderev, 2709 apr_pool_t *scratch_pool) 2710{ 2711 if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id)) 2712 { 2713 const svn_io_dirent2_t *dirent; 2714 const char *filename; 2715 2716 filename = svn_fs_fs__path_txn_node_children(fs, noderev->id, 2717 scratch_pool); 2718 2719 SVN_ERR(svn_io_stat_dirent2(&dirent, filename, FALSE, FALSE, 2720 scratch_pool, scratch_pool)); 2721 *filesize = dirent->filesize; 2722 } 2723 else 2724 { 2725 *filesize = SVN_INVALID_FILESIZE; 2726 } 2727 2728 return SVN_NO_ERROR; 2729} 2730 2731/* Fetch the contents of a directory into DIR. Values are stored 2732 as filename to string mappings; further conversion is necessary to 2733 convert them into svn_fs_dirent_t values. */ 2734static svn_error_t * 2735get_dir_contents(svn_fs_fs__dir_data_t *dir, 2736 svn_fs_t *fs, 2737 node_revision_t *noderev, 2738 apr_pool_t *result_pool, 2739 apr_pool_t *scratch_pool) 2740{ 2741 svn_stream_t *contents; 2742 2743 /* Initialize the result. */ 2744 dir->txn_filesize = SVN_INVALID_FILESIZE; 2745 2746 /* Read dir contents - unless there is none in which case we are done. */ 2747 if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id)) 2748 { 2749 /* Get location & current size of the directory representation. */ 2750 const char *filename; 2751 apr_file_t *file; 2752 2753 filename = svn_fs_fs__path_txn_node_children(fs, noderev->id, 2754 scratch_pool); 2755 2756 /* The representation is mutable. Read the old directory 2757 contents from the mutable children file, followed by the 2758 changes we've made in this transaction. */ 2759 SVN_ERR(svn_io_file_open(&file, filename, APR_READ | APR_BUFFERED, 2760 APR_OS_DEFAULT, scratch_pool)); 2761 2762 /* Obtain txn children file size. */ 2763 SVN_ERR(svn_io_file_size_get(&dir->txn_filesize, file, scratch_pool)); 2764 2765 contents = svn_stream_from_aprfile2(file, FALSE, scratch_pool); 2766 SVN_ERR(read_dir_entries(&dir->entries, contents, TRUE, noderev->id, 2767 result_pool, scratch_pool)); 2768 SVN_ERR(svn_stream_close(contents)); 2769 } 2770 else if (noderev->data_rep) 2771 { 2772 /* Undeltify content before parsing it. Otherwise, we could only 2773 * parse it byte-by-byte. 2774 */ 2775 apr_size_t len = noderev->data_rep->expanded_size; 2776 svn_stringbuf_t *text; 2777 2778 /* The representation is immutable. Read it normally. */ 2779 SVN_ERR(svn_fs_fs__get_contents(&contents, fs, noderev->data_rep, 2780 FALSE, scratch_pool)); 2781 SVN_ERR(svn_stringbuf_from_stream(&text, contents, len, scratch_pool)); 2782 SVN_ERR(svn_stream_close(contents)); 2783 2784 /* de-serialize hash */ 2785 contents = svn_stream_from_stringbuf(text, scratch_pool); 2786 SVN_ERR(read_dir_entries(&dir->entries, contents, FALSE, noderev->id, 2787 result_pool, scratch_pool)); 2788 } 2789 else 2790 { 2791 dir->entries = apr_array_make(result_pool, 0, sizeof(svn_fs_dirent_t *)); 2792 } 2793 2794 return SVN_NO_ERROR; 2795} 2796 2797 2798/* Return the cache object in FS responsible to storing the directory the 2799 * NODEREV plus the corresponding *KEY. If no cache exists, return NULL. 2800 * PAIR_KEY must point to some key struct, which does not need to be 2801 * initialized. We use it to avoid dynamic allocation. 2802 */ 2803static svn_cache__t * 2804locate_dir_cache(svn_fs_t *fs, 2805 const void **key, 2806 pair_cache_key_t *pair_key, 2807 node_revision_t *noderev, 2808 apr_pool_t *pool) 2809{ 2810 fs_fs_data_t *ffd = fs->fsap_data; 2811 if (!noderev->data_rep) 2812 { 2813 /* no data rep -> empty directory. 2814 A NULL key causes a cache miss. */ 2815 *key = NULL; 2816 return ffd->dir_cache; 2817 } 2818 2819 if (svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id)) 2820 { 2821 /* data in txns requires the expensive fs_id-based addressing mode */ 2822 *key = svn_fs_fs__id_unparse(noderev->id, pool)->data; 2823 2824 return ffd->txn_dir_cache; 2825 } 2826 else 2827 { 2828 /* committed data can use simple rev,item pairs */ 2829 pair_key->revision = noderev->data_rep->revision; 2830 pair_key->second = noderev->data_rep->item_index; 2831 *key = pair_key; 2832 2833 return ffd->dir_cache; 2834 } 2835} 2836 2837svn_error_t * 2838svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p, 2839 svn_fs_t *fs, 2840 node_revision_t *noderev, 2841 apr_pool_t *result_pool, 2842 apr_pool_t *scratch_pool) 2843{ 2844 pair_cache_key_t pair_key = { 0 }; 2845 const void *key; 2846 svn_fs_fs__dir_data_t *dir; 2847 2848 /* find the cache we may use */ 2849 svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev, 2850 scratch_pool); 2851 if (cache) 2852 { 2853 svn_boolean_t found; 2854 2855 SVN_ERR(svn_cache__get((void **)&dir, &found, cache, key, 2856 result_pool)); 2857 if (found) 2858 { 2859 /* Verify that the cached dir info is not stale 2860 * (no-op for committed data). */ 2861 svn_filesize_t filesize; 2862 SVN_ERR(get_txn_dir_info(&filesize, fs, noderev, scratch_pool)); 2863 2864 if (filesize == dir->txn_filesize) 2865 { 2866 /* Still valid. Done. */ 2867 *entries_p = dir->entries; 2868 return SVN_NO_ERROR; 2869 } 2870 } 2871 } 2872 2873 /* Read in the directory contents. */ 2874 dir = apr_pcalloc(scratch_pool, sizeof(*dir)); 2875 SVN_ERR(get_dir_contents(dir, fs, noderev, result_pool, scratch_pool)); 2876 *entries_p = dir->entries; 2877 2878 /* Update the cache, if we are to use one. 2879 * 2880 * Don't even attempt to serialize very large directories; it would cause 2881 * an unnecessary memory allocation peak. 150 bytes/entry is about right. 2882 */ 2883 if (cache && svn_cache__is_cachable(cache, 150 * dir->entries->nelts)) 2884 SVN_ERR(svn_cache__set(cache, key, dir, scratch_pool)); 2885 2886 return SVN_NO_ERROR; 2887} 2888 2889svn_fs_dirent_t * 2890svn_fs_fs__find_dir_entry(apr_array_header_t *entries, 2891 const char *name, 2892 int *hint) 2893{ 2894 svn_fs_dirent_t **result 2895 = svn_sort__array_lookup(entries, name, hint, compare_dirent_name); 2896 return result ? *result : NULL; 2897} 2898 2899svn_error_t * 2900svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent, 2901 svn_fs_t *fs, 2902 node_revision_t *noderev, 2903 const char *name, 2904 apr_pool_t *result_pool, 2905 apr_pool_t *scratch_pool) 2906{ 2907 extract_dir_entry_baton_t baton; 2908 svn_boolean_t found = FALSE; 2909 2910 /* find the cache we may use */ 2911 pair_cache_key_t pair_key = { 0 }; 2912 const void *key; 2913 svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev, 2914 scratch_pool); 2915 if (cache) 2916 { 2917 svn_filesize_t filesize; 2918 SVN_ERR(get_txn_dir_info(&filesize, fs, noderev, scratch_pool)); 2919 2920 /* Cache lookup. */ 2921 baton.txn_filesize = filesize; 2922 baton.name = name; 2923 SVN_ERR(svn_cache__get_partial((void **)dirent, 2924 &found, 2925 cache, 2926 key, 2927 svn_fs_fs__extract_dir_entry, 2928 &baton, 2929 result_pool)); 2930 } 2931 2932 /* fetch data from disk if we did not find it in the cache */ 2933 if (! found || baton.out_of_date) 2934 { 2935 svn_fs_dirent_t *entry; 2936 svn_fs_dirent_t *entry_copy = NULL; 2937 svn_fs_fs__dir_data_t dir; 2938 2939 /* Read in the directory contents. */ 2940 SVN_ERR(get_dir_contents(&dir, fs, noderev, scratch_pool, 2941 scratch_pool)); 2942 2943 /* Update the cache, if we are to use one. 2944 * 2945 * Don't even attempt to serialize very large directories; it would 2946 * cause an unnecessary memory allocation peak. 150 bytes / entry is 2947 * about right. */ 2948 if (cache && svn_cache__is_cachable(cache, 150 * dir.entries->nelts)) 2949 SVN_ERR(svn_cache__set(cache, key, &dir, scratch_pool)); 2950 2951 /* find desired entry and return a copy in POOL, if found */ 2952 entry = svn_fs_fs__find_dir_entry(dir.entries, name, NULL); 2953 if (entry) 2954 { 2955 entry_copy = apr_palloc(result_pool, sizeof(*entry_copy)); 2956 entry_copy->name = apr_pstrdup(result_pool, entry->name); 2957 entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool); 2958 entry_copy->kind = entry->kind; 2959 } 2960 2961 *dirent = entry_copy; 2962 } 2963 2964 return SVN_NO_ERROR; 2965} 2966 2967svn_error_t * 2968svn_fs_fs__get_proplist(apr_hash_t **proplist_p, 2969 svn_fs_t *fs, 2970 node_revision_t *noderev, 2971 apr_pool_t *pool) 2972{ 2973 apr_hash_t *proplist; 2974 svn_stream_t *stream; 2975 2976 if (noderev->prop_rep && svn_fs_fs__id_txn_used(&noderev->prop_rep->txn_id)) 2977 { 2978 svn_error_t *err; 2979 const char *filename 2980 = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool); 2981 proplist = apr_hash_make(pool); 2982 2983 SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool)); 2984 err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool); 2985 if (err) 2986 { 2987 svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool); 2988 2989 err = svn_error_compose_create(err, svn_stream_close(stream)); 2990 return svn_error_quick_wrapf(err, 2991 _("malformed property list for node-revision '%s' in '%s'"), 2992 id_str->data, filename); 2993 } 2994 SVN_ERR(svn_stream_close(stream)); 2995 } 2996 else if (noderev->prop_rep) 2997 { 2998 svn_error_t *err; 2999 fs_fs_data_t *ffd = fs->fsap_data; 3000 representation_t *rep = noderev->prop_rep; 3001 pair_cache_key_t key = { 0 }; 3002 3003 key.revision = rep->revision; 3004 key.second = rep->item_index; 3005 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 3006 { 3007 svn_boolean_t is_cached; 3008 SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 3009 ffd->properties_cache, &key, pool)); 3010 if (is_cached) 3011 return SVN_NO_ERROR; 3012 } 3013 3014 proplist = apr_hash_make(pool); 3015 SVN_ERR(svn_fs_fs__get_contents(&stream, fs, noderev->prop_rep, FALSE, 3016 pool)); 3017 err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool); 3018 if (err) 3019 { 3020 svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool); 3021 3022 err = svn_error_compose_create(err, svn_stream_close(stream)); 3023 return svn_error_quick_wrapf(err, 3024 _("malformed property list for node-revision '%s'"), 3025 id_str->data); 3026 } 3027 SVN_ERR(svn_stream_close(stream)); 3028 3029 if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 3030 SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool)); 3031 } 3032 else 3033 { 3034 /* return an empty prop list if the node doesn't have any props */ 3035 proplist = apr_hash_make(pool); 3036 } 3037 3038 *proplist_p = proplist; 3039 3040 return SVN_NO_ERROR; 3041} 3042 3043svn_error_t * 3044svn_fs_fs__create_changes_context(svn_fs_fs__changes_context_t **context, 3045 svn_fs_t *fs, 3046 svn_revnum_t rev, 3047 apr_pool_t *result_pool) 3048{ 3049 svn_fs_fs__changes_context_t *result = apr_pcalloc(result_pool, 3050 sizeof(*result)); 3051 result->fs = fs; 3052 result->revision = rev; 3053 result->rev_file_pool = result_pool; 3054 3055 *context = result; 3056 return SVN_NO_ERROR; 3057} 3058 3059svn_error_t * 3060svn_fs_fs__get_changes(apr_array_header_t **changes, 3061 svn_fs_fs__changes_context_t *context, 3062 apr_pool_t *result_pool, 3063 apr_pool_t *scratch_pool) 3064{ 3065 apr_off_t item_index = SVN_FS_FS__ITEM_INDEX_CHANGES; 3066 svn_boolean_t found; 3067 fs_fs_data_t *ffd = context->fs->fsap_data; 3068 svn_fs_fs__changes_list_t *changes_list; 3069 3070 pair_cache_key_t key; 3071 key.revision = context->revision; 3072 key.second = context->next; 3073 3074 /* try cache lookup first */ 3075 3076 if (ffd->changes_cache) 3077 { 3078 SVN_ERR(svn_cache__get((void **)&changes_list, &found, 3079 ffd->changes_cache, &key, result_pool)); 3080 } 3081 else 3082 { 3083 found = FALSE; 3084 } 3085 3086 if (!found) 3087 { 3088 /* read changes from revision file */ 3089 3090 if (!context->revision_file) 3091 { 3092 SVN_ERR(svn_fs_fs__ensure_revision_exists(context->revision, 3093 context->fs, 3094 scratch_pool)); 3095 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&context->revision_file, 3096 context->fs, 3097 context->revision, 3098 context->rev_file_pool, 3099 scratch_pool)); 3100 } 3101 3102 if (use_block_read(context->fs)) 3103 { 3104 /* 'block-read' will probably populate the cache with the data 3105 * that we want. However, we won't want to force it to process 3106 * very large change lists as part of this prefetching mechanism. 3107 * Those would be better handled by the iterative code below. */ 3108 SVN_ERR(block_read(NULL, context->fs, 3109 context->revision, SVN_FS_FS__ITEM_INDEX_CHANGES, 3110 context->revision_file, scratch_pool, 3111 scratch_pool)); 3112 3113 /* This may succeed now ... */ 3114 SVN_ERR(svn_cache__get((void **)&changes_list, &found, 3115 ffd->changes_cache, &key, result_pool)); 3116 } 3117 3118 /* If we still have no data, read it here. */ 3119 if (!found) 3120 { 3121 apr_off_t changes_offset; 3122 3123 /* Addressing is very different for old formats 3124 * (needs to read the revision trailer). */ 3125 if (svn_fs_fs__use_log_addressing(context->fs)) 3126 { 3127 SVN_ERR(svn_fs_fs__item_offset(&changes_offset, context->fs, 3128 context->revision_file, 3129 context->revision, NULL, 3130 SVN_FS_FS__ITEM_INDEX_CHANGES, 3131 scratch_pool)); 3132 } 3133 else 3134 { 3135 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, 3136 context->revision_file, 3137 context->fs, context->revision, 3138 scratch_pool)); 3139 3140 /* This variable will be used for debug logging only. */ 3141 item_index = changes_offset; 3142 } 3143 3144 /* Actual reading and parsing are the same, though. */ 3145 SVN_ERR(aligned_seek(context->fs, context->revision_file->file, 3146 NULL, changes_offset + context->next_offset, 3147 scratch_pool)); 3148 3149 SVN_ERR(svn_fs_fs__read_changes(changes, 3150 context->revision_file->stream, 3151 SVN_FS_FS__CHANGES_BLOCK_SIZE, 3152 result_pool, scratch_pool)); 3153 3154 /* Construct the info object for the entries block we just read. */ 3155 changes_list = apr_pcalloc(scratch_pool, sizeof(*changes_list)); 3156 SVN_ERR(svn_io_file_get_offset(&changes_list->end_offset, 3157 context->revision_file->file, 3158 scratch_pool)); 3159 changes_list->end_offset -= changes_offset; 3160 changes_list->start_offset = context->next_offset; 3161 changes_list->count = (*changes)->nelts; 3162 changes_list->changes = (change_t **)(*changes)->elts; 3163 changes_list->eol = changes_list->count < SVN_FS_FS__CHANGES_BLOCK_SIZE; 3164 3165 /* cache for future reference */ 3166 3167 if (ffd->changes_cache) 3168 SVN_ERR(svn_cache__set(ffd->changes_cache, &key, changes_list, 3169 scratch_pool)); 3170 } 3171 } 3172 3173 if (found) 3174 { 3175 /* Return the block as a "proper" APR array. */ 3176 (*changes) = apr_array_make(result_pool, 0, sizeof(void *)); 3177 (*changes)->elts = (char *)changes_list->changes; 3178 (*changes)->nelts = changes_list->count; 3179 (*changes)->nalloc = changes_list->count; 3180 } 3181 3182 /* Where to look next - if there is more data. */ 3183 context->next += (*changes)->nelts; 3184 context->next_offset = changes_list->end_offset; 3185 context->eol = changes_list->eol; 3186 3187 /* Close the revision file after we read all data. */ 3188 if (context->eol && context->revision_file) 3189 { 3190 SVN_ERR(svn_fs_fs__close_revision_file(context->revision_file)); 3191 context->revision_file = NULL; 3192 } 3193 3194 SVN_ERR(dbg_log_access(context->fs, context->revision, item_index, *changes, 3195 SVN_FS_FS__ITEM_TYPE_CHANGES, scratch_pool)); 3196 3197 return SVN_NO_ERROR; 3198} 3199 3200/* Inialize the representation read state RS for the given REP_HEADER and 3201 * p2l index ENTRY. If not NULL, assign FILE and STREAM to RS. 3202 * Use RESULT_POOL for allocations. 3203 */ 3204static svn_error_t * 3205init_rep_state(rep_state_t *rs, 3206 svn_fs_fs__rep_header_t *rep_header, 3207 svn_fs_t *fs, 3208 svn_fs_fs__revision_file_t *file, 3209 svn_fs_fs__p2l_entry_t* entry, 3210 apr_pool_t *result_pool) 3211{ 3212 fs_fs_data_t *ffd = fs->fsap_data; 3213 shared_file_t *shared_file = apr_pcalloc(result_pool, sizeof(*shared_file)); 3214 3215 /* this function does not apply to representation containers */ 3216 SVN_ERR_ASSERT(entry->type >= SVN_FS_FS__ITEM_TYPE_FILE_REP 3217 && entry->type <= SVN_FS_FS__ITEM_TYPE_DIR_PROPS); 3218 3219 shared_file->rfile = file; 3220 shared_file->fs = fs; 3221 shared_file->revision = entry->item.revision; 3222 shared_file->pool = result_pool; 3223 3224 rs->sfile = shared_file; 3225 rs->revision = entry->item.revision; 3226 rs->item_index = entry->item.number; 3227 rs->header_size = rep_header->header_size; 3228 rs->start = entry->offset + rs->header_size; 3229 rs->current = rep_header->type == svn_fs_fs__rep_plain ? 0 : 4; 3230 rs->size = entry->size - rep_header->header_size - 7; 3231 rs->ver = -1; 3232 rs->chunk_index = 0; 3233 rs->raw_window_cache = ffd->raw_window_cache; 3234 rs->window_cache = ffd->txdelta_window_cache; 3235 rs->combined_cache = ffd->combined_window_cache; 3236 3237 return SVN_NO_ERROR; 3238} 3239 3240/* Implement svn_cache__partial_getter_func_t for txdelta windows. 3241 * Instead of the whole window data, return only END_OFFSET member. 3242 */ 3243static svn_error_t * 3244get_txdelta_window_end(void **out, 3245 const void *data, 3246 apr_size_t data_len, 3247 void *baton, 3248 apr_pool_t *result_pool) 3249{ 3250 const svn_fs_fs__txdelta_cached_window_t *window 3251 = (const svn_fs_fs__txdelta_cached_window_t *)data; 3252 *(apr_off_t*)out = window->end_offset; 3253 3254 return SVN_NO_ERROR; 3255} 3256 3257/* Implement svn_cache__partial_getter_func_t for raw windows. 3258 * Instead of the whole window data, return only END_OFFSET member. 3259 */ 3260static svn_error_t * 3261get_raw_window_end(void **out, 3262 const void *data, 3263 apr_size_t data_len, 3264 void *baton, 3265 apr_pool_t *result_pool) 3266{ 3267 const svn_fs_fs__raw_cached_window_t *window 3268 = (const svn_fs_fs__raw_cached_window_t *)data; 3269 *(apr_off_t*)out = window->end_offset; 3270 3271 return SVN_NO_ERROR; 3272} 3273 3274/* Walk through all windows in the representation addressed by RS in FS 3275 * (excluding the delta bases) and put those not already cached into the 3276 * window caches. If MAX_OFFSET is not -1, don't read windows that start 3277 * at or beyond that offset. Use POOL for temporary allocations. 3278 * 3279 * This function requires RS->RAW_WINDOW_CACHE and RS->WINDOW_CACHE to 3280 * be non-NULL. 3281 */ 3282static svn_error_t * 3283cache_windows(svn_fs_t *fs, 3284 rep_state_t *rs, 3285 apr_off_t max_offset, 3286 apr_pool_t *pool) 3287{ 3288 apr_pool_t *iterpool = svn_pool_create(pool); 3289 3290 SVN_ERR(auto_read_diff_version(rs, iterpool)); 3291 3292 while (rs->current < rs->size) 3293 { 3294 apr_off_t end_offset; 3295 svn_boolean_t found = FALSE; 3296 window_cache_key_t key = { 0 }; 3297 3298 svn_pool_clear(iterpool); 3299 3300 if (max_offset != -1 && rs->start + rs->current >= max_offset) 3301 { 3302 svn_pool_destroy(iterpool); 3303 return SVN_NO_ERROR; 3304 } 3305 3306 /* We don't need to read the data again if it is already in cache. 3307 * It might be cached as either raw or parsed window. 3308 */ 3309 SVN_ERR(svn_cache__get_partial((void **) &end_offset, &found, 3310 rs->raw_window_cache, 3311 get_window_key(&key, rs), 3312 get_raw_window_end, NULL, 3313 iterpool)); 3314 if (! found) 3315 SVN_ERR(svn_cache__get_partial((void **) &end_offset, &found, 3316 rs->window_cache, &key, 3317 get_txdelta_window_end, NULL, 3318 iterpool)); 3319 3320 if (found) 3321 { 3322 rs->current = end_offset; 3323 } 3324 else 3325 { 3326 /* Read, decode and cache the window. */ 3327 svn_fs_fs__raw_cached_window_t window; 3328 apr_off_t start_offset = rs->start + rs->current; 3329 apr_size_t window_len; 3330 char *buf; 3331 3332 /* navigate to the current window */ 3333 SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, iterpool)); 3334 SVN_ERR(svn_txdelta__read_raw_window_len(&window_len, 3335 rs->sfile->rfile->stream, 3336 iterpool)); 3337 3338 /* Read the raw window. */ 3339 buf = apr_palloc(iterpool, window_len + 1); 3340 SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, iterpool)); 3341 SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, buf, 3342 window_len, NULL, NULL, iterpool)); 3343 buf[window_len] = 0; 3344 3345 /* update relative offset in representation */ 3346 rs->current += window_len; 3347 3348 /* Construct the cachable raw window object. */ 3349 window.end_offset = rs->current; 3350 window.window.len = window_len; 3351 window.window.data = buf; 3352 window.ver = rs->ver; 3353 3354 /* cache the window now */ 3355 SVN_ERR(svn_cache__set(rs->raw_window_cache, &key, &window, 3356 iterpool)); 3357 } 3358 3359 if (rs->current > rs->size) 3360 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 3361 _("Reading one svndiff window read beyond " 3362 "the end of the representation")); 3363 3364 rs->chunk_index++; 3365 } 3366 3367 svn_pool_destroy(iterpool); 3368 return SVN_NO_ERROR; 3369} 3370 3371/* Read all txdelta / plain windows following REP_HEADER in FS as described 3372 * by ENTRY. Read the data from the already open FILE and the wrapping 3373 * STREAM object. If MAX_OFFSET is not -1, don't read windows that start 3374 * at or beyond that offset. Use SCRATCH_POOL for temporary allocations. 3375 * If caching is not enabled, this is a no-op. 3376 */ 3377static svn_error_t * 3378block_read_windows(svn_fs_fs__rep_header_t *rep_header, 3379 svn_fs_t *fs, 3380 svn_fs_fs__revision_file_t *rev_file, 3381 svn_fs_fs__p2l_entry_t* entry, 3382 apr_off_t max_offset, 3383 apr_pool_t *result_pool, 3384 apr_pool_t *scratch_pool) 3385{ 3386 fs_fs_data_t *ffd = fs->fsap_data; 3387 rep_state_t rs = { 0 }; 3388 apr_off_t offset; 3389 window_cache_key_t key = { 0 }; 3390 3391 if ( (rep_header->type != svn_fs_fs__rep_plain 3392 && (!ffd->txdelta_window_cache || !ffd->raw_window_cache)) 3393 || (rep_header->type == svn_fs_fs__rep_plain 3394 && !ffd->combined_window_cache)) 3395 return SVN_NO_ERROR; 3396 3397 SVN_ERR(init_rep_state(&rs, rep_header, fs, rev_file, entry, 3398 result_pool)); 3399 3400 /* RS->FILE may be shared between RS instances -> make sure we point 3401 * to the right data. */ 3402 offset = rs.start + rs.current; 3403 if (rep_header->type == svn_fs_fs__rep_plain) 3404 { 3405 svn_stringbuf_t *plaintext; 3406 svn_boolean_t is_cached; 3407 3408 /* already in cache? */ 3409 SVN_ERR(svn_cache__has_key(&is_cached, rs.combined_cache, 3410 get_window_key(&key, &rs), 3411 scratch_pool)); 3412 if (is_cached) 3413 return SVN_NO_ERROR; 3414 3415 /* for larger reps, the header may have crossed a block boundary. 3416 * make sure we still read blocks properly aligned, i.e. don't use 3417 * plain seek here. */ 3418 SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, scratch_pool)); 3419 3420 plaintext = svn_stringbuf_create_ensure(rs.size, result_pool); 3421 SVN_ERR(svn_io_file_read_full2(rev_file->file, plaintext->data, 3422 rs.size, &plaintext->len, NULL, 3423 result_pool)); 3424 plaintext->data[plaintext->len] = 0; 3425 rs.current += rs.size; 3426 3427 SVN_ERR(set_cached_combined_window(plaintext, &rs, scratch_pool)); 3428 } 3429 else 3430 { 3431 SVN_ERR(cache_windows(fs, &rs, max_offset, scratch_pool)); 3432 } 3433 3434 return SVN_NO_ERROR; 3435} 3436 3437/* Try to get the representation header identified by KEY from FS's cache. 3438 * If it has not been cached, read it from the current position in STREAM 3439 * and put it into the cache (if caching has been enabled for rep headers). 3440 * Return the result in *REP_HEADER. Use POOL for allocations. 3441 */ 3442static svn_error_t * 3443read_rep_header(svn_fs_fs__rep_header_t **rep_header, 3444 svn_fs_t *fs, 3445 svn_stream_t *stream, 3446 pair_cache_key_t *key, 3447 apr_pool_t *result_pool, 3448 apr_pool_t *scratch_pool) 3449{ 3450 fs_fs_data_t *ffd = fs->fsap_data; 3451 svn_boolean_t is_cached = FALSE; 3452 3453 if (ffd->rep_header_cache) 3454 { 3455 SVN_ERR(svn_cache__get((void**)rep_header, &is_cached, 3456 ffd->rep_header_cache, key, 3457 result_pool)); 3458 if (is_cached) 3459 return SVN_NO_ERROR; 3460 } 3461 3462 SVN_ERR(svn_fs_fs__read_rep_header(rep_header, stream, result_pool, 3463 scratch_pool)); 3464 3465 if (ffd->rep_header_cache) 3466 SVN_ERR(svn_cache__set(ffd->rep_header_cache, key, *rep_header, 3467 scratch_pool)); 3468 3469 return SVN_NO_ERROR; 3470} 3471 3472/* Fetch the representation data (header, txdelta / plain windows) 3473 * addressed by ENTRY->ITEM in FS and cache it if caches are enabled. 3474 * Read the data from REV_FILE. If MAX_OFFSET is not -1, don't read 3475 * windows that start at or beyond that offset. 3476 * Use SCRATCH_POOL for temporary allocations. 3477 */ 3478static svn_error_t * 3479block_read_contents(svn_fs_t *fs, 3480 svn_fs_fs__revision_file_t *rev_file, 3481 svn_fs_fs__p2l_entry_t* entry, 3482 apr_off_t max_offset, 3483 apr_pool_t *scratch_pool) 3484{ 3485 pair_cache_key_t header_key = { 0 }; 3486 svn_fs_fs__rep_header_t *rep_header; 3487 3488 header_key.revision = (apr_int32_t)entry->item.revision; 3489 header_key.second = entry->item.number; 3490 3491 SVN_ERR(read_rep_header(&rep_header, fs, rev_file->stream, &header_key, 3492 scratch_pool, scratch_pool)); 3493 SVN_ERR(block_read_windows(rep_header, fs, rev_file, entry, max_offset, 3494 scratch_pool, scratch_pool)); 3495 3496 return SVN_NO_ERROR; 3497} 3498 3499/* For the given REV_FILE in FS, in *STREAM return a stream covering the 3500 * item specified by ENTRY. Also, verify the item's content by low-level 3501 * checksum. Allocate the result in POOL. 3502 */ 3503static svn_error_t * 3504read_item(svn_stream_t **stream, 3505 svn_fs_t *fs, 3506 svn_fs_fs__revision_file_t *rev_file, 3507 svn_fs_fs__p2l_entry_t* entry, 3508 apr_pool_t *pool) 3509{ 3510 apr_uint32_t digest; 3511 svn_checksum_t *expected, *actual; 3512 apr_uint32_t plain_digest; 3513 3514 /* Read item into string buffer. */ 3515 svn_stringbuf_t *text = svn_stringbuf_create_ensure(entry->size, pool); 3516 text->len = entry->size; 3517 text->data[text->len] = 0; 3518 SVN_ERR(svn_io_file_read_full2(rev_file->file, text->data, text->len, 3519 NULL, NULL, pool)); 3520 3521 /* Return (construct, calculate) stream and checksum. */ 3522 *stream = svn_stream_from_stringbuf(text, pool); 3523 digest = svn__fnv1a_32x4(text->data, text->len); 3524 3525 /* Checksums will match most of the time. */ 3526 if (entry->fnv1_checksum == digest) 3527 return SVN_NO_ERROR; 3528 3529 /* Construct proper checksum objects from their digests to allow for 3530 * nice error messages. */ 3531 plain_digest = htonl(entry->fnv1_checksum); 3532 expected = svn_checksum__from_digest_fnv1a_32x4( 3533 (const unsigned char *)&plain_digest, pool); 3534 plain_digest = htonl(digest); 3535 actual = svn_checksum__from_digest_fnv1a_32x4( 3536 (const unsigned char *)&plain_digest, pool); 3537 3538 /* Construct the full error message with all the info we have. */ 3539 return svn_checksum_mismatch_err(expected, actual, pool, 3540 _("Low-level checksum mismatch while reading\n" 3541 "%s bytes of meta data at offset %s " 3542 "for item %s in revision %ld"), 3543 apr_off_t_toa(pool, entry->size), 3544 apr_off_t_toa(pool, entry->offset), 3545 apr_psprintf(pool, "%" APR_UINT64_T_FMT, entry->item.number), 3546 entry->item.revision); 3547} 3548 3549/* If not already cached, read the changed paths list addressed by ENTRY in 3550 * FS and cache it if it has no more than SVN_FS_FS__CHANGES_BLOCK_SIZE 3551 * entries and caching is enabled. Read the data from REV_FILE. 3552 * Allocate temporaries in SCRATCH_POOL. 3553 */ 3554static svn_error_t * 3555block_read_changes(svn_fs_t *fs, 3556 svn_fs_fs__revision_file_t *rev_file, 3557 svn_fs_fs__p2l_entry_t *entry, 3558 apr_pool_t *scratch_pool) 3559{ 3560 fs_fs_data_t *ffd = fs->fsap_data; 3561 svn_stream_t *stream; 3562 apr_array_header_t *changes; 3563 3564 pair_cache_key_t key; 3565 key.revision = entry->item.revision; 3566 key.second = 0; 3567 3568 if (!ffd->changes_cache) 3569 return SVN_NO_ERROR; 3570 3571 /* already in cache? */ 3572 if (ffd->changes_cache) 3573 { 3574 svn_boolean_t is_cached; 3575 SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_cache, &key, 3576 scratch_pool)); 3577 if (is_cached) 3578 return SVN_NO_ERROR; 3579 } 3580 3581 SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3582 3583 /* Read changes from revision file. But read just past the first block to 3584 enable us to determine whether the first block already hit the EOL. 3585 3586 Note: A 100 entries block is already > 10kB on disk. With a 4kB default 3587 disk block size, this function won't even be called for larger 3588 changed paths lists. */ 3589 SVN_ERR(svn_fs_fs__read_changes(&changes, stream, 3590 SVN_FS_FS__CHANGES_BLOCK_SIZE + 1, 3591 scratch_pool, scratch_pool)); 3592 3593 /* We can only cache small lists that don't need to be split up. 3594 For longer lists, we miss the file offset info for the respective */ 3595 if (changes->nelts <= SVN_FS_FS__CHANGES_BLOCK_SIZE) 3596 { 3597 svn_fs_fs__changes_list_t changes_list; 3598 3599 /* Construct the info object for the entries block we just read. */ 3600 changes_list.end_offset = entry->size; 3601 changes_list.start_offset = 0; 3602 changes_list.count = changes->nelts; 3603 changes_list.changes = (change_t **)changes->elts; 3604 changes_list.eol = TRUE; 3605 3606 SVN_ERR(svn_cache__set(ffd->changes_cache, &key, &changes_list, 3607 scratch_pool)); 3608 } 3609 3610 return SVN_NO_ERROR; 3611} 3612 3613/* If not already cached or if MUST_READ is set, read the node revision 3614 * addressed by ENTRY in FS and ret��rn it in *NODEREV_P. Cache the 3615 * result if caching is enabled. Read the data from REV_FILE. Allocate 3616 * *NODEREV_P in RESUSLT_POOL and allocate temporaries in SCRATCH_POOL. 3617 */ 3618static svn_error_t * 3619block_read_noderev(node_revision_t **noderev_p, 3620 svn_fs_t *fs, 3621 svn_fs_fs__revision_file_t *rev_file, 3622 svn_fs_fs__p2l_entry_t *entry, 3623 svn_boolean_t must_read, 3624 apr_pool_t *result_pool, 3625 apr_pool_t *scratch_pool) 3626{ 3627 fs_fs_data_t *ffd = fs->fsap_data; 3628 svn_stream_t *stream; 3629 3630 pair_cache_key_t key = { 0 }; 3631 key.revision = entry->item.revision; 3632 key.second = entry->item.number; 3633 3634 if (!must_read && !ffd->node_revision_cache) 3635 return SVN_NO_ERROR; 3636 3637 /* already in cache? */ 3638 if (!must_read && ffd->node_revision_cache) 3639 { 3640 svn_boolean_t is_cached; 3641 SVN_ERR(svn_cache__has_key(&is_cached, ffd->node_revision_cache, 3642 &key, scratch_pool)); 3643 if (is_cached) 3644 return SVN_NO_ERROR; 3645 } 3646 3647 SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3648 3649 /* read node rev from revision file */ 3650 SVN_ERR(svn_fs_fs__read_noderev(noderev_p, stream, 3651 result_pool, scratch_pool)); 3652 SVN_ERR(fixup_node_revision(fs, *noderev_p, scratch_pool)); 3653 3654 if (ffd->node_revision_cache) 3655 SVN_ERR(svn_cache__set(ffd->node_revision_cache, &key, *noderev_p, 3656 scratch_pool)); 3657 3658 return SVN_NO_ERROR; 3659} 3660 3661/* Read the whole (e.g. 64kB) block containing ITEM_INDEX of REVISION in FS 3662 * and put all data into cache. If necessary and depending on heuristics, 3663 * neighboring blocks may also get read. The data is being read from 3664 * already open REVISION_FILE, which must be the correct rev / pack file 3665 * w.r.t. REVISION. 3666 * 3667 * For noderevs and changed path lists, the item fetched can be allocated 3668 * RESULT_POOL and returned in *RESULT. Otherwise, RESULT must be NULL. 3669 */ 3670static svn_error_t * 3671block_read(void **result, 3672 svn_fs_t *fs, 3673 svn_revnum_t revision, 3674 apr_uint64_t item_index, 3675 svn_fs_fs__revision_file_t *revision_file, 3676 apr_pool_t *result_pool, 3677 apr_pool_t *scratch_pool) 3678{ 3679 fs_fs_data_t *ffd = fs->fsap_data; 3680 apr_off_t offset, wanted_offset = 0; 3681 apr_off_t block_start = 0; 3682 apr_array_header_t *entries; 3683 int run_count = 0; 3684 int i; 3685 apr_pool_t *iterpool; 3686 3687 /* Block read is an optional feature. If the caller does not want anything 3688 * specific we may not have to read anything. */ 3689 if (!result) 3690 return SVN_NO_ERROR; 3691 3692 iterpool = svn_pool_create(scratch_pool); 3693 3694 /* don't try this on transaction protorev files */ 3695 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 3696 3697 /* index lookup: find the OFFSET of the item we *must* read plus (in the 3698 * "do-while" block) the list of items in the same block. */ 3699 SVN_ERR(svn_fs_fs__item_offset(&wanted_offset, fs, revision_file, 3700 revision, NULL, item_index, iterpool)); 3701 3702 offset = wanted_offset; 3703 3704 /* Heuristics: 3705 * 3706 * Read this block. If the last item crosses the block boundary, read 3707 * the next block but stop there. Because cross-boundary items cause 3708 * blocks to be read twice, this heuristics will limit this effect to 3709 * approx. 50% of blocks, probably less, while providing a sensible 3710 * amount of read-ahead. 3711 */ 3712 do 3713 { 3714 /* fetch list of items in the block surrounding OFFSET */ 3715 block_start = offset - (offset % ffd->block_size); 3716 SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, fs, revision_file, 3717 revision, block_start, 3718 ffd->block_size, scratch_pool, 3719 scratch_pool)); 3720 3721 SVN_ERR(aligned_seek(fs, revision_file->file, &block_start, offset, 3722 iterpool)); 3723 3724 /* read all items from the block */ 3725 for (i = 0; i < entries->nelts; ++i) 3726 { 3727 svn_boolean_t is_result, is_wanted; 3728 apr_pool_t *pool; 3729 svn_fs_fs__p2l_entry_t* entry; 3730 3731 svn_pool_clear(iterpool); 3732 3733 /* skip empty sections */ 3734 entry = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t); 3735 if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED) 3736 continue; 3737 3738 /* the item / container we were looking for? */ 3739 is_wanted = entry->offset == wanted_offset 3740 && entry->item.revision == revision 3741 && entry->item.number == item_index; 3742 is_result = result && is_wanted; 3743 3744 /* select the pool that we want the item to be allocated in */ 3745 pool = is_result ? result_pool : iterpool; 3746 3747 /* handle all items that start within this block and are relatively 3748 * small (i.e. < block size). Always read the item we need to return. 3749 */ 3750 if (is_result || ( entry->offset >= block_start 3751 && entry->size < ffd->block_size)) 3752 { 3753 void *item = NULL; 3754 SVN_ERR(svn_io_file_seek(revision_file->file, APR_SET, 3755 &entry->offset, iterpool)); 3756 switch (entry->type) 3757 { 3758 case SVN_FS_FS__ITEM_TYPE_FILE_REP: 3759 case SVN_FS_FS__ITEM_TYPE_DIR_REP: 3760 case SVN_FS_FS__ITEM_TYPE_FILE_PROPS: 3761 case SVN_FS_FS__ITEM_TYPE_DIR_PROPS: 3762 SVN_ERR(block_read_contents(fs, revision_file, entry, 3763 is_wanted 3764 ? -1 3765 : block_start + ffd->block_size, 3766 iterpool)); 3767 break; 3768 3769 case SVN_FS_FS__ITEM_TYPE_NODEREV: 3770 if (ffd->node_revision_cache || is_result) 3771 SVN_ERR(block_read_noderev((node_revision_t **)&item, 3772 fs, revision_file, 3773 entry, is_result, pool, 3774 iterpool)); 3775 break; 3776 3777 case SVN_FS_FS__ITEM_TYPE_CHANGES: 3778 SVN_ERR(block_read_changes(fs, revision_file, 3779 entry, iterpool)); 3780 break; 3781 3782 default: 3783 break; 3784 } 3785 3786 if (is_result) 3787 *result = item; 3788 3789 /* if we crossed a block boundary, read the remainder of 3790 * the last block as well */ 3791 offset = entry->offset + entry->size; 3792 if (offset - block_start > ffd->block_size) 3793 ++run_count; 3794 } 3795 } 3796 3797 } 3798 while(run_count++ == 1); /* can only be true once and only if a block 3799 * boundary got crossed */ 3800 3801 /* if the caller requested a result, we must have provided one by now */ 3802 assert(!result || *result); 3803 svn_pool_destroy(iterpool); 3804 3805 return SVN_NO_ERROR; 3806} 3807