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