1289177Speter/* cached_data.c --- cached (read) access to FSX data 2289177Speter * 3289177Speter * ==================================================================== 4289177Speter * Licensed to the Apache Software Foundation (ASF) under one 5289177Speter * or more contributor license agreements. See the NOTICE file 6289177Speter * distributed with this work for additional information 7289177Speter * regarding copyright ownership. The ASF licenses this file 8289177Speter * to you under the Apache License, Version 2.0 (the 9289177Speter * "License"); you may not use this file except in compliance 10289177Speter * with the License. You may obtain a copy of the License at 11289177Speter * 12289177Speter * http://www.apache.org/licenses/LICENSE-2.0 13289177Speter * 14289177Speter * Unless required by applicable law or agreed to in writing, 15289177Speter * software distributed under the License is distributed on an 16289177Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17289177Speter * KIND, either express or implied. See the License for the 18289177Speter * specific language governing permissions and limitations 19289177Speter * under the License. 20289177Speter * ==================================================================== 21289177Speter */ 22289177Speter 23289177Speter#include "cached_data.h" 24289177Speter 25289177Speter#include <assert.h> 26289177Speter 27289177Speter#include "svn_hash.h" 28289177Speter#include "svn_ctype.h" 29289177Speter#include "svn_sorts.h" 30289177Speter 31289177Speter#include "private/svn_io_private.h" 32289177Speter#include "private/svn_sorts_private.h" 33289177Speter#include "private/svn_subr_private.h" 34289177Speter#include "private/svn_temp_serializer.h" 35289177Speter 36289177Speter#include "fs_x.h" 37289177Speter#include "low_level.h" 38289177Speter#include "util.h" 39289177Speter#include "pack.h" 40289177Speter#include "temp_serializer.h" 41289177Speter#include "index.h" 42289177Speter#include "changes.h" 43289177Speter#include "noderevs.h" 44289177Speter#include "reps.h" 45289177Speter 46289177Speter#include "../libsvn_fs/fs-loader.h" 47289177Speter#include "../libsvn_delta/delta.h" /* for SVN_DELTA_WINDOW_SIZE */ 48289177Speter 49289177Speter#include "svn_private_config.h" 50289177Speter 51289177Speter/* forward-declare. See implementation for the docstring */ 52289177Speterstatic svn_error_t * 53289177Speterblock_read(void **result, 54289177Speter svn_fs_t *fs, 55289177Speter const svn_fs_x__id_t *id, 56289177Speter svn_fs_x__revision_file_t *revision_file, 57289177Speter apr_pool_t *result_pool, 58289177Speter apr_pool_t *scratch_pool); 59289177Speter 60289177Speter 61289177Speter/* Defined this to enable access logging via dgb__log_access 62289177Speter#define SVN_FS_X__LOG_ACCESS 63289177Speter*/ 64289177Speter 65289177Speter/* When SVN_FS_X__LOG_ACCESS has been defined, write a line to console 66289177Speter * showing where ID is located in FS and use ITEM to show details on it's 67289177Speter * contents if not NULL. Use SCRATCH_POOL for temporary allocations. 68289177Speter */ 69289177Speterstatic svn_error_t * 70289177Speterdgb__log_access(svn_fs_t *fs, 71289177Speter const svn_fs_x__id_t *id, 72289177Speter void *item, 73289177Speter apr_uint32_t item_type, 74289177Speter apr_pool_t *scratch_pool) 75289177Speter{ 76289177Speter /* no-op if this macro is not defined */ 77289177Speter#ifdef SVN_FS_X__LOG_ACCESS 78289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 79289177Speter apr_off_t offset = -1; 80289177Speter apr_off_t end_offset = 0; 81289177Speter apr_uint32_t sub_item = 0; 82289177Speter svn_fs_x__p2l_entry_t *entry = NULL; 83289177Speter static const char *types[] = {"<n/a>", "frep ", "drep ", "fprop", "dprop", 84289177Speter "node ", "chgs ", "rep ", "c:", "n:", "r:"}; 85289177Speter const char *description = ""; 86289177Speter const char *type = types[item_type]; 87289177Speter const char *pack = ""; 88289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(id->change_set); 89289177Speter 90289177Speter /* determine rev / pack file offset */ 91289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, id, scratch_pool)); 92289177Speter 93289177Speter /* constructing the pack file description */ 94289177Speter if (revision < ffd->min_unpacked_rev) 95289177Speter pack = apr_psprintf(scratch_pool, "%4ld|", 96289177Speter revision / ffd->max_files_per_dir); 97289177Speter 98289177Speter /* construct description if possible */ 99289177Speter if (item_type == SVN_FS_X__ITEM_TYPE_NODEREV && item != NULL) 100289177Speter { 101289177Speter svn_fs_x__noderev_t *node = item; 102289177Speter const char *data_rep 103289177Speter = node->data_rep 104289177Speter ? apr_psprintf(scratch_pool, " d=%ld/%" APR_UINT64_T_FMT, 105289177Speter svn_fs_x__get_revnum(node->data_rep->id.change_set), 106289177Speter node->data_rep->id.number) 107289177Speter : ""; 108289177Speter const char *prop_rep 109289177Speter = node->prop_rep 110289177Speter ? apr_psprintf(scratch_pool, " p=%ld/%" APR_UINT64_T_FMT, 111289177Speter svn_fs_x__get_revnum(node->prop_rep->id.change_set), 112289177Speter node->prop_rep->id.number) 113289177Speter : ""; 114289177Speter description = apr_psprintf(scratch_pool, "%s (pc=%d%s%s)", 115289177Speter node->created_path, 116289177Speter node->predecessor_count, 117289177Speter data_rep, 118289177Speter prop_rep); 119289177Speter } 120289177Speter else if (item_type == SVN_FS_X__ITEM_TYPE_ANY_REP) 121289177Speter { 122289177Speter svn_fs_x__rep_header_t *header = item; 123289177Speter if (header == NULL) 124289177Speter description = " (txdelta window)"; 125289177Speter else if (header->type == svn_fs_x__rep_self_delta) 126289177Speter description = " DELTA"; 127289177Speter else 128289177Speter description = apr_psprintf(scratch_pool, 129289177Speter " DELTA against %ld/%" APR_UINT64_T_FMT, 130289177Speter header->base_revision, 131289177Speter header->base_item_index); 132289177Speter } 133289177Speter else if (item_type == SVN_FS_X__ITEM_TYPE_CHANGES && item != NULL) 134289177Speter { 135289177Speter apr_array_header_t *changes = item; 136289177Speter switch (changes->nelts) 137289177Speter { 138289177Speter case 0: description = " no change"; 139289177Speter break; 140289177Speter case 1: description = " 1 change"; 141289177Speter break; 142289177Speter default: description = apr_psprintf(scratch_pool, " %d changes", 143289177Speter changes->nelts); 144289177Speter } 145289177Speter } 146289177Speter 147289177Speter /* reverse index lookup: get item description in ENTRY */ 148289177Speter SVN_ERR(svn_fs_x__p2l_entry_lookup(&entry, fs, revision, offset, 149289177Speter scratch_pool)); 150289177Speter if (entry) 151289177Speter { 152289177Speter /* more details */ 153289177Speter end_offset = offset + entry->size; 154289177Speter type = types[entry->type]; 155289177Speter 156289177Speter /* merge the sub-item number with the container type */ 157289177Speter if ( entry->type == SVN_FS_X__ITEM_TYPE_CHANGES_CONT 158289177Speter || entry->type == SVN_FS_X__ITEM_TYPE_NODEREVS_CONT 159289177Speter || entry->type == SVN_FS_X__ITEM_TYPE_REPS_CONT) 160289177Speter type = apr_psprintf(scratch_pool, "%s%-3d", type, sub_item); 161289177Speter } 162289177Speter 163289177Speter /* line output */ 164289177Speter printf("%5s%4lx:%04lx -%4lx:%04lx %s %7ld %5"APR_UINT64_T_FMT" %s\n", 165289177Speter pack, (long)(offset / ffd->block_size), 166289177Speter (long)(offset % ffd->block_size), 167289177Speter (long)(end_offset / ffd->block_size), 168289177Speter (long)(end_offset % ffd->block_size), 169289177Speter type, revision, id->number, description); 170289177Speter 171289177Speter#endif 172289177Speter 173289177Speter return SVN_NO_ERROR; 174289177Speter} 175289177Speter 176289177Speter/* Convenience wrapper around svn_io_file_aligned_seek, taking filesystem 177289177Speter FS instead of a block size. */ 178289177Speterstatic svn_error_t * 179289177Speteraligned_seek(svn_fs_t *fs, 180289177Speter apr_file_t *file, 181289177Speter apr_off_t *buffer_start, 182289177Speter apr_off_t offset, 183289177Speter apr_pool_t *scratch_pool) 184289177Speter{ 185289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 186289177Speter return svn_error_trace(svn_io_file_aligned_seek(file, ffd->block_size, 187289177Speter buffer_start, offset, 188289177Speter scratch_pool)); 189289177Speter} 190289177Speter 191289177Speter/* Open the revision file for the item given by ID in filesystem FS and 192289177Speter store the newly opened file in FILE. Seek to the item's location before 193289177Speter returning. 194289177Speter 195289177Speter Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL. */ 196289177Speterstatic svn_error_t * 197289177Speteropen_and_seek_revision(svn_fs_x__revision_file_t **file, 198289177Speter svn_fs_t *fs, 199289177Speter const svn_fs_x__id_t *id, 200289177Speter apr_pool_t *result_pool, 201289177Speter apr_pool_t *scratch_pool) 202289177Speter{ 203289177Speter svn_fs_x__revision_file_t *rev_file; 204289177Speter apr_off_t offset = -1; 205289177Speter apr_uint32_t sub_item = 0; 206289177Speter svn_revnum_t rev = svn_fs_x__get_revnum(id->change_set); 207289177Speter 208289177Speter SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool)); 209289177Speter 210289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, rev, result_pool, 211289177Speter scratch_pool)); 212289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file, id, 213289177Speter scratch_pool)); 214289177Speter SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, scratch_pool)); 215289177Speter 216289177Speter *file = rev_file; 217289177Speter 218289177Speter return SVN_NO_ERROR; 219289177Speter} 220289177Speter 221289177Speter/* Open the representation REP for a node-revision in filesystem FS, seek 222289177Speter to its position and store the newly opened file in FILE. 223289177Speter 224289177Speter Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL. */ 225289177Speterstatic svn_error_t * 226289177Speteropen_and_seek_transaction(svn_fs_x__revision_file_t **file, 227289177Speter svn_fs_t *fs, 228289177Speter svn_fs_x__representation_t *rep, 229289177Speter apr_pool_t *result_pool, 230289177Speter apr_pool_t *scratch_pool) 231289177Speter{ 232289177Speter apr_off_t offset; 233289177Speter apr_uint32_t sub_item = 0; 234289177Speter apr_int64_t txn_id = svn_fs_x__get_txn_id(rep->id.change_set); 235289177Speter 236289177Speter SVN_ERR(svn_fs_x__open_proto_rev_file(file, fs, txn_id, result_pool, 237289177Speter scratch_pool)); 238289177Speter 239289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, *file, &rep->id, 240289177Speter scratch_pool)); 241289177Speter SVN_ERR(aligned_seek(fs, (*file)->file, NULL, offset, scratch_pool)); 242289177Speter 243289177Speter return SVN_NO_ERROR; 244289177Speter} 245289177Speter 246289177Speter/* Given a node-id ID, and a representation REP in filesystem FS, open 247289177Speter the correct file and seek to the correction location. Store this 248289177Speter file in *FILE_P. 249289177Speter 250289177Speter Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL. */ 251289177Speterstatic svn_error_t * 252289177Speteropen_and_seek_representation(svn_fs_x__revision_file_t **file_p, 253289177Speter svn_fs_t *fs, 254289177Speter svn_fs_x__representation_t *rep, 255289177Speter apr_pool_t *result_pool, 256289177Speter apr_pool_t *scratch_pool) 257289177Speter{ 258289177Speter if (svn_fs_x__is_revision(rep->id.change_set)) 259289177Speter return open_and_seek_revision(file_p, fs, &rep->id, result_pool, 260289177Speter scratch_pool); 261289177Speter else 262289177Speter return open_and_seek_transaction(file_p, fs, rep, result_pool, 263289177Speter scratch_pool); 264289177Speter} 265289177Speter 266289177Speter 267289177Speter 268289177Speterstatic svn_error_t * 269289177Spetererr_dangling_id(svn_fs_t *fs, 270289177Speter const svn_fs_x__id_t *id) 271289177Speter{ 272289177Speter svn_string_t *id_str = svn_fs_x__id_unparse(id, fs->pool); 273289177Speter return svn_error_createf 274289177Speter (SVN_ERR_FS_ID_NOT_FOUND, 0, 275289177Speter _("Reference to non-existent node '%s' in filesystem '%s'"), 276289177Speter id_str->data, fs->path); 277289177Speter} 278289177Speter 279289177Speter/* Get the node-revision for the node ID in FS. 280289177Speter Set *NODEREV_P to the new node-revision structure, allocated in POOL. 281289177Speter See svn_fs_x__get_node_revision, which wraps this and adds another 282289177Speter error. */ 283289177Speterstatic svn_error_t * 284289177Speterget_node_revision_body(svn_fs_x__noderev_t **noderev_p, 285289177Speter svn_fs_t *fs, 286289177Speter const svn_fs_x__id_t *id, 287289177Speter apr_pool_t *result_pool, 288289177Speter apr_pool_t *scratch_pool) 289289177Speter{ 290289177Speter svn_error_t *err; 291289177Speter svn_boolean_t is_cached = FALSE; 292289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 293289177Speter 294289177Speter if (svn_fs_x__is_txn(id->change_set)) 295289177Speter { 296289177Speter apr_file_t *file; 297289177Speter 298289177Speter /* This is a transaction node-rev. Its storage logic is very 299289177Speter different from that of rev / pack files. */ 300289177Speter err = svn_io_file_open(&file, 301289177Speter svn_fs_x__path_txn_node_rev(fs, id, 302289177Speter scratch_pool, 303289177Speter scratch_pool), 304289177Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, 305289177Speter scratch_pool); 306289177Speter if (err) 307289177Speter { 308289177Speter if (APR_STATUS_IS_ENOENT(err->apr_err)) 309289177Speter { 310289177Speter svn_error_clear(err); 311289177Speter return svn_error_trace(err_dangling_id(fs, id)); 312289177Speter } 313289177Speter 314289177Speter return svn_error_trace(err); 315289177Speter } 316289177Speter 317289177Speter SVN_ERR(svn_fs_x__read_noderev(noderev_p, 318289177Speter svn_stream_from_aprfile2(file, 319289177Speter FALSE, 320289177Speter scratch_pool), 321289177Speter result_pool, scratch_pool)); 322289177Speter } 323289177Speter else 324289177Speter { 325289177Speter svn_fs_x__revision_file_t *revision_file; 326289177Speter 327289177Speter /* noderevs in rev / pack files can be cached */ 328289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(id->change_set); 329289177Speter svn_fs_x__pair_cache_key_t key; 330289177Speter 331289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&revision_file, fs, revision, 332289177Speter scratch_pool, scratch_pool)); 333289177Speter 334289177Speter /* First, try a noderevs container cache lookup. */ 335289177Speter if ( svn_fs_x__is_packed_rev(fs, revision) 336289177Speter && ffd->noderevs_container_cache) 337289177Speter { 338289177Speter apr_off_t offset; 339289177Speter apr_uint32_t sub_item; 340289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, revision_file, 341289177Speter id, scratch_pool)); 342289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 343289177Speter key.second = offset; 344289177Speter 345289177Speter SVN_ERR(svn_cache__get_partial((void **)noderev_p, &is_cached, 346289177Speter ffd->noderevs_container_cache, &key, 347289177Speter svn_fs_x__noderevs_get_func, 348289177Speter &sub_item, result_pool)); 349289177Speter if (is_cached) 350289177Speter return SVN_NO_ERROR; 351289177Speter } 352289177Speter 353289177Speter key.revision = revision; 354289177Speter key.second = id->number; 355289177Speter 356289177Speter /* Not found or not applicable. Try a noderev cache lookup. 357289177Speter * If that succeeds, we are done here. */ 358289177Speter if (ffd->node_revision_cache) 359289177Speter { 360289177Speter SVN_ERR(svn_cache__get((void **) noderev_p, 361289177Speter &is_cached, 362289177Speter ffd->node_revision_cache, 363289177Speter &key, 364289177Speter result_pool)); 365289177Speter if (is_cached) 366289177Speter return SVN_NO_ERROR; 367289177Speter } 368289177Speter 369289177Speter /* block-read will parse the whole block and will also return 370289177Speter the one noderev that we need right now. */ 371289177Speter SVN_ERR(block_read((void **)noderev_p, fs, 372289177Speter id, 373289177Speter revision_file, 374289177Speter result_pool, 375289177Speter scratch_pool)); 376289177Speter SVN_ERR(svn_fs_x__close_revision_file(revision_file)); 377289177Speter } 378289177Speter 379289177Speter return SVN_NO_ERROR; 380289177Speter} 381289177Speter 382289177Spetersvn_error_t * 383289177Spetersvn_fs_x__get_node_revision(svn_fs_x__noderev_t **noderev_p, 384289177Speter svn_fs_t *fs, 385289177Speter const svn_fs_x__id_t *id, 386289177Speter apr_pool_t *result_pool, 387289177Speter apr_pool_t *scratch_pool) 388289177Speter{ 389289177Speter svn_error_t *err = get_node_revision_body(noderev_p, fs, id, 390289177Speter result_pool, scratch_pool); 391289177Speter if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 392289177Speter { 393289177Speter svn_string_t *id_string = svn_fs_x__id_unparse(id, scratch_pool); 394289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 395289177Speter "Corrupt node-revision '%s'", 396289177Speter id_string->data); 397289177Speter } 398289177Speter 399289177Speter SVN_ERR(dgb__log_access(fs, id, *noderev_p, 400289177Speter SVN_FS_X__ITEM_TYPE_NODEREV, scratch_pool)); 401289177Speter 402289177Speter return svn_error_trace(err); 403289177Speter} 404289177Speter 405289177Speter 406289177Spetersvn_error_t * 407289177Spetersvn_fs_x__get_mergeinfo_count(apr_int64_t *count, 408289177Speter svn_fs_t *fs, 409289177Speter const svn_fs_x__id_t *id, 410289177Speter apr_pool_t *scratch_pool) 411289177Speter{ 412289177Speter svn_fs_x__noderev_t *noderev; 413289177Speter 414289177Speter /* If we want a full acccess log, we need to provide full data and 415289177Speter cannot take shortcuts here. */ 416289177Speter#if !defined(SVN_FS_X__LOG_ACCESS) 417289177Speter 418289177Speter /* First, try a noderevs container cache lookup. */ 419289177Speter if (! svn_fs_x__is_txn(id->change_set)) 420289177Speter { 421289177Speter /* noderevs in rev / pack files can be cached */ 422289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 423289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(id->change_set); 424289177Speter 425289177Speter svn_fs_x__revision_file_t *rev_file; 426289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, revision, 427289177Speter scratch_pool, scratch_pool)); 428289177Speter 429289177Speter if ( svn_fs_x__is_packed_rev(fs, revision) 430289177Speter && ffd->noderevs_container_cache) 431289177Speter { 432289177Speter svn_fs_x__pair_cache_key_t key; 433289177Speter apr_off_t offset; 434289177Speter apr_uint32_t sub_item; 435289177Speter svn_boolean_t is_cached; 436289177Speter 437289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file, 438289177Speter id, scratch_pool)); 439289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 440289177Speter key.second = offset; 441289177Speter 442289177Speter SVN_ERR(svn_cache__get_partial((void **)count, &is_cached, 443289177Speter ffd->noderevs_container_cache, &key, 444289177Speter svn_fs_x__mergeinfo_count_get_func, 445289177Speter &sub_item, scratch_pool)); 446289177Speter if (is_cached) 447289177Speter return SVN_NO_ERROR; 448289177Speter } 449289177Speter } 450289177Speter#endif 451289177Speter 452289177Speter /* fallback to the naive implementation handling all edge cases */ 453289177Speter SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool, 454289177Speter scratch_pool)); 455289177Speter *count = noderev->mergeinfo_count; 456289177Speter 457289177Speter return SVN_NO_ERROR; 458289177Speter} 459289177Speter 460289177Speter/* Describes a lazily opened rev / pack file. Instances will be shared 461289177Speter between multiple instances of rep_state_t. */ 462289177Spetertypedef struct shared_file_t 463289177Speter{ 464289177Speter /* The opened file. NULL while file is not open, yet. */ 465289177Speter svn_fs_x__revision_file_t *rfile; 466289177Speter 467289177Speter /* file system to open the file in */ 468289177Speter svn_fs_t *fs; 469289177Speter 470289177Speter /* a revision contained in the FILE. Since this file may be shared, 471289177Speter that value may be different from REP_STATE_T->REVISION. */ 472289177Speter svn_revnum_t revision; 473289177Speter 474289177Speter /* pool to use when creating the FILE. This guarantees that the file 475289177Speter remains open / valid beyond the respective local context that required 476289177Speter the file to be opened eventually. */ 477289177Speter apr_pool_t *pool; 478289177Speter} shared_file_t; 479289177Speter 480289177Speter/* Represents where in the current svndiff data block each 481289177Speter representation is. */ 482289177Spetertypedef struct rep_state_t 483289177Speter{ 484289177Speter /* shared lazy-open rev/pack file structure */ 485289177Speter shared_file_t *sfile; 486289177Speter /* The txdelta window cache to use or NULL. */ 487289177Speter svn_cache__t *window_cache; 488289177Speter /* Caches un-deltified windows. May be NULL. */ 489289177Speter svn_cache__t *combined_cache; 490289177Speter /* ID addressing the representation */ 491289177Speter svn_fs_x__id_t rep_id; 492289177Speter /* length of the header at the start of the rep. 493289177Speter 0 iff this is rep is stored in a container 494289177Speter (i.e. does not have a header) */ 495289177Speter apr_size_t header_size; 496289177Speter apr_off_t start; /* The starting offset for the raw 497289177Speter svndiff data minus header. 498289177Speter -1 if the offset is yet unknown. */ 499289177Speter /* sub-item index in case the rep is containered */ 500289177Speter apr_uint32_t sub_item; 501289177Speter apr_off_t current;/* The current offset relative to START. */ 502289177Speter apr_off_t size; /* The on-disk size of the representation. */ 503289177Speter int ver; /* If a delta, what svndiff version? 504289177Speter -1 for unknown delta version. */ 505289177Speter int chunk_index; /* number of the window to read */ 506289177Speter} rep_state_t; 507289177Speter 508289177Speter/* Simple wrapper around svn_fs_x__get_file_offset to simplify callers. */ 509289177Speterstatic svn_error_t * 510289177Speterget_file_offset(apr_off_t *offset, 511289177Speter rep_state_t *rs, 512289177Speter apr_pool_t *scratch_pool) 513289177Speter{ 514289177Speter return svn_error_trace(svn_fs_x__get_file_offset(offset, 515289177Speter rs->sfile->rfile->file, 516289177Speter scratch_pool)); 517289177Speter} 518289177Speter 519289177Speter/* Simple wrapper around svn_io_file_aligned_seek to simplify callers. */ 520289177Speterstatic svn_error_t * 521289177Speterrs_aligned_seek(rep_state_t *rs, 522289177Speter apr_off_t *buffer_start, 523289177Speter apr_off_t offset, 524289177Speter apr_pool_t *scratch_pool) 525289177Speter{ 526289177Speter svn_fs_x__data_t *ffd = rs->sfile->fs->fsap_data; 527289177Speter return svn_error_trace(svn_io_file_aligned_seek(rs->sfile->rfile->file, 528289177Speter ffd->block_size, 529289177Speter buffer_start, offset, 530289177Speter scratch_pool)); 531289177Speter} 532289177Speter 533289177Speter/* Open FILE->FILE and FILE->STREAM if they haven't been opened, yet. */ 534289177Speterstatic svn_error_t* 535289177Speterauto_open_shared_file(shared_file_t *file) 536289177Speter{ 537289177Speter if (file->rfile == NULL) 538289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&file->rfile, file->fs, 539289177Speter file->revision, file->pool, 540289177Speter file->pool)); 541289177Speter 542289177Speter return SVN_NO_ERROR; 543289177Speter} 544289177Speter 545289177Speter/* Set RS->START to the begin of the representation raw in RS->SFILE->RFILE, 546289177Speter if that hasn't been done yet. Use SCRATCH_POOL for temporary allocations. 547289177Speter */ 548289177Speterstatic svn_error_t* 549289177Speterauto_set_start_offset(rep_state_t *rs, 550289177Speter apr_pool_t *scratch_pool) 551289177Speter{ 552289177Speter if (rs->start == -1) 553289177Speter { 554289177Speter SVN_ERR(svn_fs_x__item_offset(&rs->start, &rs->sub_item, 555289177Speter rs->sfile->fs, rs->sfile->rfile, 556289177Speter &rs->rep_id, scratch_pool)); 557289177Speter rs->start += rs->header_size; 558289177Speter } 559289177Speter 560289177Speter return SVN_NO_ERROR; 561289177Speter} 562289177Speter 563289177Speter/* Set RS->VER depending on what is found in the already open RS->FILE->FILE 564289177Speter if the diff version is still unknown. Use SCRATCH_POOL for temporary 565289177Speter allocations. 566289177Speter */ 567289177Speterstatic svn_error_t* 568289177Speterauto_read_diff_version(rep_state_t *rs, 569289177Speter apr_pool_t *scratch_pool) 570289177Speter{ 571289177Speter if (rs->ver == -1) 572289177Speter { 573289177Speter char buf[4]; 574289177Speter SVN_ERR(rs_aligned_seek(rs, NULL, rs->start, scratch_pool)); 575289177Speter SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, buf, 576289177Speter sizeof(buf), NULL, NULL, scratch_pool)); 577289177Speter 578289177Speter /* ### Layering violation */ 579289177Speter if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N'))) 580289177Speter return svn_error_create 581289177Speter (SVN_ERR_FS_CORRUPT, NULL, 582289177Speter _("Malformed svndiff data in representation")); 583289177Speter rs->ver = buf[3]; 584289177Speter 585289177Speter rs->chunk_index = 0; 586289177Speter rs->current = 4; 587289177Speter } 588289177Speter 589289177Speter return SVN_NO_ERROR; 590289177Speter} 591289177Speter 592289177Speter/* See create_rep_state, which wraps this and adds another error. */ 593289177Speterstatic svn_error_t * 594289177Spetercreate_rep_state_body(rep_state_t **rep_state, 595289177Speter svn_fs_x__rep_header_t **rep_header, 596289177Speter shared_file_t **shared_file, 597289177Speter svn_fs_x__representation_t *rep, 598289177Speter svn_fs_t *fs, 599289177Speter apr_pool_t *result_pool, 600289177Speter apr_pool_t *scratch_pool) 601289177Speter{ 602289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 603289177Speter rep_state_t *rs = apr_pcalloc(result_pool, sizeof(*rs)); 604289177Speter svn_fs_x__rep_header_t *rh; 605289177Speter svn_boolean_t is_cached = FALSE; 606289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set); 607289177Speter apr_uint64_t estimated_window_storage; 608289177Speter 609289177Speter /* If the hint is 610289177Speter * - given, 611289177Speter * - refers to a valid revision, 612289177Speter * - refers to a packed revision, 613289177Speter * - as does the rep we want to read, and 614289177Speter * - refers to the same pack file as the rep 615289177Speter * we can re-use the same, already open file object 616289177Speter */ 617289177Speter svn_boolean_t reuse_shared_file 618289177Speter = shared_file && *shared_file && (*shared_file)->rfile 619289177Speter && SVN_IS_VALID_REVNUM((*shared_file)->revision) 620289177Speter && (*shared_file)->revision < ffd->min_unpacked_rev 621289177Speter && revision < ffd->min_unpacked_rev 622289177Speter && ( ((*shared_file)->revision / ffd->max_files_per_dir) 623289177Speter == (revision / ffd->max_files_per_dir)); 624289177Speter 625289177Speter svn_fs_x__representation_cache_key_t key = { 0 }; 626289177Speter key.revision = revision; 627289177Speter key.is_packed = revision < ffd->min_unpacked_rev; 628289177Speter key.item_index = rep->id.number; 629289177Speter 630289177Speter /* continue constructing RS and RA */ 631289177Speter rs->size = rep->size; 632289177Speter rs->rep_id = rep->id; 633289177Speter rs->ver = -1; 634289177Speter rs->start = -1; 635289177Speter 636289177Speter /* Very long files stored as self-delta will produce a huge number of 637289177Speter delta windows. Don't cache them lest we don't thrash the cache. 638289177Speter Since we don't know the depth of the delta chain, let's assume, the 639289177Speter whole contents get rewritten 3 times. 640289177Speter */ 641289177Speter estimated_window_storage 642289177Speter = 4 * ( (rep->expanded_size ? rep->expanded_size : rep->size) 643289177Speter + SVN_DELTA_WINDOW_SIZE); 644289177Speter estimated_window_storage = MIN(estimated_window_storage, APR_SIZE_MAX); 645289177Speter 646289177Speter rs->window_cache = ffd->txdelta_window_cache 647289177Speter && svn_cache__is_cachable(ffd->txdelta_window_cache, 648289177Speter (apr_size_t)estimated_window_storage) 649289177Speter ? ffd->txdelta_window_cache 650289177Speter : NULL; 651289177Speter rs->combined_cache = ffd->combined_window_cache 652289177Speter && svn_cache__is_cachable(ffd->combined_window_cache, 653289177Speter (apr_size_t)estimated_window_storage) 654289177Speter ? ffd->combined_window_cache 655289177Speter : NULL; 656289177Speter 657289177Speter /* cache lookup, i.e. skip reading the rep header if possible */ 658289177Speter if (ffd->rep_header_cache && SVN_IS_VALID_REVNUM(revision)) 659289177Speter SVN_ERR(svn_cache__get((void **) &rh, &is_cached, 660289177Speter ffd->rep_header_cache, &key, result_pool)); 661289177Speter 662289177Speter /* initialize the (shared) FILE member in RS */ 663289177Speter if (reuse_shared_file) 664289177Speter { 665289177Speter rs->sfile = *shared_file; 666289177Speter } 667289177Speter else 668289177Speter { 669289177Speter shared_file_t *file = apr_pcalloc(result_pool, sizeof(*file)); 670289177Speter file->revision = revision; 671289177Speter file->pool = result_pool; 672289177Speter file->fs = fs; 673289177Speter rs->sfile = file; 674289177Speter 675289177Speter /* remember the current file, if suggested by the caller */ 676289177Speter if (shared_file) 677289177Speter *shared_file = file; 678289177Speter } 679289177Speter 680289177Speter /* read rep header, if necessary */ 681289177Speter if (!is_cached) 682289177Speter { 683289177Speter /* we will need the on-disk location for non-txn reps */ 684289177Speter apr_off_t offset; 685289177Speter svn_boolean_t in_container = TRUE; 686289177Speter 687289177Speter /* ensure file is open and navigate to the start of rep header */ 688289177Speter if (reuse_shared_file) 689289177Speter { 690289177Speter /* ... we can re-use the same, already open file object. 691289177Speter * This implies that we don't read from a txn. 692289177Speter */ 693289177Speter rs->sfile = *shared_file; 694289177Speter SVN_ERR(auto_open_shared_file(rs->sfile)); 695289177Speter } 696289177Speter else 697289177Speter { 698289177Speter /* otherwise, create a new file object. May or may not be 699289177Speter * an in-txn file. 700289177Speter */ 701289177Speter SVN_ERR(open_and_seek_representation(&rs->sfile->rfile, fs, rep, 702289177Speter result_pool, scratch_pool)); 703289177Speter } 704289177Speter 705289177Speter if (SVN_IS_VALID_REVNUM(revision)) 706289177Speter { 707289177Speter apr_uint32_t sub_item; 708289177Speter 709289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, 710289177Speter rs->sfile->rfile, &rep->id, 711289177Speter scratch_pool)); 712289177Speter 713289177Speter /* is rep stored in some star-deltified container? */ 714289177Speter if (sub_item == 0) 715289177Speter { 716289177Speter svn_fs_x__p2l_entry_t *entry; 717289177Speter SVN_ERR(svn_fs_x__p2l_entry_lookup(&entry, fs, rs->sfile->rfile, 718289177Speter revision, offset, 719289177Speter scratch_pool, scratch_pool)); 720289177Speter in_container = entry->type == SVN_FS_X__ITEM_TYPE_REPS_CONT; 721289177Speter } 722289177Speter 723289177Speter if (in_container) 724289177Speter { 725289177Speter /* construct a container rep header */ 726289177Speter *rep_header = apr_pcalloc(result_pool, sizeof(**rep_header)); 727289177Speter (*rep_header)->type = svn_fs_x__rep_container; 728289177Speter 729289177Speter /* exit to caller */ 730289177Speter *rep_state = rs; 731289177Speter return SVN_NO_ERROR; 732289177Speter } 733289177Speter 734289177Speter SVN_ERR(rs_aligned_seek(rs, NULL, offset, scratch_pool)); 735289177Speter } 736289177Speter 737289177Speter SVN_ERR(svn_fs_x__read_rep_header(&rh, rs->sfile->rfile->stream, 738289177Speter result_pool, scratch_pool)); 739289177Speter SVN_ERR(get_file_offset(&rs->start, rs, result_pool)); 740289177Speter 741289177Speter /* populate the cache if appropriate */ 742289177Speter if (SVN_IS_VALID_REVNUM(revision)) 743289177Speter { 744289177Speter SVN_ERR(block_read(NULL, fs, &rs->rep_id, rs->sfile->rfile, 745289177Speter result_pool, scratch_pool)); 746289177Speter if (ffd->rep_header_cache) 747289177Speter SVN_ERR(svn_cache__set(ffd->rep_header_cache, &key, rh, 748289177Speter scratch_pool)); 749289177Speter } 750289177Speter } 751289177Speter 752289177Speter /* finalize */ 753289177Speter SVN_ERR(dgb__log_access(fs, &rs->rep_id, rh, SVN_FS_X__ITEM_TYPE_ANY_REP, 754289177Speter scratch_pool)); 755289177Speter 756289177Speter rs->header_size = rh->header_size; 757289177Speter *rep_state = rs; 758289177Speter *rep_header = rh; 759289177Speter 760289177Speter rs->chunk_index = 0; 761289177Speter 762289177Speter /* skip "SVNx" diff marker */ 763289177Speter rs->current = 4; 764289177Speter 765289177Speter return SVN_NO_ERROR; 766289177Speter} 767289177Speter 768289177Speter/* Read the rep args for REP in filesystem FS and create a rep_state 769289177Speter for reading the representation. Return the rep_state in *REP_STATE 770289177Speter and the rep args in *REP_ARGS, both allocated in POOL. 771289177Speter 772289177Speter When reading multiple reps, i.e. a skip delta chain, you may provide 773289177Speter non-NULL SHARED_FILE. (If SHARED_FILE is not NULL, in the first 774289177Speter call it should be a pointer to NULL.) The function will use this 775289177Speter variable to store the previous call results and tries to re-use it. 776289177Speter This may result in significant savings in I/O for packed files and 777289177Speter number of open file handles. 778289177Speter */ 779289177Speterstatic svn_error_t * 780289177Spetercreate_rep_state(rep_state_t **rep_state, 781289177Speter svn_fs_x__rep_header_t **rep_header, 782289177Speter shared_file_t **shared_file, 783289177Speter svn_fs_x__representation_t *rep, 784289177Speter svn_fs_t *fs, 785289177Speter apr_pool_t *result_pool, 786289177Speter apr_pool_t *scratch_pool) 787289177Speter{ 788289177Speter svn_error_t *err = create_rep_state_body(rep_state, rep_header, 789289177Speter shared_file, rep, fs, 790289177Speter result_pool, scratch_pool); 791289177Speter if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 792289177Speter { 793289177Speter /* ### This always returns "-1" for transaction reps, because 794289177Speter ### this particular bit of code doesn't know if the rep is 795289177Speter ### stored in the protorev or in the mutable area (for props 796289177Speter ### or dir contents). It is pretty rare for FSX to *read* 797289177Speter ### from the protorev file, though, so this is probably OK. 798289177Speter ### And anyone going to debug corruption errors is probably 799289177Speter ### going to jump straight to this comment anyway! */ 800289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 801289177Speter "Corrupt representation '%s'", 802289177Speter rep 803289177Speter ? svn_fs_x__unparse_representation 804289177Speter (rep, TRUE, scratch_pool, 805289177Speter scratch_pool)->data 806289177Speter : "(null)"); 807289177Speter } 808289177Speter /* ### Call representation_string() ? */ 809289177Speter return svn_error_trace(err); 810289177Speter} 811289177Speter 812289177Spetersvn_error_t * 813289177Spetersvn_fs_x__check_rep(svn_fs_x__representation_t *rep, 814289177Speter svn_fs_t *fs, 815289177Speter apr_pool_t *scratch_pool) 816289177Speter{ 817289177Speter apr_off_t offset; 818289177Speter apr_uint32_t sub_item; 819289177Speter svn_fs_x__p2l_entry_t *entry; 820289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set); 821289177Speter 822289177Speter svn_fs_x__revision_file_t *rev_file; 823289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, revision, 824289177Speter scratch_pool, scratch_pool)); 825289177Speter 826289177Speter /* Does REP->ID refer to an actual item? Which one is it? */ 827289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file, &rep->id, 828289177Speter scratch_pool)); 829289177Speter 830289177Speter /* What is the type of that item? */ 831289177Speter SVN_ERR(svn_fs_x__p2l_entry_lookup(&entry, fs, rev_file, revision, offset, 832289177Speter scratch_pool, scratch_pool)); 833289177Speter 834289177Speter /* Verify that we've got an item that is actually a representation. */ 835289177Speter if ( entry == NULL 836289177Speter || ( entry->type != SVN_FS_X__ITEM_TYPE_FILE_REP 837289177Speter && entry->type != SVN_FS_X__ITEM_TYPE_DIR_REP 838289177Speter && entry->type != SVN_FS_X__ITEM_TYPE_FILE_PROPS 839289177Speter && entry->type != SVN_FS_X__ITEM_TYPE_DIR_PROPS 840289177Speter && entry->type != SVN_FS_X__ITEM_TYPE_REPS_CONT)) 841289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 842289177Speter _("No representation found at offset %s " 843289177Speter "for item %s in revision %ld"), 844289177Speter apr_off_t_toa(scratch_pool, offset), 845289177Speter apr_psprintf(scratch_pool, "%" APR_UINT64_T_FMT, 846289177Speter rep->id.number), 847289177Speter revision); 848289177Speter 849289177Speter return SVN_NO_ERROR; 850289177Speter} 851289177Speter 852289177Speter/* . 853289177Speter Do any allocations in POOL. */ 854289177Spetersvn_error_t * 855289177Spetersvn_fs_x__rep_chain_length(int *chain_length, 856289177Speter int *shard_count, 857289177Speter svn_fs_x__representation_t *rep, 858289177Speter svn_fs_t *fs, 859289177Speter apr_pool_t *scratch_pool) 860289177Speter{ 861289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 862289177Speter svn_revnum_t shard_size = ffd->max_files_per_dir; 863289177Speter svn_boolean_t is_delta = FALSE; 864289177Speter int count = 0; 865289177Speter int shards = 1; 866289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set); 867289177Speter svn_revnum_t last_shard = revision / shard_size; 868289177Speter 869289177Speter /* Note that this iteration pool will be used in a non-standard way. 870289177Speter * To reuse open file handles between iterations (e.g. while within the 871289177Speter * same pack file), we only clear this pool once in a while instead of 872289177Speter * at the start of each iteration. */ 873289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 874289177Speter 875289177Speter /* Check whether the length of the deltification chain is acceptable. 876289177Speter * Otherwise, shared reps may form a non-skipping delta chain in 877289177Speter * extreme cases. */ 878289177Speter svn_fs_x__representation_t base_rep = *rep; 879289177Speter 880289177Speter /* re-use open files between iterations */ 881289177Speter shared_file_t *file_hint = NULL; 882289177Speter 883289177Speter svn_fs_x__rep_header_t *header; 884289177Speter 885289177Speter /* follow the delta chain towards the end but for at most 886289177Speter * MAX_CHAIN_LENGTH steps. */ 887289177Speter do 888289177Speter { 889289177Speter rep_state_t *rep_state; 890289177Speter revision = svn_fs_x__get_revnum(base_rep.id.change_set); 891289177Speter if (revision / shard_size != last_shard) 892289177Speter { 893289177Speter last_shard = revision / shard_size; 894289177Speter ++shards; 895289177Speter } 896289177Speter 897289177Speter SVN_ERR(create_rep_state_body(&rep_state, 898289177Speter &header, 899289177Speter &file_hint, 900289177Speter &base_rep, 901289177Speter fs, 902289177Speter iterpool, 903289177Speter iterpool)); 904289177Speter 905289177Speter base_rep.id.change_set 906289177Speter = svn_fs_x__change_set_by_rev(header->base_revision); 907289177Speter base_rep.id.number = header->base_item_index; 908289177Speter base_rep.size = header->base_length; 909289177Speter is_delta = header->type == svn_fs_x__rep_delta; 910289177Speter 911289177Speter /* Clear it the ITERPOOL once in a while. Doing it too frequently 912289177Speter * renders the FILE_HINT ineffective. Doing too infrequently, may 913289177Speter * leave us with too many open file handles. 914289177Speter * 915289177Speter * Note that this is mostly about efficiency, with larger values 916289177Speter * being more efficient, and any non-zero value is legal here. When 917289177Speter * reading deltified contents, we may keep 10s of rev files open at 918289177Speter * the same time and the system has to cope with that. Thus, the 919289177Speter * limit of 16 chosen below is in the same ballpark. 920289177Speter */ 921289177Speter ++count; 922289177Speter if (count % 16 == 0) 923289177Speter { 924289177Speter file_hint = NULL; 925289177Speter svn_pool_clear(iterpool); 926289177Speter } 927289177Speter } 928289177Speter while (is_delta && base_rep.id.change_set); 929289177Speter 930289177Speter *chain_length = count; 931289177Speter *shard_count = shards; 932289177Speter svn_pool_destroy(iterpool); 933289177Speter 934289177Speter return SVN_NO_ERROR; 935289177Speter} 936289177Speter 937289177Speter 938289177Spetertypedef struct rep_read_baton_t 939289177Speter{ 940289177Speter /* The FS from which we're reading. */ 941289177Speter svn_fs_t *fs; 942289177Speter 943289177Speter /* Representation to read. */ 944289177Speter svn_fs_x__representation_t rep; 945289177Speter 946289177Speter /* If not NULL, this is the base for the first delta window in rs_list */ 947289177Speter svn_stringbuf_t *base_window; 948289177Speter 949289177Speter /* The state of all prior delta representations. */ 950289177Speter apr_array_header_t *rs_list; 951289177Speter 952289177Speter /* The plaintext state, if there is a plaintext. */ 953289177Speter rep_state_t *src_state; 954289177Speter 955289177Speter /* The index of the current delta chunk, if we are reading a delta. */ 956289177Speter int chunk_index; 957289177Speter 958289177Speter /* The buffer where we store undeltified data. */ 959289177Speter char *buf; 960289177Speter apr_size_t buf_pos; 961289177Speter apr_size_t buf_len; 962289177Speter 963289177Speter /* A checksum context for summing the data read in order to verify it. 964289177Speter Note: we don't need to use the sha1 checksum because we're only doing 965289177Speter data verification, for which md5 is perfectly safe. */ 966289177Speter svn_checksum_ctx_t *md5_checksum_ctx; 967289177Speter 968289177Speter svn_boolean_t checksum_finalized; 969289177Speter 970289177Speter /* The stored checksum of the representation we are reading, its 971289177Speter length, and the amount we've read so far. Some of this 972289177Speter information is redundant with rs_list and src_state, but it's 973289177Speter convenient for the checksumming code to have it here. */ 974289177Speter unsigned char md5_digest[APR_MD5_DIGESTSIZE]; 975289177Speter 976289177Speter svn_filesize_t len; 977289177Speter svn_filesize_t off; 978289177Speter 979289177Speter /* The key for the fulltext cache for this rep, if there is a 980289177Speter fulltext cache. */ 981289177Speter svn_fs_x__pair_cache_key_t fulltext_cache_key; 982289177Speter /* The text we've been reading, if we're going to cache it. */ 983289177Speter svn_stringbuf_t *current_fulltext; 984289177Speter 985289177Speter /* If not NULL, attempt to read the data from this cache. 986289177Speter Once that lookup fails, reset it to NULL. */ 987289177Speter svn_cache__t *fulltext_cache; 988289177Speter 989289177Speter /* Bytes delivered from the FULLTEXT_CACHE so far. If the next 990289177Speter lookup fails, we need to skip that much data from the reconstructed 991289177Speter window stream before we continue normal operation. */ 992289177Speter svn_filesize_t fulltext_delivered; 993289177Speter 994289177Speter /* Used for temporary allocations during the read. */ 995289177Speter apr_pool_t *scratch_pool; 996289177Speter 997289177Speter /* Pool used to store file handles and other data that is persistant 998289177Speter for the entire stream read. */ 999289177Speter apr_pool_t *filehandle_pool; 1000289177Speter} rep_read_baton_t; 1001289177Speter 1002289177Speter/* Set window key in *KEY to address the window described by RS. 1003289177Speter For convenience, return the KEY. */ 1004289177Speterstatic svn_fs_x__window_cache_key_t * 1005289177Speterget_window_key(svn_fs_x__window_cache_key_t *key, 1006289177Speter rep_state_t *rs) 1007289177Speter{ 1008289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rs->rep_id.change_set); 1009289177Speter assert(revision <= APR_UINT32_MAX); 1010289177Speter 1011289177Speter key->revision = (apr_uint32_t)revision; 1012289177Speter key->item_index = rs->rep_id.number; 1013289177Speter key->chunk_index = rs->chunk_index; 1014289177Speter 1015289177Speter return key; 1016289177Speter} 1017289177Speter 1018289177Speter/* Read the WINDOW_P number CHUNK_INDEX for the representation given in 1019289177Speter * rep state RS from the current FSX session's cache. This will be a 1020289177Speter * no-op and IS_CACHED will be set to FALSE if no cache has been given. 1021289177Speter * If a cache is available IS_CACHED will inform the caller about the 1022289177Speter * success of the lookup. Allocations (of the window in particualar) will 1023289177Speter * be made from POOL. 1024289177Speter * 1025289177Speter * If the information could be found, put RS to CHUNK_INDEX. 1026289177Speter */ 1027289177Speter 1028289177Speter/* Return data type for get_cached_window_sizes_func. 1029289177Speter */ 1030289177Spetertypedef struct window_sizes_t 1031289177Speter{ 1032289177Speter /* length of the txdelta window in its on-disk format */ 1033289177Speter svn_filesize_t packed_len; 1034289177Speter 1035289177Speter /* expanded (and combined) window length */ 1036289177Speter svn_filesize_t target_len; 1037289177Speter} window_sizes_t; 1038289177Speter 1039289177Speter/* Implements svn_cache__partial_getter_func_t extracting the packed 1040289177Speter * and expanded window sizes from a cached window and return the size 1041289177Speter * info as a window_sizes_t* in *OUT. 1042289177Speter */ 1043289177Speterstatic svn_error_t * 1044289177Speterget_cached_window_sizes_func(void **out, 1045289177Speter const void *data, 1046289177Speter apr_size_t data_len, 1047289177Speter void *baton, 1048289177Speter apr_pool_t *pool) 1049289177Speter{ 1050289177Speter const svn_fs_x__txdelta_cached_window_t *window = data; 1051289177Speter const svn_txdelta_window_t *txdelta_window 1052289177Speter = svn_temp_deserializer__ptr(window, (const void **)&window->window); 1053289177Speter 1054289177Speter window_sizes_t *result = apr_palloc(pool, sizeof(*result)); 1055289177Speter result->packed_len = window->end_offset - window->start_offset; 1056289177Speter result->target_len = txdelta_window->tview_len; 1057289177Speter 1058289177Speter *out = result; 1059289177Speter 1060289177Speter return SVN_NO_ERROR; 1061289177Speter} 1062289177Speter 1063289177Speter/* Read the WINDOW_P number CHUNK_INDEX for the representation given in 1064289177Speter * rep state RS from the current FSFS session's cache. This will be a 1065289177Speter * no-op and IS_CACHED will be set to FALSE if no cache has been given. 1066289177Speter * If a cache is available IS_CACHED will inform the caller about the 1067289177Speter * success of the lookup. Allocations of the window in will be made 1068289177Speter * from RESULT_POOL. Use SCRATCH_POOL for temporary allocations. 1069289177Speter * 1070289177Speter * If the information could be found, put RS to CHUNK_INDEX. 1071289177Speter */ 1072289177Speterstatic svn_error_t * 1073289177Speterget_cached_window_sizes(window_sizes_t **sizes, 1074289177Speter rep_state_t *rs, 1075289177Speter svn_boolean_t *is_cached, 1076289177Speter apr_pool_t *pool) 1077289177Speter{ 1078289177Speter if (! rs->window_cache) 1079289177Speter { 1080289177Speter /* txdelta window has not been enabled */ 1081289177Speter *is_cached = FALSE; 1082289177Speter } 1083289177Speter else 1084289177Speter { 1085289177Speter svn_fs_x__window_cache_key_t key = { 0 }; 1086289177Speter SVN_ERR(svn_cache__get_partial((void **)sizes, 1087289177Speter is_cached, 1088289177Speter rs->window_cache, 1089289177Speter get_window_key(&key, rs), 1090289177Speter get_cached_window_sizes_func, 1091289177Speter NULL, 1092289177Speter pool)); 1093289177Speter } 1094289177Speter 1095289177Speter return SVN_NO_ERROR; 1096289177Speter} 1097289177Speter 1098289177Speterstatic svn_error_t * 1099289177Speterget_cached_window(svn_txdelta_window_t **window_p, 1100289177Speter rep_state_t *rs, 1101289177Speter int chunk_index, 1102289177Speter svn_boolean_t *is_cached, 1103289177Speter apr_pool_t *result_pool, 1104289177Speter apr_pool_t *scratch_pool) 1105289177Speter{ 1106289177Speter if (! rs->window_cache) 1107289177Speter { 1108289177Speter /* txdelta window has not been enabled */ 1109289177Speter *is_cached = FALSE; 1110289177Speter } 1111289177Speter else 1112289177Speter { 1113289177Speter /* ask the cache for the desired txdelta window */ 1114289177Speter svn_fs_x__txdelta_cached_window_t *cached_window; 1115289177Speter svn_fs_x__window_cache_key_t key = { 0 }; 1116289177Speter get_window_key(&key, rs); 1117289177Speter key.chunk_index = chunk_index; 1118289177Speter SVN_ERR(svn_cache__get((void **) &cached_window, 1119289177Speter is_cached, 1120289177Speter rs->window_cache, 1121289177Speter &key, 1122289177Speter result_pool)); 1123289177Speter 1124289177Speter if (*is_cached) 1125289177Speter { 1126289177Speter /* found it. Pass it back to the caller. */ 1127289177Speter *window_p = cached_window->window; 1128289177Speter 1129289177Speter /* manipulate the RS as if we just read the data */ 1130289177Speter rs->current = cached_window->end_offset; 1131289177Speter rs->chunk_index = chunk_index; 1132289177Speter } 1133289177Speter } 1134289177Speter 1135289177Speter return SVN_NO_ERROR; 1136289177Speter} 1137289177Speter 1138289177Speter/* Store the WINDOW read for the rep state RS with the given START_OFFSET 1139289177Speter * within the pack / rev file in the current FSX session's cache. This 1140289177Speter * will be a no-op if no cache has been given. 1141289177Speter * Temporary allocations will be made from SCRATCH_POOL. */ 1142289177Speterstatic svn_error_t * 1143289177Speterset_cached_window(svn_txdelta_window_t *window, 1144289177Speter rep_state_t *rs, 1145289177Speter apr_off_t start_offset, 1146289177Speter apr_pool_t *scratch_pool) 1147289177Speter{ 1148289177Speter if (rs->window_cache) 1149289177Speter { 1150289177Speter /* store the window and the first offset _past_ it */ 1151289177Speter svn_fs_x__txdelta_cached_window_t cached_window; 1152289177Speter svn_fs_x__window_cache_key_t key = {0}; 1153289177Speter 1154289177Speter cached_window.window = window; 1155289177Speter cached_window.start_offset = start_offset - rs->start; 1156289177Speter cached_window.end_offset = rs->current; 1157289177Speter 1158289177Speter /* but key it with the start offset because that is the known state 1159289177Speter * when we will look it up */ 1160289177Speter SVN_ERR(svn_cache__set(rs->window_cache, 1161289177Speter get_window_key(&key, rs), 1162289177Speter &cached_window, 1163289177Speter scratch_pool)); 1164289177Speter } 1165289177Speter 1166289177Speter return SVN_NO_ERROR; 1167289177Speter} 1168289177Speter 1169289177Speter/* Read the WINDOW_P for the rep state RS from the current FSX session's 1170289177Speter * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 1171289177Speter * cache has been given. If a cache is available IS_CACHED will inform 1172289177Speter * the caller about the success of the lookup. Allocations (of the window 1173289177Speter * in particular) will be made from POOL. 1174289177Speter */ 1175289177Speterstatic svn_error_t * 1176289177Speterget_cached_combined_window(svn_stringbuf_t **window_p, 1177289177Speter rep_state_t *rs, 1178289177Speter svn_boolean_t *is_cached, 1179289177Speter apr_pool_t *pool) 1180289177Speter{ 1181289177Speter if (! rs->combined_cache) 1182289177Speter { 1183289177Speter /* txdelta window has not been enabled */ 1184289177Speter *is_cached = FALSE; 1185289177Speter } 1186289177Speter else 1187289177Speter { 1188289177Speter /* ask the cache for the desired txdelta window */ 1189289177Speter svn_fs_x__window_cache_key_t key = { 0 }; 1190289177Speter return svn_cache__get((void **)window_p, 1191289177Speter is_cached, 1192289177Speter rs->combined_cache, 1193289177Speter get_window_key(&key, rs), 1194289177Speter pool); 1195289177Speter } 1196289177Speter 1197289177Speter return SVN_NO_ERROR; 1198289177Speter} 1199289177Speter 1200289177Speter/* Store the WINDOW read for the rep state RS in the current FSX session's 1201289177Speter * cache. This will be a no-op if no cache has been given. 1202289177Speter * Temporary allocations will be made from SCRATCH_POOL. */ 1203289177Speterstatic svn_error_t * 1204289177Speterset_cached_combined_window(svn_stringbuf_t *window, 1205289177Speter rep_state_t *rs, 1206289177Speter apr_pool_t *scratch_pool) 1207289177Speter{ 1208289177Speter if (rs->combined_cache) 1209289177Speter { 1210289177Speter /* but key it with the start offset because that is the known state 1211289177Speter * when we will look it up */ 1212289177Speter svn_fs_x__window_cache_key_t key = { 0 }; 1213289177Speter return svn_cache__set(rs->combined_cache, 1214289177Speter get_window_key(&key, rs), 1215289177Speter window, 1216289177Speter scratch_pool); 1217289177Speter } 1218289177Speter 1219289177Speter return SVN_NO_ERROR; 1220289177Speter} 1221289177Speter 1222289177Speter/* Build an array of rep_state structures in *LIST giving the delta 1223289177Speter reps from first_rep to a self-compressed rep. Set *SRC_STATE to 1224289177Speter the container rep we find at the end of the chain, or to NULL if 1225289177Speter the final delta representation is self-compressed. 1226289177Speter The representation to start from is designated by filesystem FS, id 1227289177Speter ID, and representation REP. 1228289177Speter Also, set *WINDOW_P to the base window content for *LIST, if it 1229289177Speter could be found in cache. Otherwise, *LIST will contain the base 1230289177Speter representation for the whole delta chain. 1231289177Speter */ 1232289177Speterstatic svn_error_t * 1233289177Speterbuild_rep_list(apr_array_header_t **list, 1234289177Speter svn_stringbuf_t **window_p, 1235289177Speter rep_state_t **src_state, 1236289177Speter svn_fs_t *fs, 1237289177Speter svn_fs_x__representation_t *first_rep, 1238289177Speter apr_pool_t *result_pool, 1239289177Speter apr_pool_t *scratch_pool) 1240289177Speter{ 1241289177Speter svn_fs_x__representation_t rep; 1242289177Speter rep_state_t *rs = NULL; 1243289177Speter svn_fs_x__rep_header_t *rep_header; 1244289177Speter svn_boolean_t is_cached = FALSE; 1245289177Speter shared_file_t *shared_file = NULL; 1246289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1247289177Speter 1248289177Speter *list = apr_array_make(result_pool, 1, sizeof(rep_state_t *)); 1249289177Speter rep = *first_rep; 1250289177Speter 1251289177Speter /* for the top-level rep, we need the rep_args */ 1252289177Speter SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, &rep, fs, 1253289177Speter result_pool, iterpool)); 1254289177Speter 1255289177Speter while (1) 1256289177Speter { 1257289177Speter svn_pool_clear(iterpool); 1258289177Speter 1259289177Speter /* fetch state, if that has not been done already */ 1260289177Speter if (!rs) 1261289177Speter SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, 1262289177Speter &rep, fs, result_pool, iterpool)); 1263289177Speter 1264289177Speter /* for txn reps and containered reps, there won't be a cached 1265289177Speter * combined window */ 1266289177Speter if (svn_fs_x__is_revision(rep.id.change_set) 1267289177Speter && rep_header->type != svn_fs_x__rep_container) 1268289177Speter SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, 1269289177Speter result_pool)); 1270289177Speter 1271289177Speter if (is_cached) 1272289177Speter { 1273289177Speter /* We already have a reconstructed window in our cache. 1274289177Speter Write a pseudo rep_state with the full length. */ 1275289177Speter rs->start = 0; 1276289177Speter rs->current = 0; 1277289177Speter rs->size = (*window_p)->len; 1278289177Speter *src_state = rs; 1279289177Speter break; 1280289177Speter } 1281289177Speter 1282289177Speter if (rep_header->type == svn_fs_x__rep_container) 1283289177Speter { 1284289177Speter /* This is a container item, so just return the current rep_state. */ 1285289177Speter *src_state = rs; 1286289177Speter break; 1287289177Speter } 1288289177Speter 1289289177Speter /* Push this rep onto the list. If it's self-compressed, we're done. */ 1290289177Speter APR_ARRAY_PUSH(*list, rep_state_t *) = rs; 1291289177Speter if (rep_header->type == svn_fs_x__rep_self_delta) 1292289177Speter { 1293289177Speter *src_state = NULL; 1294289177Speter break; 1295289177Speter } 1296289177Speter 1297289177Speter rep.id.change_set 1298289177Speter = svn_fs_x__change_set_by_rev(rep_header->base_revision); 1299289177Speter rep.id.number = rep_header->base_item_index; 1300289177Speter rep.size = rep_header->base_length; 1301289177Speter 1302289177Speter rs = NULL; 1303289177Speter } 1304289177Speter svn_pool_destroy(iterpool); 1305289177Speter 1306289177Speter return SVN_NO_ERROR; 1307289177Speter} 1308289177Speter 1309289177Speter 1310289177Speter/* Create a rep_read_baton structure for node revision NODEREV in 1311289177Speter filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not 1312289177Speter NULL, it is the rep's key in the fulltext cache, and a stringbuf 1313289177Speter must be allocated to store the text. If rep is mutable, it must be 1314289177Speter refer to file contents. 1315289177Speter 1316289177Speter Allocate the result in RESULT_POOL. This includes the pools within *RB_P. 1317289177Speter */ 1318289177Speterstatic svn_error_t * 1319289177Speterrep_read_get_baton(rep_read_baton_t **rb_p, 1320289177Speter svn_fs_t *fs, 1321289177Speter svn_fs_x__representation_t *rep, 1322289177Speter svn_fs_x__pair_cache_key_t fulltext_cache_key, 1323289177Speter apr_pool_t *result_pool) 1324289177Speter{ 1325289177Speter rep_read_baton_t *b; 1326289177Speter 1327289177Speter b = apr_pcalloc(result_pool, sizeof(*b)); 1328289177Speter b->fs = fs; 1329289177Speter b->rep = *rep; 1330289177Speter b->base_window = NULL; 1331289177Speter b->chunk_index = 0; 1332289177Speter b->buf = NULL; 1333289177Speter b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, 1334289177Speter result_pool); 1335289177Speter b->checksum_finalized = FALSE; 1336289177Speter memcpy(b->md5_digest, rep->md5_digest, sizeof(rep->md5_digest)); 1337289177Speter b->len = rep->expanded_size; 1338289177Speter b->off = 0; 1339289177Speter b->fulltext_cache_key = fulltext_cache_key; 1340289177Speter 1341289177Speter /* Clearable sub-pools. Since they have to remain valid for as long as B 1342289177Speter lives, we can't take them from some scratch pool. The caller of this 1343289177Speter function will have no control over how those subpools will be used. */ 1344289177Speter b->scratch_pool = svn_pool_create(result_pool); 1345289177Speter b->filehandle_pool = svn_pool_create(result_pool); 1346289177Speter b->fulltext_cache = NULL; 1347289177Speter b->fulltext_delivered = 0; 1348289177Speter b->current_fulltext = NULL; 1349289177Speter 1350289177Speter /* Save our output baton. */ 1351289177Speter *rb_p = b; 1352289177Speter 1353289177Speter return SVN_NO_ERROR; 1354289177Speter} 1355289177Speter 1356289177Speter/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta 1357289177Speter window into *NWIN. */ 1358289177Speterstatic svn_error_t * 1359289177Speterread_delta_window(svn_txdelta_window_t **nwin, int this_chunk, 1360289177Speter rep_state_t *rs, apr_pool_t *result_pool, 1361289177Speter apr_pool_t *scratch_pool) 1362289177Speter{ 1363289177Speter svn_boolean_t is_cached; 1364289177Speter apr_off_t start_offset; 1365289177Speter apr_off_t end_offset; 1366289177Speter apr_pool_t *iterpool; 1367289177Speter 1368289177Speter SVN_ERR_ASSERT(rs->chunk_index <= this_chunk); 1369289177Speter 1370289177Speter SVN_ERR(dgb__log_access(rs->sfile->fs, &rs->rep_id, NULL, 1371289177Speter SVN_FS_X__ITEM_TYPE_ANY_REP, scratch_pool)); 1372289177Speter 1373289177Speter /* Read the next window. But first, try to find it in the cache. */ 1374289177Speter SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached, 1375289177Speter result_pool, scratch_pool)); 1376289177Speter if (is_cached) 1377289177Speter return SVN_NO_ERROR; 1378289177Speter 1379289177Speter /* someone has to actually read the data from file. Open it */ 1380289177Speter SVN_ERR(auto_open_shared_file(rs->sfile)); 1381289177Speter 1382289177Speter /* invoke the 'block-read' feature for non-txn data. 1383289177Speter However, don't do that if we are in the middle of some representation, 1384289177Speter because the block is unlikely to contain other data. */ 1385289177Speter if ( rs->chunk_index == 0 1386289177Speter && svn_fs_x__is_revision(rs->rep_id.change_set) 1387289177Speter && rs->window_cache) 1388289177Speter { 1389289177Speter SVN_ERR(block_read(NULL, rs->sfile->fs, &rs->rep_id, 1390289177Speter rs->sfile->rfile, result_pool, scratch_pool)); 1391289177Speter 1392289177Speter /* reading the whole block probably also provided us with the 1393289177Speter desired txdelta window */ 1394289177Speter SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached, 1395289177Speter result_pool, scratch_pool)); 1396289177Speter if (is_cached) 1397289177Speter return SVN_NO_ERROR; 1398289177Speter } 1399289177Speter 1400289177Speter /* data is still not cached -> we need to read it. 1401289177Speter Make sure we have all the necessary info. */ 1402289177Speter SVN_ERR(auto_set_start_offset(rs, scratch_pool)); 1403289177Speter SVN_ERR(auto_read_diff_version(rs, scratch_pool)); 1404289177Speter 1405289177Speter /* RS->FILE may be shared between RS instances -> make sure we point 1406289177Speter * to the right data. */ 1407289177Speter start_offset = rs->start + rs->current; 1408289177Speter SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, scratch_pool)); 1409289177Speter 1410289177Speter /* Skip windows to reach the current chunk if we aren't there yet. */ 1411289177Speter iterpool = svn_pool_create(scratch_pool); 1412289177Speter while (rs->chunk_index < this_chunk) 1413289177Speter { 1414289177Speter apr_file_t *file = rs->sfile->rfile->file; 1415289177Speter svn_pool_clear(iterpool); 1416289177Speter 1417289177Speter SVN_ERR(svn_txdelta_skip_svndiff_window(file, rs->ver, iterpool)); 1418289177Speter rs->chunk_index++; 1419289177Speter SVN_ERR(svn_fs_x__get_file_offset(&start_offset, file, iterpool)); 1420289177Speter 1421289177Speter rs->current = start_offset - rs->start; 1422289177Speter if (rs->current >= rs->size) 1423289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1424289177Speter _("Reading one svndiff window read " 1425289177Speter "beyond the end of the " 1426289177Speter "representation")); 1427289177Speter } 1428289177Speter svn_pool_destroy(iterpool); 1429289177Speter 1430289177Speter /* Actually read the next window. */ 1431289177Speter SVN_ERR(svn_txdelta_read_svndiff_window(nwin, rs->sfile->rfile->stream, 1432289177Speter rs->ver, result_pool)); 1433289177Speter SVN_ERR(get_file_offset(&end_offset, rs, scratch_pool)); 1434289177Speter rs->current = end_offset - rs->start; 1435289177Speter if (rs->current > rs->size) 1436289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1437289177Speter _("Reading one svndiff window read beyond " 1438289177Speter "the end of the representation")); 1439289177Speter 1440289177Speter /* the window has not been cached before, thus cache it now 1441289177Speter * (if caching is used for them at all) */ 1442289177Speter if (svn_fs_x__is_revision(rs->rep_id.change_set)) 1443289177Speter SVN_ERR(set_cached_window(*nwin, rs, start_offset, scratch_pool)); 1444289177Speter 1445289177Speter return SVN_NO_ERROR; 1446289177Speter} 1447289177Speter 1448289177Speter/* Read the whole representation RS and return it in *NWIN. */ 1449289177Speterstatic svn_error_t * 1450289177Speterread_container_window(svn_stringbuf_t **nwin, 1451289177Speter rep_state_t *rs, 1452289177Speter apr_size_t size, 1453289177Speter apr_pool_t *result_pool, 1454289177Speter apr_pool_t *scratch_pool) 1455289177Speter{ 1456289177Speter svn_fs_x__rep_extractor_t *extractor = NULL; 1457289177Speter svn_fs_t *fs = rs->sfile->fs; 1458289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 1459289177Speter svn_fs_x__pair_cache_key_t key; 1460289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rs->rep_id.change_set); 1461289177Speter 1462289177Speter SVN_ERR(auto_set_start_offset(rs, scratch_pool)); 1463289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 1464289177Speter key.second = rs->start; 1465289177Speter 1466289177Speter /* already in cache? */ 1467289177Speter if (ffd->reps_container_cache) 1468289177Speter { 1469289177Speter svn_boolean_t is_cached = FALSE; 1470289177Speter svn_fs_x__reps_baton_t baton; 1471289177Speter baton.fs = fs; 1472289177Speter baton.idx = rs->sub_item; 1473289177Speter 1474289177Speter SVN_ERR(svn_cache__get_partial((void**)&extractor, &is_cached, 1475289177Speter ffd->reps_container_cache, &key, 1476289177Speter svn_fs_x__reps_get_func, &baton, 1477289177Speter result_pool)); 1478289177Speter } 1479289177Speter 1480289177Speter /* read from disk, if necessary */ 1481289177Speter if (extractor == NULL) 1482289177Speter { 1483289177Speter SVN_ERR(auto_open_shared_file(rs->sfile)); 1484289177Speter SVN_ERR(block_read((void **)&extractor, fs, &rs->rep_id, 1485289177Speter rs->sfile->rfile, result_pool, scratch_pool)); 1486289177Speter } 1487289177Speter 1488289177Speter SVN_ERR(svn_fs_x__extractor_drive(nwin, extractor, rs->current, size, 1489289177Speter result_pool, scratch_pool)); 1490289177Speter 1491289177Speter /* Update RS. */ 1492289177Speter rs->current += (apr_off_t)size; 1493289177Speter 1494289177Speter return SVN_NO_ERROR; 1495289177Speter} 1496289177Speter 1497289177Speter/* Get the undeltified window that is a result of combining all deltas 1498289177Speter from the current desired representation identified in *RB with its 1499289177Speter base representation. Store the window in *RESULT. */ 1500289177Speterstatic svn_error_t * 1501289177Speterget_combined_window(svn_stringbuf_t **result, 1502289177Speter rep_read_baton_t *rb) 1503289177Speter{ 1504289177Speter apr_pool_t *pool, *new_pool, *window_pool; 1505289177Speter int i; 1506289177Speter apr_array_header_t *windows; 1507289177Speter svn_stringbuf_t *source, *buf = rb->base_window; 1508289177Speter rep_state_t *rs; 1509289177Speter apr_pool_t *iterpool; 1510289177Speter 1511289177Speter /* Read all windows that we need to combine. This is fine because 1512289177Speter the size of each window is relatively small (100kB) and skip- 1513289177Speter delta limits the number of deltas in a chain to well under 100. 1514289177Speter Stop early if one of them does not depend on its predecessors. */ 1515289177Speter window_pool = svn_pool_create(rb->scratch_pool); 1516289177Speter windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *)); 1517289177Speter iterpool = svn_pool_create(rb->scratch_pool); 1518289177Speter for (i = 0; i < rb->rs_list->nelts; ++i) 1519289177Speter { 1520289177Speter svn_txdelta_window_t *window; 1521289177Speter 1522289177Speter svn_pool_clear(iterpool); 1523289177Speter 1524289177Speter rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *); 1525289177Speter SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool, 1526289177Speter iterpool)); 1527289177Speter 1528289177Speter APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window; 1529289177Speter if (window->src_ops == 0) 1530289177Speter { 1531289177Speter ++i; 1532289177Speter break; 1533289177Speter } 1534289177Speter } 1535289177Speter 1536289177Speter /* Combine in the windows from the other delta reps. */ 1537289177Speter pool = svn_pool_create(rb->scratch_pool); 1538289177Speter for (--i; i >= 0; --i) 1539289177Speter { 1540289177Speter svn_txdelta_window_t *window; 1541289177Speter 1542289177Speter svn_pool_clear(iterpool); 1543289177Speter 1544289177Speter rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *); 1545289177Speter window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *); 1546289177Speter 1547289177Speter /* Maybe, we've got a start representation in a container. If we do, 1548289177Speter read as much data from it as the needed for the txdelta window's 1549289177Speter source view. 1550289177Speter Note that BUF / SOURCE may only be NULL in the first iteration. */ 1551289177Speter source = buf; 1552289177Speter if (source == NULL && rb->src_state != NULL) 1553289177Speter SVN_ERR(read_container_window(&source, rb->src_state, 1554289177Speter window->sview_len, pool, iterpool)); 1555289177Speter 1556289177Speter /* Combine this window with the current one. */ 1557289177Speter new_pool = svn_pool_create(rb->scratch_pool); 1558289177Speter buf = svn_stringbuf_create_ensure(window->tview_len, new_pool); 1559289177Speter buf->len = window->tview_len; 1560289177Speter 1561289177Speter svn_txdelta_apply_instructions(window, source ? source->data : NULL, 1562289177Speter buf->data, &buf->len); 1563289177Speter if (buf->len != window->tview_len) 1564289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1565289177Speter _("svndiff window length is " 1566289177Speter "corrupt")); 1567289177Speter 1568289177Speter /* Cache windows only if the whole rep content could be read as a 1569289177Speter single chunk. Only then will no other chunk need a deeper RS 1570289177Speter list than the cached chunk. */ 1571289177Speter if ( (rb->chunk_index == 0) && (rs->current == rs->size) 1572289177Speter && svn_fs_x__is_revision(rs->rep_id.change_set)) 1573289177Speter SVN_ERR(set_cached_combined_window(buf, rs, new_pool)); 1574289177Speter 1575289177Speter rs->chunk_index++; 1576289177Speter 1577289177Speter /* Cycle pools so that we only need to hold three windows at a time. */ 1578289177Speter svn_pool_destroy(pool); 1579289177Speter pool = new_pool; 1580289177Speter } 1581289177Speter svn_pool_destroy(iterpool); 1582289177Speter 1583289177Speter svn_pool_destroy(window_pool); 1584289177Speter 1585289177Speter *result = buf; 1586289177Speter return SVN_NO_ERROR; 1587289177Speter} 1588289177Speter 1589289177Speter/* Returns whether or not the expanded fulltext of the file is cachable 1590289177Speter * based on its size SIZE. The decision depends on the cache used by RB. 1591289177Speter */ 1592289177Speterstatic svn_boolean_t 1593289177Speterfulltext_size_is_cachable(svn_fs_x__data_t *ffd, 1594289177Speter svn_filesize_t size) 1595289177Speter{ 1596289177Speter return (size < APR_SIZE_MAX) 1597289177Speter && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size); 1598289177Speter} 1599289177Speter 1600289177Speter/* Close method used on streams returned by read_representation(). 1601289177Speter */ 1602289177Speterstatic svn_error_t * 1603289177Speterrep_read_contents_close(void *baton) 1604289177Speter{ 1605289177Speter rep_read_baton_t *rb = baton; 1606289177Speter 1607289177Speter svn_pool_destroy(rb->scratch_pool); 1608289177Speter svn_pool_destroy(rb->filehandle_pool); 1609289177Speter 1610289177Speter return SVN_NO_ERROR; 1611289177Speter} 1612289177Speter 1613289177Speter/* Inialize the representation read state RS for the given REP_HEADER and 1614289177Speter * p2l index ENTRY. If not NULL, assign FILE and STREAM to RS. 1615289177Speter * Allocate all sub-structures of RS in RESULT_POOL. 1616289177Speter */ 1617289177Speterstatic svn_error_t * 1618289177Speterinit_rep_state(rep_state_t *rs, 1619289177Speter svn_fs_x__rep_header_t *rep_header, 1620289177Speter svn_fs_t *fs, 1621289177Speter svn_fs_x__revision_file_t *rev_file, 1622289177Speter svn_fs_x__p2l_entry_t* entry, 1623289177Speter apr_pool_t *result_pool) 1624289177Speter{ 1625289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 1626289177Speter shared_file_t *shared_file = apr_pcalloc(result_pool, sizeof(*shared_file)); 1627289177Speter 1628289177Speter /* this function does not apply to representation containers */ 1629289177Speter SVN_ERR_ASSERT(entry->type >= SVN_FS_X__ITEM_TYPE_FILE_REP 1630289177Speter && entry->type <= SVN_FS_X__ITEM_TYPE_DIR_PROPS); 1631289177Speter SVN_ERR_ASSERT(entry->item_count == 1); 1632289177Speter 1633289177Speter shared_file->rfile = rev_file; 1634289177Speter shared_file->fs = fs; 1635289177Speter shared_file->revision = svn_fs_x__get_revnum(entry->items[0].change_set); 1636289177Speter shared_file->pool = result_pool; 1637289177Speter 1638289177Speter rs->sfile = shared_file; 1639289177Speter rs->rep_id = entry->items[0]; 1640289177Speter rs->header_size = rep_header->header_size; 1641289177Speter rs->start = entry->offset + rs->header_size; 1642289177Speter rs->current = 4; 1643289177Speter rs->size = entry->size - rep_header->header_size - 7; 1644289177Speter rs->ver = 1; 1645289177Speter rs->chunk_index = 0; 1646289177Speter rs->window_cache = ffd->txdelta_window_cache; 1647289177Speter rs->combined_cache = ffd->combined_window_cache; 1648289177Speter 1649289177Speter return SVN_NO_ERROR; 1650289177Speter} 1651289177Speter 1652289177Speter/* Walk through all windows in the representation addressed by RS in FS 1653289177Speter * (excluding the delta bases) and put those not already cached into the 1654289177Speter * window caches. If MAX_OFFSET is not -1, don't read windows that start 1655289177Speter * at or beyond that offset. As a side effect, return the total sum of all 1656289177Speter * expanded window sizes in *FULLTEXT_LEN. 1657289177Speter * Use SCRATCH_POOL for temporary allocations. 1658289177Speter */ 1659289177Speterstatic svn_error_t * 1660289177Spetercache_windows(svn_filesize_t *fulltext_len, 1661289177Speter svn_fs_t *fs, 1662289177Speter rep_state_t *rs, 1663289177Speter apr_off_t max_offset, 1664289177Speter apr_pool_t *scratch_pool) 1665289177Speter{ 1666289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1667289177Speter *fulltext_len = 0; 1668289177Speter 1669289177Speter while (rs->current < rs->size) 1670289177Speter { 1671289177Speter svn_boolean_t is_cached = FALSE; 1672289177Speter window_sizes_t *window_sizes; 1673289177Speter 1674289177Speter svn_pool_clear(iterpool); 1675289177Speter if (max_offset != -1 && rs->start + rs->current >= max_offset) 1676289177Speter { 1677289177Speter svn_pool_destroy(iterpool); 1678289177Speter return SVN_NO_ERROR; 1679289177Speter } 1680289177Speter 1681289177Speter /* efficiently skip windows that are still being cached instead 1682289177Speter * of fully decoding them */ 1683289177Speter SVN_ERR(get_cached_window_sizes(&window_sizes, rs, &is_cached, 1684289177Speter iterpool)); 1685289177Speter if (is_cached) 1686289177Speter { 1687289177Speter *fulltext_len += window_sizes->target_len; 1688289177Speter rs->current += window_sizes->packed_len; 1689289177Speter } 1690289177Speter else 1691289177Speter { 1692289177Speter svn_txdelta_window_t *window; 1693289177Speter apr_off_t start_offset = rs->start + rs->current; 1694289177Speter apr_off_t end_offset; 1695289177Speter apr_off_t block_start; 1696289177Speter 1697289177Speter /* navigate to & read the current window */ 1698289177Speter SVN_ERR(rs_aligned_seek(rs, &block_start, start_offset, iterpool)); 1699289177Speter SVN_ERR(svn_txdelta_read_svndiff_window(&window, 1700289177Speter rs->sfile->rfile->stream, 1701289177Speter rs->ver, iterpool)); 1702289177Speter 1703289177Speter /* aggregate expanded window size */ 1704289177Speter *fulltext_len += window->tview_len; 1705289177Speter 1706289177Speter /* determine on-disk window size */ 1707289177Speter SVN_ERR(svn_fs_x__get_file_offset(&end_offset, 1708289177Speter rs->sfile->rfile->file, 1709289177Speter iterpool)); 1710289177Speter rs->current = end_offset - rs->start; 1711289177Speter if (rs->current > rs->size) 1712289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1713289177Speter _("Reading one svndiff window read beyond " 1714289177Speter "the end of the representation")); 1715289177Speter 1716289177Speter /* if the window has not been cached before, cache it now 1717289177Speter * (if caching is used for them at all) */ 1718289177Speter if (!is_cached) 1719289177Speter SVN_ERR(set_cached_window(window, rs, start_offset, iterpool)); 1720289177Speter } 1721289177Speter 1722289177Speter rs->chunk_index++; 1723289177Speter } 1724289177Speter 1725289177Speter svn_pool_destroy(iterpool); 1726289177Speter 1727289177Speter return SVN_NO_ERROR; 1728289177Speter} 1729289177Speter 1730289177Speter/* Try to get the representation header identified by KEY from FS's cache. 1731289177Speter * If it has not been cached, read it from the current position in STREAM 1732289177Speter * and put it into the cache (if caching has been enabled for rep headers). 1733289177Speter * Return the result in *REP_HEADER. Use POOL for allocations. 1734289177Speter */ 1735289177Speterstatic svn_error_t * 1736289177Speterread_rep_header(svn_fs_x__rep_header_t **rep_header, 1737289177Speter svn_fs_t *fs, 1738289177Speter svn_stream_t *stream, 1739289177Speter svn_fs_x__representation_cache_key_t *key, 1740289177Speter apr_pool_t *pool) 1741289177Speter{ 1742289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 1743289177Speter svn_boolean_t is_cached = FALSE; 1744289177Speter 1745289177Speter if (ffd->rep_header_cache) 1746289177Speter { 1747289177Speter SVN_ERR(svn_cache__get((void**)rep_header, &is_cached, 1748289177Speter ffd->rep_header_cache, key, pool)); 1749289177Speter if (is_cached) 1750289177Speter return SVN_NO_ERROR; 1751289177Speter } 1752289177Speter 1753289177Speter SVN_ERR(svn_fs_x__read_rep_header(rep_header, stream, pool, pool)); 1754289177Speter 1755289177Speter if (ffd->rep_header_cache) 1756289177Speter SVN_ERR(svn_cache__set(ffd->rep_header_cache, key, *rep_header, pool)); 1757289177Speter 1758289177Speter return SVN_NO_ERROR; 1759289177Speter} 1760289177Speter 1761289177Spetersvn_error_t * 1762289177Spetersvn_fs_x__get_representation_length(svn_filesize_t *packed_len, 1763289177Speter svn_filesize_t *expanded_len, 1764289177Speter svn_fs_t *fs, 1765289177Speter svn_fs_x__revision_file_t *rev_file, 1766289177Speter svn_fs_x__p2l_entry_t* entry, 1767289177Speter apr_pool_t *scratch_pool) 1768289177Speter{ 1769289177Speter svn_fs_x__representation_cache_key_t key = { 0 }; 1770289177Speter rep_state_t rs = { 0 }; 1771289177Speter svn_fs_x__rep_header_t *rep_header; 1772289177Speter 1773289177Speter /* this function does not apply to representation containers */ 1774289177Speter SVN_ERR_ASSERT(entry->type >= SVN_FS_X__ITEM_TYPE_FILE_REP 1775289177Speter && entry->type <= SVN_FS_X__ITEM_TYPE_DIR_PROPS); 1776289177Speter SVN_ERR_ASSERT(entry->item_count == 1); 1777289177Speter 1778289177Speter /* get / read the representation header */ 1779289177Speter key.revision = svn_fs_x__get_revnum(entry->items[0].change_set); 1780289177Speter key.is_packed = svn_fs_x__is_packed_rev(fs, key.revision); 1781289177Speter key.item_index = entry->items[0].number; 1782289177Speter SVN_ERR(read_rep_header(&rep_header, fs, rev_file->stream, &key, 1783289177Speter scratch_pool)); 1784289177Speter 1785289177Speter /* prepare representation reader state (rs) structure */ 1786289177Speter SVN_ERR(init_rep_state(&rs, rep_header, fs, rev_file, entry, 1787289177Speter scratch_pool)); 1788289177Speter 1789289177Speter /* RS->SFILE may be shared between RS instances -> make sure we point 1790289177Speter * to the right data. */ 1791289177Speter *packed_len = rs.size; 1792289177Speter SVN_ERR(cache_windows(expanded_len, fs, &rs, -1, scratch_pool)); 1793289177Speter 1794289177Speter return SVN_NO_ERROR; 1795289177Speter} 1796289177Speter 1797289177Speter/* Return the next *LEN bytes of the rep from our plain / delta windows 1798289177Speter and store them in *BUF. */ 1799289177Speterstatic svn_error_t * 1800289177Speterget_contents_from_windows(rep_read_baton_t *rb, 1801289177Speter char *buf, 1802289177Speter apr_size_t *len) 1803289177Speter{ 1804289177Speter apr_size_t copy_len, remaining = *len; 1805289177Speter char *cur = buf; 1806289177Speter rep_state_t *rs; 1807289177Speter 1808289177Speter /* Special case for when there are no delta reps, only a 1809289177Speter containered text. */ 1810289177Speter if (rb->rs_list->nelts == 0 && rb->buf == NULL) 1811289177Speter { 1812289177Speter copy_len = remaining; 1813289177Speter rs = rb->src_state; 1814289177Speter 1815289177Speter /* reps in containers don't have a header */ 1816289177Speter if (rs->header_size == 0 && rb->base_window == NULL) 1817289177Speter { 1818289177Speter /* RS->SIZE is unreliable here because it is based upon 1819289177Speter * the delta rep size _before_ putting the data into a 1820289177Speter * a container. */ 1821289177Speter SVN_ERR(read_container_window(&rb->base_window, rs, rb->len, 1822289177Speter rb->scratch_pool, rb->scratch_pool)); 1823289177Speter rs->current -= rb->base_window->len; 1824289177Speter } 1825289177Speter 1826289177Speter if (rb->base_window != NULL) 1827289177Speter { 1828289177Speter /* We got the desired rep directly from the cache. 1829289177Speter This is where we need the pseudo rep_state created 1830289177Speter by build_rep_list(). */ 1831289177Speter apr_size_t offset = (apr_size_t)rs->current; 1832289177Speter if (copy_len + offset > rb->base_window->len) 1833289177Speter copy_len = offset < rb->base_window->len 1834289177Speter ? rb->base_window->len - offset 1835289177Speter : 0ul; 1836289177Speter 1837289177Speter memcpy (cur, rb->base_window->data + offset, copy_len); 1838289177Speter } 1839289177Speter 1840289177Speter rs->current += copy_len; 1841289177Speter *len = copy_len; 1842289177Speter return SVN_NO_ERROR; 1843289177Speter } 1844289177Speter 1845289177Speter while (remaining > 0) 1846289177Speter { 1847289177Speter /* If we have buffered data from a previous chunk, use that. */ 1848289177Speter if (rb->buf) 1849289177Speter { 1850289177Speter /* Determine how much to copy from the buffer. */ 1851289177Speter copy_len = rb->buf_len - rb->buf_pos; 1852289177Speter if (copy_len > remaining) 1853289177Speter copy_len = remaining; 1854289177Speter 1855289177Speter /* Actually copy the data. */ 1856289177Speter memcpy(cur, rb->buf + rb->buf_pos, copy_len); 1857289177Speter rb->buf_pos += copy_len; 1858289177Speter cur += copy_len; 1859289177Speter remaining -= copy_len; 1860289177Speter 1861289177Speter /* If the buffer is all used up, clear it and empty the 1862289177Speter local pool. */ 1863289177Speter if (rb->buf_pos == rb->buf_len) 1864289177Speter { 1865289177Speter svn_pool_clear(rb->scratch_pool); 1866289177Speter rb->buf = NULL; 1867289177Speter } 1868289177Speter } 1869289177Speter else 1870289177Speter { 1871289177Speter svn_stringbuf_t *sbuf = NULL; 1872289177Speter 1873289177Speter rs = APR_ARRAY_IDX(rb->rs_list, 0, rep_state_t *); 1874289177Speter if (rs->current == rs->size) 1875289177Speter break; 1876289177Speter 1877289177Speter /* Get more buffered data by evaluating a chunk. */ 1878289177Speter SVN_ERR(get_combined_window(&sbuf, rb)); 1879289177Speter 1880289177Speter rb->chunk_index++; 1881289177Speter rb->buf_len = sbuf->len; 1882289177Speter rb->buf = sbuf->data; 1883289177Speter rb->buf_pos = 0; 1884289177Speter } 1885289177Speter } 1886289177Speter 1887289177Speter *len = cur - buf; 1888289177Speter 1889289177Speter return SVN_NO_ERROR; 1890289177Speter} 1891289177Speter 1892289177Speter/* Baton type for get_fulltext_partial. */ 1893289177Spetertypedef struct fulltext_baton_t 1894289177Speter{ 1895289177Speter /* Target buffer to write to; of at least LEN bytes. */ 1896289177Speter char *buffer; 1897289177Speter 1898289177Speter /* Offset within the respective fulltext at which we shall start to 1899289177Speter copy data into BUFFER. */ 1900289177Speter apr_size_t start; 1901289177Speter 1902289177Speter /* Number of bytes to copy. The actual amount may be less in case 1903289177Speter the fulltext is short(er). */ 1904289177Speter apr_size_t len; 1905289177Speter 1906289177Speter /* Number of bytes actually copied into BUFFER. */ 1907289177Speter apr_size_t read; 1908289177Speter} fulltext_baton_t; 1909289177Speter 1910289177Speter/* Implement svn_cache__partial_getter_func_t for fulltext caches. 1911289177Speter * From the fulltext in DATA, we copy the range specified by the 1912289177Speter * fulltext_baton_t* BATON into the buffer provided by that baton. 1913289177Speter * OUT and RESULT_POOL are not used. 1914289177Speter */ 1915289177Speterstatic svn_error_t * 1916289177Speterget_fulltext_partial(void **out, 1917289177Speter const void *data, 1918289177Speter apr_size_t data_len, 1919289177Speter void *baton, 1920289177Speter apr_pool_t *result_pool) 1921289177Speter{ 1922289177Speter fulltext_baton_t *fulltext_baton = baton; 1923289177Speter 1924289177Speter /* We cached the fulltext with an NUL appended to it. */ 1925289177Speter apr_size_t fulltext_len = data_len - 1; 1926289177Speter 1927289177Speter /* Clip the copy range to what the fulltext size allows. */ 1928289177Speter apr_size_t start = MIN(fulltext_baton->start, fulltext_len); 1929289177Speter fulltext_baton->read = MIN(fulltext_len - start, fulltext_baton->len); 1930289177Speter 1931289177Speter /* Copy the data to the output buffer and be done. */ 1932289177Speter memcpy(fulltext_baton->buffer, (const char *)data + start, 1933289177Speter fulltext_baton->read); 1934289177Speter 1935289177Speter return SVN_NO_ERROR; 1936289177Speter} 1937289177Speter 1938289177Speter/* Find the fulltext specified in BATON in the fulltext cache given 1939289177Speter * as well by BATON. If that succeeds, set *CACHED to TRUE and copy 1940289177Speter * up to the next *LEN bytes into BUFFER. Set *LEN to the actual 1941289177Speter * number of bytes copied. 1942289177Speter */ 1943289177Speterstatic svn_error_t * 1944289177Speterget_contents_from_fulltext(svn_boolean_t *cached, 1945289177Speter rep_read_baton_t *baton, 1946289177Speter char *buffer, 1947289177Speter apr_size_t *len) 1948289177Speter{ 1949289177Speter void *dummy; 1950289177Speter fulltext_baton_t fulltext_baton; 1951289177Speter 1952289177Speter SVN_ERR_ASSERT((apr_size_t)baton->fulltext_delivered 1953289177Speter == baton->fulltext_delivered); 1954289177Speter fulltext_baton.buffer = buffer; 1955289177Speter fulltext_baton.start = (apr_size_t)baton->fulltext_delivered; 1956289177Speter fulltext_baton.len = *len; 1957289177Speter fulltext_baton.read = 0; 1958289177Speter 1959289177Speter SVN_ERR(svn_cache__get_partial(&dummy, cached, baton->fulltext_cache, 1960289177Speter &baton->fulltext_cache_key, 1961289177Speter get_fulltext_partial, &fulltext_baton, 1962289177Speter baton->scratch_pool)); 1963289177Speter 1964289177Speter if (*cached) 1965289177Speter { 1966289177Speter baton->fulltext_delivered += fulltext_baton.read; 1967289177Speter *len = fulltext_baton.read; 1968289177Speter } 1969289177Speter 1970289177Speter return SVN_NO_ERROR; 1971289177Speter} 1972289177Speter 1973289177Speter/* Determine the optimal size of a string buf that shall receive a 1974289177Speter * (full-) text of NEEDED bytes. 1975289177Speter * 1976289177Speter * The critical point is that those buffers may be very large and 1977289177Speter * can cause memory fragmentation. We apply simple heuristics to 1978289177Speter * make fragmentation less likely. 1979289177Speter */ 1980289177Speterstatic apr_size_t 1981289177Speteroptimimal_allocation_size(apr_size_t needed) 1982289177Speter{ 1983289177Speter /* For all allocations, assume some overhead that is shared between 1984289177Speter * OS memory managemnt, APR memory management and svn_stringbuf_t. */ 1985289177Speter const apr_size_t overhead = 0x400; 1986289177Speter apr_size_t optimal; 1987289177Speter 1988289177Speter /* If an allocation size if safe for other ephemeral buffers, it should 1989289177Speter * be safe for ours. */ 1990289177Speter if (needed <= SVN__STREAM_CHUNK_SIZE) 1991289177Speter return needed; 1992289177Speter 1993289177Speter /* Paranoia edge case: 1994289177Speter * Skip our heuristics if they created arithmetical overflow. 1995289177Speter * Beware to make this test work for NEEDED = APR_SIZE_MAX as well! */ 1996289177Speter if (needed >= APR_SIZE_MAX / 2 - overhead) 1997289177Speter return needed; 1998289177Speter 1999289177Speter /* As per definition SVN__STREAM_CHUNK_SIZE is a power of two. 2000289177Speter * Since we know NEEDED to be larger than that, use it as the 2001289177Speter * starting point. 2002289177Speter * 2003289177Speter * Heuristics: Allocate a power-of-two number of bytes that fit 2004289177Speter * NEEDED plus some OVERHEAD. The APR allocator 2005289177Speter * will round it up to the next full page size. 2006289177Speter */ 2007289177Speter optimal = SVN__STREAM_CHUNK_SIZE; 2008289177Speter while (optimal - overhead < needed) 2009289177Speter optimal *= 2; 2010289177Speter 2011289177Speter /* This is above or equal to NEEDED. */ 2012289177Speter return optimal - overhead; 2013289177Speter} 2014289177Speter 2015289177Speter/* After a fulltext cache lookup failure, we will continue to read from 2016289177Speter * combined delta or plain windows. However, we must first make that data 2017289177Speter * stream in BATON catch up tho the position LEN already delivered from the 2018289177Speter * fulltext cache. Also, we need to store the reconstructed fulltext if we 2019289177Speter * want to cache it at the end. 2020289177Speter */ 2021289177Speterstatic svn_error_t * 2022289177Speterskip_contents(rep_read_baton_t *baton, 2023289177Speter svn_filesize_t len) 2024289177Speter{ 2025289177Speter svn_error_t *err = SVN_NO_ERROR; 2026289177Speter 2027289177Speter /* Do we want to cache the reconstructed fulltext? */ 2028289177Speter if (SVN_IS_VALID_REVNUM(baton->fulltext_cache_key.revision)) 2029289177Speter { 2030289177Speter char *buffer; 2031289177Speter svn_filesize_t to_alloc = MAX(len, baton->len); 2032289177Speter 2033289177Speter /* This should only be happening if BATON->LEN and LEN are 2034289177Speter * cacheable, implying they fit into memory. */ 2035289177Speter SVN_ERR_ASSERT((apr_size_t)to_alloc == to_alloc); 2036289177Speter 2037289177Speter /* Allocate the fulltext buffer. */ 2038289177Speter baton->current_fulltext = svn_stringbuf_create_ensure( 2039289177Speter optimimal_allocation_size((apr_size_t)to_alloc), 2040289177Speter baton->filehandle_pool); 2041289177Speter 2042289177Speter /* Read LEN bytes from the window stream and store the data 2043289177Speter * in the fulltext buffer (will be filled by further reads later). */ 2044289177Speter baton->current_fulltext->len = (apr_size_t)len; 2045289177Speter baton->current_fulltext->data[(apr_size_t)len] = 0; 2046289177Speter 2047289177Speter buffer = baton->current_fulltext->data; 2048289177Speter while (len > 0 && !err) 2049289177Speter { 2050289177Speter apr_size_t to_read = (apr_size_t)len; 2051289177Speter err = get_contents_from_windows(baton, buffer, &to_read); 2052289177Speter len -= to_read; 2053289177Speter buffer += to_read; 2054289177Speter } 2055289177Speter } 2056289177Speter else if (len > 0) 2057289177Speter { 2058289177Speter /* Simply drain LEN bytes from the window stream. */ 2059289177Speter apr_pool_t *subpool = svn_pool_create(baton->scratch_pool); 2060289177Speter char *buffer = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE); 2061289177Speter 2062289177Speter while (len > 0 && !err) 2063289177Speter { 2064289177Speter apr_size_t to_read = len > SVN__STREAM_CHUNK_SIZE 2065289177Speter ? SVN__STREAM_CHUNK_SIZE 2066289177Speter : (apr_size_t)len; 2067289177Speter 2068289177Speter err = get_contents_from_windows(baton, buffer, &to_read); 2069289177Speter len -= to_read; 2070289177Speter } 2071289177Speter 2072289177Speter svn_pool_destroy(subpool); 2073289177Speter } 2074289177Speter 2075289177Speter return svn_error_trace(err); 2076289177Speter} 2077289177Speter 2078289177Speter/* BATON is of type `rep_read_baton_t'; read the next *LEN bytes of the 2079289177Speter representation and store them in *BUF. Sum as we read and verify 2080289177Speter the MD5 sum at the end. */ 2081289177Speterstatic svn_error_t * 2082289177Speterrep_read_contents(void *baton, 2083289177Speter char *buf, 2084289177Speter apr_size_t *len) 2085289177Speter{ 2086289177Speter rep_read_baton_t *rb = baton; 2087289177Speter 2088289177Speter /* Get data from the fulltext cache for as long as we can. */ 2089289177Speter if (rb->fulltext_cache) 2090289177Speter { 2091289177Speter svn_boolean_t cached; 2092289177Speter SVN_ERR(get_contents_from_fulltext(&cached, rb, buf, len)); 2093289177Speter if (cached) 2094289177Speter return SVN_NO_ERROR; 2095289177Speter 2096289177Speter /* Cache miss. From now on, we will never read from the fulltext 2097289177Speter * cache for this representation anymore. */ 2098289177Speter rb->fulltext_cache = NULL; 2099289177Speter } 2100289177Speter 2101289177Speter /* No fulltext cache to help us. We must read from the window stream. */ 2102289177Speter if (!rb->rs_list) 2103289177Speter { 2104289177Speter /* Window stream not initialized, yet. Do it now. */ 2105289177Speter SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window, 2106289177Speter &rb->src_state, rb->fs, &rb->rep, 2107289177Speter rb->filehandle_pool, rb->scratch_pool)); 2108289177Speter 2109289177Speter /* In case we did read from the fulltext cache before, make the 2110289177Speter * window stream catch up. Also, initialize the fulltext buffer 2111289177Speter * if we want to cache the fulltext at the end. */ 2112289177Speter SVN_ERR(skip_contents(rb, rb->fulltext_delivered)); 2113289177Speter } 2114289177Speter 2115289177Speter /* Get the next block of data. */ 2116289177Speter SVN_ERR(get_contents_from_windows(rb, buf, len)); 2117289177Speter 2118289177Speter if (rb->current_fulltext) 2119289177Speter svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); 2120289177Speter 2121289177Speter /* Perform checksumming. We want to check the checksum as soon as 2122289177Speter the last byte of data is read, in case the caller never performs 2123289177Speter a short read, but we don't want to finalize the MD5 context 2124289177Speter twice. */ 2125289177Speter if (!rb->checksum_finalized) 2126289177Speter { 2127289177Speter SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len)); 2128289177Speter rb->off += *len; 2129289177Speter if (rb->off == rb->len) 2130289177Speter { 2131289177Speter svn_checksum_t *md5_checksum; 2132289177Speter svn_checksum_t expected; 2133289177Speter expected.kind = svn_checksum_md5; 2134289177Speter expected.digest = rb->md5_digest; 2135289177Speter 2136289177Speter rb->checksum_finalized = TRUE; 2137289177Speter SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx, 2138289177Speter rb->scratch_pool)); 2139289177Speter if (!svn_checksum_match(md5_checksum, &expected)) 2140289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, 2141289177Speter svn_checksum_mismatch_err(&expected, md5_checksum, 2142289177Speter rb->scratch_pool, 2143289177Speter _("Checksum mismatch while reading representation")), 2144289177Speter NULL); 2145289177Speter } 2146289177Speter } 2147289177Speter 2148289177Speter if (rb->off == rb->len && rb->current_fulltext) 2149289177Speter { 2150289177Speter svn_fs_x__data_t *ffd = rb->fs->fsap_data; 2151289177Speter SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key, 2152289177Speter rb->current_fulltext, rb->scratch_pool)); 2153289177Speter rb->current_fulltext = NULL; 2154289177Speter } 2155289177Speter 2156289177Speter return SVN_NO_ERROR; 2157289177Speter} 2158289177Speter 2159289177Spetersvn_error_t * 2160289177Spetersvn_fs_x__get_contents(svn_stream_t **contents_p, 2161289177Speter svn_fs_t *fs, 2162289177Speter svn_fs_x__representation_t *rep, 2163289177Speter svn_boolean_t cache_fulltext, 2164289177Speter apr_pool_t *result_pool) 2165289177Speter{ 2166289177Speter if (! rep) 2167289177Speter { 2168289177Speter *contents_p = svn_stream_empty(result_pool); 2169289177Speter } 2170289177Speter else 2171289177Speter { 2172289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2173289177Speter svn_filesize_t len = rep->expanded_size; 2174289177Speter rep_read_baton_t *rb; 2175289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set); 2176289177Speter 2177289177Speter svn_fs_x__pair_cache_key_t fulltext_cache_key = { 0 }; 2178289177Speter fulltext_cache_key.revision = revision; 2179289177Speter fulltext_cache_key.second = rep->id.number; 2180289177Speter 2181289177Speter /* Initialize the reader baton. Some members may added lazily 2182289177Speter * while reading from the stream */ 2183289177Speter SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, 2184289177Speter result_pool)); 2185289177Speter 2186289177Speter /* Make the stream attempt fulltext cache lookups if the fulltext 2187289177Speter * is cacheable. If it is not, then also don't try to buffer and 2188289177Speter * cache it. */ 2189289177Speter if (ffd->fulltext_cache && cache_fulltext 2190289177Speter && SVN_IS_VALID_REVNUM(revision) 2191289177Speter && fulltext_size_is_cachable(ffd, len)) 2192289177Speter { 2193289177Speter rb->fulltext_cache = ffd->fulltext_cache; 2194289177Speter } 2195289177Speter else 2196289177Speter { 2197289177Speter /* This will also prevent the reconstructed fulltext from being 2198289177Speter put into the cache. */ 2199289177Speter rb->fulltext_cache_key.revision = SVN_INVALID_REVNUM; 2200289177Speter } 2201289177Speter 2202289177Speter *contents_p = svn_stream_create(rb, result_pool); 2203289177Speter svn_stream_set_read2(*contents_p, NULL /* only full read support */, 2204289177Speter rep_read_contents); 2205289177Speter svn_stream_set_close(*contents_p, rep_read_contents_close); 2206289177Speter } 2207289177Speter 2208289177Speter return SVN_NO_ERROR; 2209289177Speter} 2210289177Speter 2211289177Speter 2212289177Speter/* Baton for cache_access_wrapper. Wraps the original parameters of 2213289177Speter * svn_fs_x__try_process_file_content(). 2214289177Speter */ 2215289177Spetertypedef struct cache_access_wrapper_baton_t 2216289177Speter{ 2217289177Speter svn_fs_process_contents_func_t func; 2218289177Speter void* baton; 2219289177Speter} cache_access_wrapper_baton_t; 2220289177Speter 2221289177Speter/* Wrapper to translate between svn_fs_process_contents_func_t and 2222289177Speter * svn_cache__partial_getter_func_t. 2223289177Speter */ 2224289177Speterstatic svn_error_t * 2225289177Spetercache_access_wrapper(void **out, 2226289177Speter const void *data, 2227289177Speter apr_size_t data_len, 2228289177Speter void *baton, 2229289177Speter apr_pool_t *pool) 2230289177Speter{ 2231289177Speter cache_access_wrapper_baton_t *wrapper_baton = baton; 2232289177Speter 2233289177Speter SVN_ERR(wrapper_baton->func((const unsigned char *)data, 2234289177Speter data_len - 1, /* cache adds terminating 0 */ 2235289177Speter wrapper_baton->baton, 2236289177Speter pool)); 2237289177Speter 2238289177Speter /* non-NULL value to signal the calling cache that all went well */ 2239289177Speter *out = baton; 2240289177Speter 2241289177Speter return SVN_NO_ERROR; 2242289177Speter} 2243289177Speter 2244289177Spetersvn_error_t * 2245289177Spetersvn_fs_x__try_process_file_contents(svn_boolean_t *success, 2246289177Speter svn_fs_t *fs, 2247289177Speter svn_fs_x__noderev_t *noderev, 2248289177Speter svn_fs_process_contents_func_t processor, 2249289177Speter void* baton, 2250289177Speter apr_pool_t *scratch_pool) 2251289177Speter{ 2252289177Speter svn_fs_x__representation_t *rep = noderev->data_rep; 2253289177Speter if (rep) 2254289177Speter { 2255289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2256289177Speter svn_fs_x__pair_cache_key_t fulltext_cache_key = { 0 }; 2257289177Speter 2258289177Speter fulltext_cache_key.revision = svn_fs_x__get_revnum(rep->id.change_set); 2259289177Speter fulltext_cache_key.second = rep->id.number; 2260289177Speter if (ffd->fulltext_cache 2261289177Speter && SVN_IS_VALID_REVNUM(fulltext_cache_key.revision) 2262289177Speter && fulltext_size_is_cachable(ffd, rep->expanded_size)) 2263289177Speter { 2264289177Speter cache_access_wrapper_baton_t wrapper_baton; 2265289177Speter void *dummy = NULL; 2266289177Speter 2267289177Speter wrapper_baton.func = processor; 2268289177Speter wrapper_baton.baton = baton; 2269289177Speter return svn_cache__get_partial(&dummy, success, 2270289177Speter ffd->fulltext_cache, 2271289177Speter &fulltext_cache_key, 2272289177Speter cache_access_wrapper, 2273289177Speter &wrapper_baton, 2274289177Speter scratch_pool); 2275289177Speter } 2276289177Speter } 2277289177Speter 2278289177Speter *success = FALSE; 2279289177Speter return SVN_NO_ERROR; 2280289177Speter} 2281289177Speter 2282289177Speter/* Baton used when reading delta windows. */ 2283289177Spetertypedef struct delta_read_baton_t 2284289177Speter{ 2285289177Speter struct rep_state_t *rs; 2286289177Speter unsigned char md5_digest[APR_MD5_DIGESTSIZE]; 2287289177Speter} delta_read_baton_t; 2288289177Speter 2289289177Speter/* This implements the svn_txdelta_next_window_fn_t interface. */ 2290289177Speterstatic svn_error_t * 2291289177Speterdelta_read_next_window(svn_txdelta_window_t **window, 2292289177Speter void *baton, 2293289177Speter apr_pool_t *pool) 2294289177Speter{ 2295289177Speter delta_read_baton_t *drb = baton; 2296289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 2297289177Speter 2298289177Speter *window = NULL; 2299289177Speter if (drb->rs->current < drb->rs->size) 2300289177Speter { 2301289177Speter SVN_ERR(read_delta_window(window, drb->rs->chunk_index, drb->rs, pool, 2302289177Speter scratch_pool)); 2303289177Speter drb->rs->chunk_index++; 2304289177Speter } 2305289177Speter 2306289177Speter svn_pool_destroy(scratch_pool); 2307289177Speter 2308289177Speter return SVN_NO_ERROR; 2309289177Speter} 2310289177Speter 2311289177Speter/* This implements the svn_txdelta_md5_digest_fn_t interface. */ 2312289177Speterstatic const unsigned char * 2313289177Speterdelta_read_md5_digest(void *baton) 2314289177Speter{ 2315289177Speter delta_read_baton_t *drb = baton; 2316289177Speter return drb->md5_digest; 2317289177Speter} 2318289177Speter 2319289177Speter/* Return a txdelta stream for on-disk representation REP_STATE 2320289177Speter * of TARGET. Allocate the result in RESULT_POOL. 2321289177Speter */ 2322289177Speterstatic svn_txdelta_stream_t * 2323289177Speterget_storaged_delta_stream(rep_state_t *rep_state, 2324289177Speter svn_fs_x__noderev_t *target, 2325289177Speter apr_pool_t *result_pool) 2326289177Speter{ 2327289177Speter /* Create the delta read baton. */ 2328289177Speter delta_read_baton_t *drb = apr_pcalloc(result_pool, sizeof(*drb)); 2329289177Speter drb->rs = rep_state; 2330289177Speter memcpy(drb->md5_digest, target->data_rep->md5_digest, 2331289177Speter sizeof(drb->md5_digest)); 2332289177Speter return svn_txdelta_stream_create(drb, delta_read_next_window, 2333289177Speter delta_read_md5_digest, result_pool); 2334289177Speter} 2335289177Speter 2336289177Spetersvn_error_t * 2337289177Spetersvn_fs_x__get_file_delta_stream(svn_txdelta_stream_t **stream_p, 2338289177Speter svn_fs_t *fs, 2339289177Speter svn_fs_x__noderev_t *source, 2340289177Speter svn_fs_x__noderev_t *target, 2341289177Speter apr_pool_t *result_pool, 2342289177Speter apr_pool_t *scratch_pool) 2343289177Speter{ 2344289177Speter svn_stream_t *source_stream, *target_stream; 2345289177Speter rep_state_t *rep_state; 2346289177Speter svn_fs_x__rep_header_t *rep_header; 2347289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2348289177Speter 2349289177Speter /* Try a shortcut: if the target is stored as a delta against the source, 2350289177Speter then just use that delta. However, prefer using the fulltext cache 2351289177Speter whenever that is available. */ 2352289177Speter if (target->data_rep && (source || !ffd->fulltext_cache)) 2353289177Speter { 2354289177Speter /* Read target's base rep if any. */ 2355289177Speter SVN_ERR(create_rep_state(&rep_state, &rep_header, NULL, 2356289177Speter target->data_rep, fs, result_pool, 2357289177Speter scratch_pool)); 2358289177Speter 2359289177Speter /* Try a shortcut: if the target is stored as a delta against the source, 2360289177Speter then just use that delta. */ 2361289177Speter if (source && source->data_rep && target->data_rep) 2362289177Speter { 2363289177Speter /* If that matches source, then use this delta as is. 2364289177Speter Note that we want an actual delta here. E.g. a self-delta would 2365289177Speter not be good enough. */ 2366289177Speter if (rep_header->type == svn_fs_x__rep_delta 2367289177Speter && rep_header->base_revision 2368289177Speter == svn_fs_x__get_revnum(source->data_rep->id.change_set) 2369289177Speter && rep_header->base_item_index == source->data_rep->id.number) 2370289177Speter { 2371289177Speter *stream_p = get_storaged_delta_stream(rep_state, target, 2372289177Speter result_pool); 2373289177Speter return SVN_NO_ERROR; 2374289177Speter } 2375289177Speter } 2376289177Speter else if (!source) 2377289177Speter { 2378289177Speter /* We want a self-delta. There is a fair chance that TARGET got 2379289177Speter added in this revision and is already stored in the requested 2380289177Speter format. */ 2381289177Speter if (rep_header->type == svn_fs_x__rep_self_delta) 2382289177Speter { 2383289177Speter *stream_p = get_storaged_delta_stream(rep_state, target, 2384289177Speter result_pool); 2385289177Speter return SVN_NO_ERROR; 2386289177Speter } 2387289177Speter } 2388289177Speter 2389289177Speter /* Don't keep file handles open for longer than necessary. */ 2390289177Speter if (rep_state->sfile->rfile) 2391289177Speter { 2392289177Speter SVN_ERR(svn_fs_x__close_revision_file(rep_state->sfile->rfile)); 2393289177Speter rep_state->sfile->rfile = NULL; 2394289177Speter } 2395289177Speter } 2396289177Speter 2397289177Speter /* Read both fulltexts and construct a delta. */ 2398289177Speter if (source) 2399289177Speter SVN_ERR(svn_fs_x__get_contents(&source_stream, fs, source->data_rep, 2400289177Speter TRUE, result_pool)); 2401289177Speter else 2402289177Speter source_stream = svn_stream_empty(result_pool); 2403289177Speter 2404289177Speter SVN_ERR(svn_fs_x__get_contents(&target_stream, fs, target->data_rep, 2405289177Speter TRUE, result_pool)); 2406289177Speter 2407289177Speter /* Because source and target stream will already verify their content, 2408289177Speter * there is no need to do this once more. In particular if the stream 2409289177Speter * content is being fetched from cache. */ 2410289177Speter svn_txdelta2(stream_p, source_stream, target_stream, FALSE, result_pool); 2411289177Speter 2412289177Speter return SVN_NO_ERROR; 2413289177Speter} 2414289177Speter 2415289177Speter/* Return TRUE when all svn_fs_x__dirent_t* in ENTRIES are already sorted 2416289177Speter by their respective name. */ 2417289177Speterstatic svn_boolean_t 2418289177Spetersorted(apr_array_header_t *entries) 2419289177Speter{ 2420289177Speter int i; 2421289177Speter 2422289177Speter const svn_fs_x__dirent_t * const *dirents = (const void *)entries->elts; 2423289177Speter for (i = 0; i < entries->nelts-1; ++i) 2424289177Speter if (strcmp(dirents[i]->name, dirents[i+1]->name) > 0) 2425289177Speter return FALSE; 2426289177Speter 2427289177Speter return TRUE; 2428289177Speter} 2429289177Speter 2430289177Speter/* Compare the names of the two dirents given in **A and **B. */ 2431289177Speterstatic int 2432289177Spetercompare_dirents(const void *a, 2433289177Speter const void *b) 2434289177Speter{ 2435289177Speter const svn_fs_x__dirent_t *lhs = *((const svn_fs_x__dirent_t * const *) a); 2436289177Speter const svn_fs_x__dirent_t *rhs = *((const svn_fs_x__dirent_t * const *) b); 2437289177Speter 2438289177Speter return strcmp(lhs->name, rhs->name); 2439289177Speter} 2440289177Speter 2441289177Speter/* Compare the name of the dirents given in **A with the C string in *B. */ 2442289177Speterstatic int 2443289177Spetercompare_dirent_name(const void *a, 2444289177Speter const void *b) 2445289177Speter{ 2446289177Speter const svn_fs_x__dirent_t *lhs = *((const svn_fs_x__dirent_t * const *) a); 2447289177Speter const char *rhs = b; 2448289177Speter 2449289177Speter return strcmp(lhs->name, rhs); 2450289177Speter} 2451289177Speter 2452289177Speter/* Into ENTRIES, read all directories entries from the key-value text in 2453289177Speter * STREAM. If INCREMENTAL is TRUE, read until the end of the STREAM and 2454289177Speter * update the data. ID is provided for nicer error messages. 2455289177Speter */ 2456289177Speterstatic svn_error_t * 2457289177Speterread_dir_entries(apr_array_header_t *entries, 2458289177Speter svn_stream_t *stream, 2459289177Speter svn_boolean_t incremental, 2460289177Speter const svn_fs_x__id_t *id, 2461289177Speter apr_pool_t *result_pool, 2462289177Speter apr_pool_t *scratch_pool) 2463289177Speter{ 2464289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2465289177Speter apr_hash_t *hash = incremental ? svn_hash__make(scratch_pool) : NULL; 2466289177Speter const char *terminator = SVN_HASH_TERMINATOR; 2467289177Speter 2468289177Speter /* Read until the terminator (non-incremental) or the end of STREAM 2469289177Speter (incremental mode). In the latter mode, we use a temporary HASH 2470289177Speter to make updating and removing entries cheaper. */ 2471289177Speter while (1) 2472289177Speter { 2473289177Speter svn_hash__entry_t entry; 2474289177Speter svn_fs_x__dirent_t *dirent; 2475289177Speter char *str; 2476289177Speter 2477289177Speter svn_pool_clear(iterpool); 2478289177Speter SVN_ERR(svn_hash__read_entry(&entry, stream, terminator, 2479289177Speter incremental, iterpool)); 2480289177Speter 2481289177Speter /* End of directory? */ 2482289177Speter if (entry.key == NULL) 2483289177Speter { 2484289177Speter /* In incremental mode, we skip the terminator and read the 2485289177Speter increments following it until the end of the stream. */ 2486289177Speter if (incremental && terminator) 2487289177Speter terminator = NULL; 2488289177Speter else 2489289177Speter break; 2490289177Speter } 2491289177Speter 2492289177Speter /* Deleted entry? */ 2493289177Speter if (entry.val == NULL) 2494289177Speter { 2495289177Speter /* We must be in incremental mode */ 2496289177Speter assert(hash); 2497289177Speter apr_hash_set(hash, entry.key, entry.keylen, NULL); 2498289177Speter continue; 2499289177Speter } 2500289177Speter 2501289177Speter /* Add a new directory entry. */ 2502289177Speter dirent = apr_pcalloc(result_pool, sizeof(*dirent)); 2503289177Speter dirent->name = apr_pstrmemdup(result_pool, entry.key, entry.keylen); 2504289177Speter 2505289177Speter str = svn_cstring_tokenize(" ", &entry.val); 2506289177Speter if (str == NULL) 2507289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2508289177Speter _("Directory entry corrupt in '%s'"), 2509289177Speter svn_fs_x__id_unparse(id, scratch_pool)->data); 2510289177Speter 2511289177Speter if (strcmp(str, SVN_FS_X__KIND_FILE) == 0) 2512289177Speter { 2513289177Speter dirent->kind = svn_node_file; 2514289177Speter } 2515289177Speter else if (strcmp(str, SVN_FS_X__KIND_DIR) == 0) 2516289177Speter { 2517289177Speter dirent->kind = svn_node_dir; 2518289177Speter } 2519289177Speter else 2520289177Speter { 2521289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2522289177Speter _("Directory entry corrupt in '%s'"), 2523289177Speter svn_fs_x__id_unparse(id, scratch_pool)->data); 2524289177Speter } 2525289177Speter 2526289177Speter str = svn_cstring_tokenize(" ", &entry.val); 2527289177Speter if (str == NULL) 2528289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2529289177Speter _("Directory entry corrupt in '%s'"), 2530289177Speter svn_fs_x__id_unparse(id, scratch_pool)->data); 2531289177Speter 2532289177Speter SVN_ERR(svn_fs_x__id_parse(&dirent->id, str)); 2533289177Speter 2534289177Speter /* In incremental mode, update the hash; otherwise, write to the 2535289177Speter * final array. */ 2536289177Speter if (incremental) 2537289177Speter apr_hash_set(hash, dirent->name, entry.keylen, dirent); 2538289177Speter else 2539289177Speter APR_ARRAY_PUSH(entries, svn_fs_x__dirent_t *) = dirent; 2540289177Speter } 2541289177Speter 2542289177Speter /* Convert container to a sorted array. */ 2543289177Speter if (incremental) 2544289177Speter { 2545289177Speter apr_hash_index_t *hi; 2546289177Speter for (hi = apr_hash_first(iterpool, hash); hi; hi = apr_hash_next(hi)) 2547289177Speter APR_ARRAY_PUSH(entries, svn_fs_x__dirent_t *) = apr_hash_this_val(hi); 2548289177Speter } 2549289177Speter 2550289177Speter if (!sorted(entries)) 2551289177Speter svn_sort__array(entries, compare_dirents); 2552289177Speter 2553289177Speter svn_pool_destroy(iterpool); 2554289177Speter 2555289177Speter return SVN_NO_ERROR; 2556289177Speter} 2557289177Speter 2558289177Speter/* Fetch the contents of a directory into ENTRIES. Values are stored 2559289177Speter as filename to string mappings; further conversion is necessary to 2560289177Speter convert them into svn_fs_x__dirent_t values. */ 2561289177Speterstatic svn_error_t * 2562289177Speterget_dir_contents(apr_array_header_t **entries, 2563289177Speter svn_fs_t *fs, 2564289177Speter svn_fs_x__noderev_t *noderev, 2565289177Speter apr_pool_t *result_pool, 2566289177Speter apr_pool_t *scratch_pool) 2567289177Speter{ 2568289177Speter svn_stream_t *contents; 2569289177Speter const svn_fs_x__id_t *id = &noderev->noderev_id; 2570289177Speter 2571289177Speter *entries = apr_array_make(result_pool, 16, sizeof(svn_fs_x__dirent_t *)); 2572289177Speter if (noderev->data_rep 2573289177Speter && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set)) 2574289177Speter { 2575289177Speter const char *filename 2576289177Speter = svn_fs_x__path_txn_node_children(fs, id, scratch_pool, 2577289177Speter scratch_pool); 2578289177Speter 2579289177Speter /* The representation is mutable. Read the old directory 2580289177Speter contents from the mutable children file, followed by the 2581289177Speter changes we've made in this transaction. */ 2582289177Speter SVN_ERR(svn_stream_open_readonly(&contents, filename, scratch_pool, 2583289177Speter scratch_pool)); 2584289177Speter SVN_ERR(read_dir_entries(*entries, contents, TRUE, id, 2585289177Speter result_pool, scratch_pool)); 2586289177Speter SVN_ERR(svn_stream_close(contents)); 2587289177Speter } 2588289177Speter else if (noderev->data_rep) 2589289177Speter { 2590289177Speter /* Undeltify content before parsing it. Otherwise, we could only 2591289177Speter * parse it byte-by-byte. 2592289177Speter */ 2593289177Speter apr_size_t len = noderev->data_rep->expanded_size; 2594289177Speter svn_stringbuf_t *text; 2595289177Speter 2596289177Speter /* The representation is immutable. Read it normally. */ 2597289177Speter SVN_ERR(svn_fs_x__get_contents(&contents, fs, noderev->data_rep, 2598289177Speter FALSE, scratch_pool)); 2599289177Speter SVN_ERR(svn_stringbuf_from_stream(&text, contents, len, scratch_pool)); 2600289177Speter SVN_ERR(svn_stream_close(contents)); 2601289177Speter 2602289177Speter /* de-serialize hash */ 2603289177Speter contents = svn_stream_from_stringbuf(text, scratch_pool); 2604289177Speter SVN_ERR(read_dir_entries(*entries, contents, FALSE, id, 2605289177Speter result_pool, scratch_pool)); 2606289177Speter } 2607289177Speter 2608289177Speter return SVN_NO_ERROR; 2609289177Speter} 2610289177Speter 2611289177Speter 2612289177Speter/* Return the cache object in FS responsible to storing the directory the 2613289177Speter * NODEREV plus the corresponding pre-allocated *KEY. 2614289177Speter */ 2615289177Speterstatic svn_cache__t * 2616289177Speterlocate_dir_cache(svn_fs_t *fs, 2617289177Speter svn_fs_x__id_t *key, 2618289177Speter svn_fs_x__noderev_t *noderev) 2619289177Speter{ 2620289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2621289177Speter if (svn_fs_x__is_txn(noderev->noderev_id.change_set)) 2622289177Speter { 2623289177Speter /* data in txns must be addressed by ID since the representation has 2624289177Speter not been created, yet. */ 2625289177Speter *key = noderev->noderev_id; 2626289177Speter } 2627289177Speter else 2628289177Speter { 2629289177Speter /* committed data can use simple rev,item pairs */ 2630289177Speter if (noderev->data_rep) 2631289177Speter { 2632289177Speter *key = noderev->data_rep->id; 2633289177Speter } 2634289177Speter else 2635289177Speter { 2636289177Speter /* no data rep -> empty directory. 2637289177Speter Use a key that does definitely not clash with non-NULL reps. */ 2638289177Speter key->change_set = SVN_FS_X__INVALID_CHANGE_SET; 2639289177Speter key->number = SVN_FS_X__ITEM_INDEX_UNUSED; 2640289177Speter } 2641289177Speter } 2642289177Speter 2643289177Speter return ffd->dir_cache; 2644289177Speter} 2645289177Speter 2646289177Spetersvn_error_t * 2647289177Spetersvn_fs_x__rep_contents_dir(apr_array_header_t **entries_p, 2648289177Speter svn_fs_t *fs, 2649289177Speter svn_fs_x__noderev_t *noderev, 2650289177Speter apr_pool_t *result_pool, 2651289177Speter apr_pool_t *scratch_pool) 2652289177Speter{ 2653289177Speter svn_fs_x__id_t key; 2654289177Speter 2655289177Speter /* find the cache we may use */ 2656289177Speter svn_cache__t *cache = locate_dir_cache(fs, &key, noderev); 2657289177Speter if (cache) 2658289177Speter { 2659289177Speter svn_boolean_t found; 2660289177Speter 2661289177Speter SVN_ERR(svn_cache__get((void **)entries_p, &found, cache, &key, 2662289177Speter result_pool)); 2663289177Speter if (found) 2664289177Speter return SVN_NO_ERROR; 2665289177Speter } 2666289177Speter 2667289177Speter /* Read in the directory contents. */ 2668289177Speter SVN_ERR(get_dir_contents(entries_p, fs, noderev, result_pool, 2669289177Speter scratch_pool)); 2670289177Speter 2671289177Speter /* Update the cache, if we are to use one. */ 2672289177Speter if (cache) 2673289177Speter SVN_ERR(svn_cache__set(cache, &key, *entries_p, scratch_pool)); 2674289177Speter 2675289177Speter return SVN_NO_ERROR; 2676289177Speter} 2677289177Speter 2678289177Spetersvn_fs_x__dirent_t * 2679289177Spetersvn_fs_x__find_dir_entry(apr_array_header_t *entries, 2680289177Speter const char *name, 2681289177Speter int *hint) 2682289177Speter{ 2683289177Speter svn_fs_x__dirent_t **result 2684289177Speter = svn_sort__array_lookup(entries, name, hint, compare_dirent_name); 2685289177Speter return result ? *result : NULL; 2686289177Speter} 2687289177Speter 2688289177Spetersvn_error_t * 2689289177Spetersvn_fs_x__rep_contents_dir_entry(svn_fs_x__dirent_t **dirent, 2690289177Speter svn_fs_t *fs, 2691289177Speter svn_fs_x__noderev_t *noderev, 2692289177Speter const char *name, 2693289177Speter apr_size_t *hint, 2694289177Speter apr_pool_t *result_pool, 2695289177Speter apr_pool_t *scratch_pool) 2696289177Speter{ 2697289177Speter svn_boolean_t found = FALSE; 2698289177Speter 2699289177Speter /* find the cache we may use */ 2700289177Speter svn_fs_x__id_t key; 2701289177Speter svn_cache__t *cache = locate_dir_cache(fs, &key, noderev); 2702289177Speter if (cache) 2703289177Speter { 2704289177Speter svn_fs_x__ede_baton_t baton; 2705289177Speter baton.hint = *hint; 2706289177Speter baton.name = name; 2707289177Speter 2708289177Speter /* Cache lookup. */ 2709289177Speter SVN_ERR(svn_cache__get_partial((void **)dirent, 2710289177Speter &found, 2711289177Speter cache, 2712289177Speter &key, 2713289177Speter svn_fs_x__extract_dir_entry, 2714289177Speter &baton, 2715289177Speter result_pool)); 2716289177Speter 2717289177Speter /* Remember the new clue only if we found something at that spot. */ 2718289177Speter if (found) 2719289177Speter *hint = baton.hint; 2720289177Speter } 2721289177Speter 2722289177Speter /* fetch data from disk if we did not find it in the cache */ 2723289177Speter if (! found) 2724289177Speter { 2725289177Speter apr_array_header_t *entries; 2726289177Speter svn_fs_x__dirent_t *entry; 2727289177Speter svn_fs_x__dirent_t *entry_copy = NULL; 2728289177Speter 2729289177Speter /* read the dir from the file system. It will probably be put it 2730289177Speter into the cache for faster lookup in future calls. */ 2731289177Speter SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, noderev, 2732289177Speter scratch_pool, scratch_pool)); 2733289177Speter 2734289177Speter /* find desired entry and return a copy in POOL, if found */ 2735289177Speter entry = svn_fs_x__find_dir_entry(entries, name, NULL); 2736289177Speter if (entry) 2737289177Speter { 2738289177Speter entry_copy = apr_pmemdup(result_pool, entry, sizeof(*entry_copy)); 2739289177Speter entry_copy->name = apr_pstrdup(result_pool, entry->name); 2740289177Speter } 2741289177Speter 2742289177Speter *dirent = entry_copy; 2743289177Speter } 2744289177Speter 2745289177Speter return SVN_NO_ERROR; 2746289177Speter} 2747289177Speter 2748289177Spetersvn_error_t * 2749289177Spetersvn_fs_x__get_proplist(apr_hash_t **proplist_p, 2750289177Speter svn_fs_t *fs, 2751289177Speter svn_fs_x__noderev_t *noderev, 2752289177Speter apr_pool_t *result_pool, 2753289177Speter apr_pool_t *scratch_pool) 2754289177Speter{ 2755289177Speter apr_hash_t *proplist; 2756289177Speter svn_stream_t *stream; 2757289177Speter const svn_fs_x__id_t *noderev_id = &noderev->noderev_id; 2758289177Speter 2759289177Speter if (noderev->prop_rep 2760289177Speter && !svn_fs_x__is_revision(noderev->prop_rep->id.change_set)) 2761289177Speter { 2762289177Speter const char *filename = svn_fs_x__path_txn_node_props(fs, noderev_id, 2763289177Speter scratch_pool, 2764289177Speter scratch_pool); 2765289177Speter proplist = apr_hash_make(result_pool); 2766289177Speter 2767289177Speter SVN_ERR(svn_stream_open_readonly(&stream, filename, scratch_pool, 2768289177Speter scratch_pool)); 2769289177Speter SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, 2770289177Speter result_pool)); 2771289177Speter SVN_ERR(svn_stream_close(stream)); 2772289177Speter } 2773289177Speter else if (noderev->prop_rep) 2774289177Speter { 2775289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2776289177Speter svn_fs_x__representation_t *rep = noderev->prop_rep; 2777289177Speter svn_fs_x__pair_cache_key_t key = { 0 }; 2778289177Speter 2779289177Speter key.revision = svn_fs_x__get_revnum(rep->id.change_set); 2780289177Speter key.second = rep->id.number; 2781289177Speter if (ffd->properties_cache && SVN_IS_VALID_REVNUM(key.revision)) 2782289177Speter { 2783289177Speter svn_boolean_t is_cached; 2784289177Speter SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 2785289177Speter ffd->properties_cache, &key, result_pool)); 2786289177Speter if (is_cached) 2787289177Speter return SVN_NO_ERROR; 2788289177Speter } 2789289177Speter 2790289177Speter proplist = apr_hash_make(result_pool); 2791289177Speter SVN_ERR(svn_fs_x__get_contents(&stream, fs, noderev->prop_rep, FALSE, 2792289177Speter scratch_pool)); 2793289177Speter SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, 2794289177Speter result_pool)); 2795289177Speter SVN_ERR(svn_stream_close(stream)); 2796289177Speter 2797289177Speter if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->id.change_set)) 2798289177Speter SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, 2799289177Speter scratch_pool)); 2800289177Speter } 2801289177Speter else 2802289177Speter { 2803289177Speter /* return an empty prop list if the node doesn't have any props */ 2804289177Speter proplist = apr_hash_make(result_pool); 2805289177Speter } 2806289177Speter 2807289177Speter *proplist_p = proplist; 2808289177Speter 2809289177Speter return SVN_NO_ERROR; 2810289177Speter} 2811289177Speter 2812289177Speter 2813289177Speter 2814289177Spetersvn_error_t * 2815289177Spetersvn_fs_x__get_changes(apr_array_header_t **changes, 2816289177Speter svn_fs_t *fs, 2817289177Speter svn_revnum_t rev, 2818289177Speter apr_pool_t *result_pool) 2819289177Speter{ 2820289177Speter svn_fs_x__revision_file_t *revision_file; 2821289177Speter svn_boolean_t found; 2822289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2823289177Speter apr_pool_t *scratch_pool = svn_pool_create(result_pool); 2824289177Speter 2825289177Speter svn_fs_x__id_t id; 2826289177Speter id.change_set = svn_fs_x__change_set_by_rev(rev); 2827289177Speter id.number = SVN_FS_X__ITEM_INDEX_CHANGES; 2828289177Speter 2829289177Speter /* Provide revision file. */ 2830289177Speter 2831289177Speter SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool)); 2832289177Speter SVN_ERR(svn_fs_x__open_pack_or_rev_file(&revision_file, fs, rev, 2833289177Speter scratch_pool, scratch_pool)); 2834289177Speter 2835289177Speter /* try cache lookup first */ 2836289177Speter 2837289177Speter if (ffd->changes_container_cache && svn_fs_x__is_packed_rev(fs, rev)) 2838289177Speter { 2839289177Speter apr_off_t offset; 2840289177Speter apr_uint32_t sub_item; 2841289177Speter svn_fs_x__pair_cache_key_t key; 2842289177Speter 2843289177Speter SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, revision_file, 2844289177Speter &id, scratch_pool)); 2845289177Speter key.revision = svn_fs_x__packed_base_rev(fs, rev); 2846289177Speter key.second = offset; 2847289177Speter 2848289177Speter SVN_ERR(svn_cache__get_partial((void **)changes, &found, 2849289177Speter ffd->changes_container_cache, &key, 2850289177Speter svn_fs_x__changes_get_list_func, 2851289177Speter &sub_item, result_pool)); 2852289177Speter } 2853289177Speter else if (ffd->changes_cache) 2854289177Speter { 2855289177Speter SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache, 2856289177Speter &rev, result_pool)); 2857289177Speter } 2858289177Speter else 2859289177Speter { 2860289177Speter found = FALSE; 2861289177Speter } 2862289177Speter 2863289177Speter if (!found) 2864289177Speter { 2865289177Speter /* 'block-read' will also provide us with the desired data */ 2866289177Speter SVN_ERR(block_read((void **)changes, fs, &id, revision_file, 2867289177Speter result_pool, scratch_pool)); 2868289177Speter 2869289177Speter SVN_ERR(svn_fs_x__close_revision_file(revision_file)); 2870289177Speter } 2871289177Speter 2872289177Speter SVN_ERR(dgb__log_access(fs, &id, *changes, SVN_FS_X__ITEM_TYPE_CHANGES, 2873289177Speter scratch_pool)); 2874289177Speter 2875289177Speter svn_pool_destroy(scratch_pool); 2876289177Speter return SVN_NO_ERROR; 2877289177Speter} 2878289177Speter 2879289177Speter/* Fetch the representation data (header, txdelta / plain windows) 2880289177Speter * addressed by ENTRY->ITEM in FS and cache it if caches are enabled. 2881289177Speter * Read the data from the already open FILE and the wrapping 2882289177Speter * STREAM object. If MAX_OFFSET is not -1, don't read windows that start 2883289177Speter * at or beyond that offset. Use SCRATCH_POOL for temporary allocations. 2884289177Speter */ 2885289177Speterstatic svn_error_t * 2886289177Speterblock_read_contents(svn_fs_t *fs, 2887289177Speter svn_fs_x__revision_file_t *rev_file, 2888289177Speter svn_fs_x__p2l_entry_t* entry, 2889289177Speter svn_fs_x__pair_cache_key_t *key, 2890289177Speter apr_off_t max_offset, 2891289177Speter apr_pool_t *scratch_pool) 2892289177Speter{ 2893289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2894289177Speter svn_fs_x__representation_cache_key_t header_key = { 0 }; 2895289177Speter rep_state_t rs = { 0 }; 2896289177Speter svn_filesize_t fulltext_len; 2897289177Speter svn_fs_x__rep_header_t *rep_header; 2898289177Speter 2899289177Speter if (!ffd->txdelta_window_cache || !ffd->combined_window_cache) 2900289177Speter return SVN_NO_ERROR; 2901289177Speter 2902289177Speter header_key.revision = (apr_int32_t)key->revision; 2903289177Speter header_key.is_packed = svn_fs_x__is_packed_rev(fs, header_key.revision); 2904289177Speter header_key.item_index = key->second; 2905289177Speter 2906289177Speter SVN_ERR(read_rep_header(&rep_header, fs, rev_file->stream, &header_key, 2907289177Speter scratch_pool)); 2908289177Speter SVN_ERR(init_rep_state(&rs, rep_header, fs, rev_file, entry, scratch_pool)); 2909289177Speter SVN_ERR(cache_windows(&fulltext_len, fs, &rs, max_offset, scratch_pool)); 2910289177Speter 2911289177Speter return SVN_NO_ERROR; 2912289177Speter} 2913289177Speter 2914289177Speter/* For the given REV_FILE in FS, in *STREAM return a stream covering the 2915289177Speter * item specified by ENTRY. Also, verify the item's content by low-level 2916289177Speter * checksum. Allocate the result in POOL. 2917289177Speter */ 2918289177Speterstatic svn_error_t * 2919289177Speterread_item(svn_stream_t **stream, 2920289177Speter svn_fs_t *fs, 2921289177Speter svn_fs_x__revision_file_t *rev_file, 2922289177Speter svn_fs_x__p2l_entry_t* entry, 2923289177Speter apr_pool_t *pool) 2924289177Speter{ 2925289177Speter apr_uint32_t digest; 2926289177Speter svn_checksum_t *expected, *actual; 2927289177Speter apr_uint32_t plain_digest; 2928289177Speter 2929289177Speter /* Read item into string buffer. */ 2930289177Speter svn_stringbuf_t *text = svn_stringbuf_create_ensure(entry->size, pool); 2931289177Speter text->len = entry->size; 2932289177Speter text->data[text->len] = 0; 2933289177Speter SVN_ERR(svn_io_file_read_full2(rev_file->file, text->data, text->len, 2934289177Speter NULL, NULL, pool)); 2935289177Speter 2936289177Speter /* Return (construct, calculate) stream and checksum. */ 2937289177Speter *stream = svn_stream_from_stringbuf(text, pool); 2938289177Speter digest = svn__fnv1a_32x4(text->data, text->len); 2939289177Speter 2940289177Speter /* Checksums will match most of the time. */ 2941289177Speter if (entry->fnv1_checksum == digest) 2942289177Speter return SVN_NO_ERROR; 2943289177Speter 2944289177Speter /* Construct proper checksum objects from their digests to allow for 2945289177Speter * nice error messages. */ 2946289177Speter plain_digest = htonl(entry->fnv1_checksum); 2947289177Speter expected = svn_checksum__from_digest_fnv1a_32x4( 2948289177Speter (const unsigned char *)&plain_digest, pool); 2949289177Speter plain_digest = htonl(digest); 2950289177Speter actual = svn_checksum__from_digest_fnv1a_32x4( 2951289177Speter (const unsigned char *)&plain_digest, pool); 2952289177Speter 2953289177Speter /* Construct the full error message with all the info we have. */ 2954289177Speter return svn_checksum_mismatch_err(expected, actual, pool, 2955289177Speter _("Low-level checksum mismatch while reading\n" 2956289177Speter "%s bytes of meta data at offset %s "), 2957289177Speter apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->size), 2958289177Speter apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->offset)); 2959289177Speter} 2960289177Speter 2961289177Speter/* Read all txdelta / plain windows following REP_HEADER in FS as described 2962289177Speter * by ENTRY. Read the data from the already open FILE and the wrapping 2963289177Speter * STREAM object. If MAX_OFFSET is not -1, don't read windows that start 2964289177Speter * at or beyond that offset. Use SCRATCH_POOL for temporary allocations. 2965289177Speter * If caching is not enabled, this is a no-op. 2966289177Speter */ 2967289177Speterstatic svn_error_t * 2968289177Speterblock_read_changes(apr_array_header_t **changes, 2969289177Speter svn_fs_t *fs, 2970289177Speter svn_fs_x__revision_file_t *rev_file, 2971289177Speter svn_fs_x__p2l_entry_t* entry, 2972289177Speter svn_boolean_t must_read, 2973289177Speter apr_pool_t *result_pool, 2974289177Speter apr_pool_t *scratch_pool) 2975289177Speter{ 2976289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 2977289177Speter svn_stream_t *stream; 2978289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(entry->items[0].change_set); 2979289177Speter if (!must_read && !ffd->changes_cache) 2980289177Speter return SVN_NO_ERROR; 2981289177Speter 2982289177Speter /* we don't support containers, yet */ 2983289177Speter SVN_ERR_ASSERT(entry->item_count == 1); 2984289177Speter 2985289177Speter /* already in cache? */ 2986289177Speter if (!must_read && ffd->changes_cache) 2987289177Speter { 2988289177Speter svn_boolean_t is_cached = FALSE; 2989289177Speter SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_cache, &revision, 2990289177Speter scratch_pool)); 2991289177Speter if (is_cached) 2992289177Speter return SVN_NO_ERROR; 2993289177Speter } 2994289177Speter 2995289177Speter SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 2996289177Speter 2997289177Speter /* read changes from revision file */ 2998289177Speter 2999289177Speter SVN_ERR(svn_fs_x__read_changes(changes, stream, result_pool, scratch_pool)); 3000289177Speter 3001289177Speter /* cache for future reference */ 3002289177Speter 3003289177Speter if (ffd->changes_cache) 3004289177Speter { 3005289177Speter /* Guesstimate for the size of the in-cache representation. */ 3006289177Speter apr_size_t estimated_size = (apr_size_t)250 * (*changes)->nelts; 3007289177Speter 3008289177Speter /* Don't even serialize data that probably won't fit into the 3009289177Speter * cache. This often implies that either CHANGES is very 3010289177Speter * large, memory is scarce or both. Having a huge temporary 3011289177Speter * copy would not be a good thing in either case. */ 3012289177Speter if (svn_cache__is_cachable(ffd->changes_cache, estimated_size)) 3013289177Speter SVN_ERR(svn_cache__set(ffd->changes_cache, &revision, *changes, 3014289177Speter scratch_pool)); 3015289177Speter } 3016289177Speter 3017289177Speter return SVN_NO_ERROR; 3018289177Speter} 3019289177Speter 3020289177Speterstatic svn_error_t * 3021289177Speterblock_read_changes_container(apr_array_header_t **changes, 3022289177Speter svn_fs_t *fs, 3023289177Speter svn_fs_x__revision_file_t *rev_file, 3024289177Speter svn_fs_x__p2l_entry_t* entry, 3025289177Speter apr_uint32_t sub_item, 3026289177Speter svn_boolean_t must_read, 3027289177Speter apr_pool_t *result_pool, 3028289177Speter apr_pool_t *scratch_pool) 3029289177Speter{ 3030289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 3031289177Speter svn_fs_x__changes_t *container; 3032289177Speter svn_fs_x__pair_cache_key_t key; 3033289177Speter svn_stream_t *stream; 3034289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(entry->items[0].change_set); 3035289177Speter 3036289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 3037289177Speter key.second = entry->offset; 3038289177Speter 3039289177Speter /* already in cache? */ 3040289177Speter if (!must_read && ffd->changes_container_cache) 3041289177Speter { 3042289177Speter svn_boolean_t is_cached = FALSE; 3043289177Speter SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_container_cache, 3044289177Speter &key, scratch_pool)); 3045289177Speter if (is_cached) 3046289177Speter return SVN_NO_ERROR; 3047289177Speter } 3048289177Speter 3049289177Speter SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3050289177Speter 3051289177Speter /* read changes from revision file */ 3052289177Speter 3053289177Speter SVN_ERR(svn_fs_x__read_changes_container(&container, stream, scratch_pool, 3054289177Speter scratch_pool)); 3055289177Speter 3056289177Speter /* extract requested data */ 3057289177Speter 3058289177Speter if (must_read) 3059289177Speter SVN_ERR(svn_fs_x__changes_get_list(changes, container, sub_item, 3060289177Speter result_pool)); 3061289177Speter 3062289177Speter if (ffd->changes_container_cache) 3063289177Speter SVN_ERR(svn_cache__set(ffd->changes_container_cache, &key, container, 3064289177Speter scratch_pool)); 3065289177Speter 3066289177Speter return SVN_NO_ERROR; 3067289177Speter} 3068289177Speter 3069289177Speterstatic svn_error_t * 3070289177Speterblock_read_noderev(svn_fs_x__noderev_t **noderev_p, 3071289177Speter svn_fs_t *fs, 3072289177Speter svn_fs_x__revision_file_t *rev_file, 3073289177Speter svn_fs_x__p2l_entry_t* entry, 3074289177Speter svn_fs_x__pair_cache_key_t *key, 3075289177Speter svn_boolean_t must_read, 3076289177Speter apr_pool_t *result_pool, 3077289177Speter apr_pool_t *scratch_pool) 3078289177Speter{ 3079289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 3080289177Speter svn_stream_t *stream; 3081289177Speter if (!must_read && !ffd->node_revision_cache) 3082289177Speter return SVN_NO_ERROR; 3083289177Speter 3084289177Speter /* we don't support containers, yet */ 3085289177Speter SVN_ERR_ASSERT(entry->item_count == 1); 3086289177Speter 3087289177Speter /* already in cache? */ 3088289177Speter if (!must_read && ffd->node_revision_cache) 3089289177Speter { 3090289177Speter svn_boolean_t is_cached = FALSE; 3091289177Speter SVN_ERR(svn_cache__has_key(&is_cached, ffd->node_revision_cache, key, 3092289177Speter scratch_pool)); 3093289177Speter if (is_cached) 3094289177Speter return SVN_NO_ERROR; 3095289177Speter } 3096289177Speter 3097289177Speter SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3098289177Speter 3099289177Speter /* read node rev from revision file */ 3100289177Speter 3101289177Speter SVN_ERR(svn_fs_x__read_noderev(noderev_p, stream, result_pool, 3102289177Speter scratch_pool)); 3103289177Speter if (ffd->node_revision_cache) 3104289177Speter SVN_ERR(svn_cache__set(ffd->node_revision_cache, key, *noderev_p, 3105289177Speter scratch_pool)); 3106289177Speter 3107289177Speter return SVN_NO_ERROR; 3108289177Speter} 3109289177Speter 3110289177Speterstatic svn_error_t * 3111289177Speterblock_read_noderevs_container(svn_fs_x__noderev_t **noderev_p, 3112289177Speter svn_fs_t *fs, 3113289177Speter svn_fs_x__revision_file_t *rev_file, 3114289177Speter svn_fs_x__p2l_entry_t* entry, 3115289177Speter apr_uint32_t sub_item, 3116289177Speter svn_boolean_t must_read, 3117289177Speter apr_pool_t *result_pool, 3118289177Speter apr_pool_t *scratch_pool) 3119289177Speter{ 3120289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 3121289177Speter svn_fs_x__noderevs_t *container; 3122289177Speter svn_stream_t *stream; 3123289177Speter svn_fs_x__pair_cache_key_t key; 3124289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(entry->items[0].change_set); 3125289177Speter 3126289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 3127289177Speter key.second = entry->offset; 3128289177Speter 3129289177Speter /* already in cache? */ 3130289177Speter if (!must_read && ffd->noderevs_container_cache) 3131289177Speter { 3132289177Speter svn_boolean_t is_cached = FALSE; 3133289177Speter SVN_ERR(svn_cache__has_key(&is_cached, ffd->noderevs_container_cache, 3134289177Speter &key, scratch_pool)); 3135289177Speter if (is_cached) 3136289177Speter return SVN_NO_ERROR; 3137289177Speter } 3138289177Speter 3139289177Speter SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3140289177Speter 3141289177Speter /* read noderevs from revision file */ 3142289177Speter SVN_ERR(svn_fs_x__read_noderevs_container(&container, stream, scratch_pool, 3143289177Speter scratch_pool)); 3144289177Speter 3145289177Speter /* extract requested data */ 3146289177Speter if (must_read) 3147289177Speter SVN_ERR(svn_fs_x__noderevs_get(noderev_p, container, sub_item, 3148289177Speter result_pool)); 3149289177Speter 3150289177Speter if (ffd->noderevs_container_cache) 3151289177Speter SVN_ERR(svn_cache__set(ffd->noderevs_container_cache, &key, container, 3152289177Speter scratch_pool)); 3153289177Speter 3154289177Speter return SVN_NO_ERROR; 3155289177Speter} 3156289177Speter 3157289177Speterstatic svn_error_t * 3158289177Speterblock_read_reps_container(svn_fs_x__rep_extractor_t **extractor, 3159289177Speter svn_fs_t *fs, 3160289177Speter svn_fs_x__revision_file_t *rev_file, 3161289177Speter svn_fs_x__p2l_entry_t* entry, 3162289177Speter apr_uint32_t sub_item, 3163289177Speter svn_boolean_t must_read, 3164289177Speter apr_pool_t *result_pool, 3165289177Speter apr_pool_t *scratch_pool) 3166289177Speter{ 3167289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 3168289177Speter svn_fs_x__reps_t *container; 3169289177Speter svn_stream_t *stream; 3170289177Speter svn_fs_x__pair_cache_key_t key; 3171289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(entry->items[0].change_set); 3172289177Speter 3173289177Speter key.revision = svn_fs_x__packed_base_rev(fs, revision); 3174289177Speter key.second = entry->offset; 3175289177Speter 3176289177Speter /* already in cache? */ 3177289177Speter if (!must_read && ffd->reps_container_cache) 3178289177Speter { 3179289177Speter svn_boolean_t is_cached = FALSE; 3180289177Speter SVN_ERR(svn_cache__has_key(&is_cached, ffd->reps_container_cache, 3181289177Speter &key, scratch_pool)); 3182289177Speter if (is_cached) 3183289177Speter return SVN_NO_ERROR; 3184289177Speter } 3185289177Speter 3186289177Speter SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool)); 3187289177Speter 3188289177Speter /* read noderevs from revision file */ 3189289177Speter SVN_ERR(svn_fs_x__read_reps_container(&container, stream, result_pool, 3190289177Speter scratch_pool)); 3191289177Speter 3192289177Speter /* extract requested data */ 3193289177Speter 3194289177Speter if (must_read) 3195289177Speter SVN_ERR(svn_fs_x__reps_get(extractor, fs, container, sub_item, 3196289177Speter result_pool)); 3197289177Speter 3198289177Speter if (ffd->noderevs_container_cache) 3199289177Speter SVN_ERR(svn_cache__set(ffd->reps_container_cache, &key, container, 3200289177Speter scratch_pool)); 3201289177Speter 3202289177Speter return SVN_NO_ERROR; 3203289177Speter} 3204289177Speter 3205289177Speterstatic svn_error_t * 3206289177Speterblock_read(void **result, 3207289177Speter svn_fs_t *fs, 3208289177Speter const svn_fs_x__id_t *id, 3209289177Speter svn_fs_x__revision_file_t *revision_file, 3210289177Speter apr_pool_t *result_pool, 3211289177Speter apr_pool_t *scratch_pool) 3212289177Speter{ 3213289177Speter svn_fs_x__data_t *ffd = fs->fsap_data; 3214289177Speter apr_off_t offset, wanted_offset = 0; 3215289177Speter apr_off_t block_start = 0; 3216289177Speter apr_uint32_t wanted_sub_item = 0; 3217289177Speter svn_revnum_t revision = svn_fs_x__get_revnum(id->change_set); 3218289177Speter apr_array_header_t *entries; 3219289177Speter int run_count = 0; 3220289177Speter int i; 3221289177Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3222289177Speter 3223289177Speter /* don't try this on transaction protorev files */ 3224289177Speter SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 3225289177Speter 3226289177Speter /* index lookup: find the OFFSET of the item we *must* read plus (in the 3227289177Speter * "do-while" block) the list of items in the same block. */ 3228289177Speter SVN_ERR(svn_fs_x__item_offset(&wanted_offset, &wanted_sub_item, fs, 3229289177Speter revision_file, id, iterpool)); 3230289177Speter 3231289177Speter offset = wanted_offset; 3232289177Speter do 3233289177Speter { 3234289177Speter /* fetch list of items in the block surrounding OFFSET */ 3235289177Speter SVN_ERR(aligned_seek(fs, revision_file->file, &block_start, offset, 3236289177Speter iterpool)); 3237289177Speter SVN_ERR(svn_fs_x__p2l_index_lookup(&entries, fs, revision_file, 3238289177Speter revision, block_start, 3239289177Speter ffd->block_size, scratch_pool, 3240289177Speter scratch_pool)); 3241289177Speter 3242289177Speter /* read all items from the block */ 3243289177Speter for (i = 0; i < entries->nelts; ++i) 3244289177Speter { 3245289177Speter svn_boolean_t is_result, is_wanted; 3246289177Speter apr_pool_t *pool; 3247289177Speter 3248289177Speter svn_fs_x__p2l_entry_t* entry 3249289177Speter = &APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t); 3250289177Speter 3251289177Speter /* skip empty sections */ 3252289177Speter if (entry->type == SVN_FS_X__ITEM_TYPE_UNUSED) 3253289177Speter continue; 3254289177Speter 3255289177Speter /* the item / container we were looking for? */ 3256289177Speter is_wanted = entry->offset == wanted_offset 3257289177Speter && entry->item_count >= wanted_sub_item 3258289177Speter && svn_fs_x__id_eq(entry->items + wanted_sub_item, id); 3259289177Speter is_result = result && is_wanted; 3260289177Speter 3261289177Speter /* select the pool that we want the item to be allocated in */ 3262289177Speter pool = is_result ? result_pool : iterpool; 3263289177Speter 3264289177Speter /* handle all items that start within this block and are relatively 3265289177Speter * small (i.e. < block size). Always read the item we need to return. 3266289177Speter */ 3267289177Speter if (is_result || ( entry->offset >= block_start 3268289177Speter && entry->size < ffd->block_size)) 3269289177Speter { 3270289177Speter void *item = NULL; 3271289177Speter svn_fs_x__pair_cache_key_t key = { 0 }; 3272289177Speter key.revision = svn_fs_x__get_revnum(entry->items[0].change_set); 3273289177Speter key.second = entry->items[0].number; 3274289177Speter 3275289177Speter SVN_ERR(svn_io_file_seek(revision_file->file, SEEK_SET, 3276289177Speter &entry->offset, iterpool)); 3277289177Speter switch (entry->type) 3278289177Speter { 3279289177Speter case SVN_FS_X__ITEM_TYPE_FILE_REP: 3280289177Speter case SVN_FS_X__ITEM_TYPE_DIR_REP: 3281289177Speter case SVN_FS_X__ITEM_TYPE_FILE_PROPS: 3282289177Speter case SVN_FS_X__ITEM_TYPE_DIR_PROPS: 3283289177Speter SVN_ERR(block_read_contents(fs, revision_file, 3284289177Speter entry, &key, 3285289177Speter is_wanted 3286289177Speter ? -1 3287289177Speter : block_start + ffd->block_size, 3288289177Speter iterpool)); 3289289177Speter break; 3290289177Speter 3291289177Speter case SVN_FS_X__ITEM_TYPE_NODEREV: 3292289177Speter if (ffd->node_revision_cache || is_result) 3293289177Speter SVN_ERR(block_read_noderev((svn_fs_x__noderev_t **)&item, 3294289177Speter fs, revision_file, 3295289177Speter entry, &key, is_result, 3296289177Speter pool, iterpool)); 3297289177Speter break; 3298289177Speter 3299289177Speter case SVN_FS_X__ITEM_TYPE_CHANGES: 3300289177Speter SVN_ERR(block_read_changes((apr_array_header_t **)&item, 3301289177Speter fs, revision_file, 3302289177Speter entry, is_result, 3303289177Speter pool, iterpool)); 3304289177Speter break; 3305289177Speter 3306289177Speter case SVN_FS_X__ITEM_TYPE_CHANGES_CONT: 3307289177Speter SVN_ERR(block_read_changes_container 3308289177Speter ((apr_array_header_t **)&item, 3309289177Speter fs, revision_file, 3310289177Speter entry, wanted_sub_item, 3311289177Speter is_result, pool, iterpool)); 3312289177Speter break; 3313289177Speter 3314289177Speter case SVN_FS_X__ITEM_TYPE_NODEREVS_CONT: 3315289177Speter SVN_ERR(block_read_noderevs_container 3316289177Speter ((svn_fs_x__noderev_t **)&item, 3317289177Speter fs, revision_file, 3318289177Speter entry, wanted_sub_item, 3319289177Speter is_result, pool, iterpool)); 3320289177Speter break; 3321289177Speter 3322289177Speter case SVN_FS_X__ITEM_TYPE_REPS_CONT: 3323289177Speter SVN_ERR(block_read_reps_container 3324289177Speter ((svn_fs_x__rep_extractor_t **)&item, 3325289177Speter fs, revision_file, 3326289177Speter entry, wanted_sub_item, 3327289177Speter is_result, pool, iterpool)); 3328289177Speter break; 3329289177Speter 3330289177Speter default: 3331289177Speter break; 3332289177Speter } 3333289177Speter 3334289177Speter if (is_result) 3335289177Speter *result = item; 3336289177Speter 3337289177Speter /* if we crossed a block boundary, read the remainder of 3338289177Speter * the last block as well */ 3339289177Speter offset = entry->offset + entry->size; 3340289177Speter if (offset > block_start + ffd->block_size) 3341289177Speter ++run_count; 3342289177Speter 3343289177Speter svn_pool_clear(iterpool); 3344289177Speter } 3345289177Speter } 3346289177Speter } 3347289177Speter while(run_count++ == 1); /* can only be true once and only if a block 3348289177Speter * boundary got crossed */ 3349289177Speter 3350289177Speter /* if the caller requested a result, we must have provided one by now */ 3351289177Speter assert(!result || *result); 3352289177Speter svn_pool_destroy(iterpool); 3353289177Speter 3354289177Speter return SVN_NO_ERROR; 3355289177Speter} 3356