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