~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_x/index.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* index.c indexing support for FSX support
 
2
 *
 
3
 * ====================================================================
 
4
 *    Licensed to the Apache Software Foundation (ASF) under one
 
5
 *    or more contributor license agreements.  See the NOTICE file
 
6
 *    distributed with this work for additional information
 
7
 *    regarding copyright ownership.  The ASF licenses this file
 
8
 *    to you under the Apache License, Version 2.0 (the
 
9
 *    "License"); you may not use this file except in compliance
 
10
 *    with the License.  You may obtain a copy of the License at
 
11
 *
 
12
 *      http://www.apache.org/licenses/LICENSE-2.0
 
13
 *
 
14
 *    Unless required by applicable law or agreed to in writing,
 
15
 *    software distributed under the License is distributed on an
 
16
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
17
 *    KIND, either express or implied.  See the License for the
 
18
 *    specific language governing permissions and limitations
 
19
 *    under the License.
 
20
 * ====================================================================
 
21
 */
 
22
 
 
23
#include <assert.h>
 
24
 
 
25
#include "svn_io.h"
 
26
#include "svn_pools.h"
 
27
#include "svn_sorts.h"
 
28
 
 
29
#include "index.h"
 
30
#include "util.h"
 
31
#include "pack.h"
 
32
 
 
33
#include "private/svn_dep_compat.h"
 
34
#include "private/svn_sorts_private.h"
 
35
#include "private/svn_subr_private.h"
 
36
#include "private/svn_temp_serializer.h"
 
37
 
 
38
#include "svn_private_config.h"
 
39
#include "temp_serializer.h"
 
40
#include "fs_x.h"
 
41
 
 
42
#include "../libsvn_fs/fs-loader.h"
 
43
 
 
44
/* maximum length of a uint64 in an 7/8b encoding */
 
45
#define ENCODED_INT_LENGTH 10
 
46
 
 
47
/* APR is missing an APR_OFF_T_MAX.  So, define one.  We will use it to
 
48
 * limit file offsets stored in the indexes.
 
49
 *
 
50
 * We assume that everything shorter than 64 bits, it is at least 32 bits.
 
51
 * We also assume that the type is always signed meaning we only have an
 
52
 * effective positive range of 63 or 31 bits, respectively.
 
53
 */
 
54
static
 
55
const apr_uint64_t off_t_max = (sizeof(apr_off_t) == sizeof(apr_int64_t))
 
56
                             ? APR_INT64_MAX
 
57
                             : APR_INT32_MAX;
 
58
 
 
59
/* We store P2L proto-index entries as 6 values, 64 bits each on disk.
 
60
 * See also svn_fs_fs__p2l_proto_index_add_entry().
 
61
 */
 
62
#define P2L_PROTO_INDEX_ENTRY_SIZE (6 * sizeof(apr_uint64_t))
 
63
 
 
64
/* We put this string in front of the L2P index header. */
 
65
#define L2P_STREAM_PREFIX "L2P-INDEX\n"
 
66
 
 
67
/* We put this string in front of the P2L index header. */
 
68
#define P2L_STREAM_PREFIX "P2L-INDEX\n"
 
69
 
 
70
/* Size of the buffer that will fit the index header prefixes. */
 
71
#define STREAM_PREFIX_LEN MAX(sizeof(L2P_STREAM_PREFIX), \
 
72
                              sizeof(P2L_STREAM_PREFIX))
 
73
 
 
74
/* Page tables in the log-to-phys index file exclusively contain entries
 
75
 * of this type to describe position and size of a given page.
 
76
 */
 
77
typedef struct l2p_page_table_entry_t
 
78
{
 
79
  /* global offset on the page within the index file */
 
80
  apr_uint64_t offset;
 
81
 
 
82
  /* number of mapping entries in that page */
 
83
  apr_uint32_t entry_count;
 
84
 
 
85
  /* size of the page on disk (in the index file) */
 
86
  apr_uint32_t size;
 
87
} l2p_page_table_entry_t;
 
88
 
 
89
/* Master run-time data structure of an log-to-phys index.  It contains
 
90
 * the page tables of every revision covered by that index - but not the
 
91
 * pages themselves.
 
92
 */
 
93
typedef struct l2p_header_t
 
94
{
 
95
  /* first revision covered by this index */
 
96
  svn_revnum_t first_revision;
 
97
 
 
98
  /* number of revisions covered */
 
99
  apr_size_t revision_count;
 
100
 
 
101
  /* (max) number of entries per page */
 
102
  apr_uint32_t page_size;
 
103
 
 
104
  /* indexes into PAGE_TABLE that mark the first page of the respective
 
105
   * revision.  PAGE_TABLE_INDEX[REVISION_COUNT] points to the end of
 
106
   * PAGE_TABLE.
 
107
   */
 
108
  apr_size_t * page_table_index;
 
109
 
 
110
  /* Page table covering all pages in the index */
 
111
  l2p_page_table_entry_t * page_table;
 
112
} l2p_header_t;
 
113
 
 
114
/* Run-time data structure containing a single log-to-phys index page.
 
115
 */
 
116
typedef struct l2p_page_t
 
117
{
 
118
  /* number of entries in the OFFSETS array */
 
119
  apr_uint32_t entry_count;
 
120
 
 
121
  /* global file offsets (item index is the array index) within the
 
122
   * packed or non-packed rev file.  Offset will be -1 for unused /
 
123
   * invalid item index values. */
 
124
  apr_off_t *offsets;
 
125
 
 
126
  /* In case that the item is stored inside a container, this is the
 
127
   * identifying index of the item within that container.  0 for the
 
128
   * container itself or for items that aren't containers. */
 
129
  apr_uint32_t *sub_items;
 
130
} l2p_page_t;
 
131
 
 
132
/* All of the log-to-phys proto index file consist of entries of this type.
 
133
 */
 
134
typedef struct l2p_proto_entry_t
 
135
{
 
136
  /* phys offset + 1 of the data container. 0 for "new revision" entries. */
 
137
  apr_uint64_t offset;
 
138
 
 
139
  /* corresponding item index. 0 for "new revision" entries. */
 
140
  apr_uint64_t item_index;
 
141
 
 
142
  /* index within the container starting @ offset.  0 for "new revision"
 
143
   * entries and for items with no outer container. */
 
144
  apr_uint32_t sub_item;
 
145
} l2p_proto_entry_t;
 
146
 
 
147
/* Master run-time data structure of an phys-to-log index.  It contains
 
148
 * an array with one offset value for each rev file cluster.
 
149
 */
 
150
typedef struct p2l_header_t
 
151
{
 
152
  /* first revision covered by the index (and rev file) */
 
153
  svn_revnum_t first_revision;
 
154
 
 
155
  /* number of bytes in the rev files covered by each p2l page */
 
156
  apr_uint64_t page_size;
 
157
 
 
158
  /* number of pages / clusters in that rev file */
 
159
  apr_size_t page_count;
 
160
 
 
161
  /* number of bytes in the rev file */
 
162
  apr_uint64_t file_size;
 
163
 
 
164
  /* offsets of the pages / cluster descriptions within the index file */
 
165
  apr_off_t *offsets;
 
166
} p2l_header_t;
 
167
 
 
168
/*
 
169
 * packed stream array
 
170
 */
 
171
 
 
172
/* How many numbers we will pre-fetch and buffer in a packed number stream.
 
173
 */
 
174
enum { MAX_NUMBER_PREFETCH = 64 };
 
175
 
 
176
/* Prefetched number entry in a packed number stream.
 
177
 */
 
178
typedef struct value_position_pair_t
 
179
{
 
180
  /* prefetched number */
 
181
  apr_uint64_t value;
 
182
 
 
183
  /* number of bytes read, *including* this number, since the buffer start */
 
184
  apr_size_t total_len;
 
185
} value_position_pair_t;
 
186
 
 
187
/* State of a prefetching packed number stream.  It will read compressed
 
188
 * index data efficiently and present it as a series of non-packed uint64.
 
189
 */
 
190
struct svn_fs_x__packed_number_stream_t
 
191
{
 
192
  /* underlying data file containing the packed values */
 
193
  apr_file_t *file;
 
194
 
 
195
  /* Offset within FILE at which the stream data starts
 
196
   * (i.e. which offset will reported as offset 0 by packed_stream_offset). */
 
197
  apr_off_t stream_start;
 
198
 
 
199
  /* First offset within FILE after the stream data.
 
200
   * Attempts to read beyond this will cause an "Unexpected End Of Stream"
 
201
   * error. */
 
202
  apr_off_t stream_end;
 
203
 
 
204
  /* number of used entries in BUFFER (starting at index 0) */
 
205
  apr_size_t used;
 
206
 
 
207
  /* index of the next number to read from the BUFFER (0 .. USED).
 
208
   * If CURRENT == USED, we need to read more data upon get() */
 
209
  apr_size_t current;
 
210
 
 
211
  /* offset in FILE from which the first entry in BUFFER has been read */
 
212
  apr_off_t start_offset;
 
213
 
 
214
  /* offset in FILE from which the next number has to be read */
 
215
  apr_off_t next_offset;
 
216
 
 
217
  /* read the file in chunks of this size */
 
218
  apr_size_t block_size;
 
219
 
 
220
  /* pool to be used for file ops etc. */
 
221
  apr_pool_t *pool;
 
222
 
 
223
  /* buffer for prefetched values */
 
224
  value_position_pair_t buffer[MAX_NUMBER_PREFETCH];
 
225
};
 
226
 
 
227
/* Return an svn_error_t * object for error ERR on STREAM with the given
 
228
 * MESSAGE string.  The latter must have a placeholder for the index file
 
229
 * name ("%s") and the current read offset (e.g. "0x%lx").
 
230
 */
 
231
static svn_error_t *
 
232
stream_error_create(svn_fs_x__packed_number_stream_t *stream,
 
233
                    apr_status_t err,
 
234
                    const char *message)
 
235
{
 
236
  const char *file_name;
 
237
  apr_off_t offset;
 
238
  SVN_ERR(svn_io_file_name_get(&file_name, stream->file,
 
239
                               stream->pool));
 
240
  SVN_ERR(svn_fs_x__get_file_offset(&offset, stream->file, stream->pool));
 
241
 
 
242
  return svn_error_createf(err, NULL, message, file_name,
 
243
                           apr_psprintf(stream->pool,
 
244
                                        "%" APR_UINT64_T_HEX_FMT,
 
245
                                        (apr_uint64_t)offset));
 
246
}
 
247
 
 
248
/* Read up to MAX_NUMBER_PREFETCH numbers from the STREAM->NEXT_OFFSET in
 
249
 * STREAM->FILE and buffer them.
 
250
 *
 
251
 * We don't want GCC and others to inline this (infrequently called)
 
252
 * function into packed_stream_get() because it prevents the latter from
 
253
 * being inlined itself.
 
254
 */
 
255
SVN__PREVENT_INLINE
 
256
static svn_error_t *
 
257
packed_stream_read(svn_fs_x__packed_number_stream_t *stream)
 
258
{
 
259
  unsigned char buffer[MAX_NUMBER_PREFETCH];
 
260
  apr_size_t read = 0;
 
261
  apr_size_t i;
 
262
  value_position_pair_t *target;
 
263
  apr_off_t block_start = 0;
 
264
  apr_off_t block_left = 0;
 
265
  apr_status_t err;
 
266
 
 
267
  /* all buffered data will have been read starting here */
 
268
  stream->start_offset = stream->next_offset;
 
269
 
 
270
  /* packed numbers are usually not aligned to MAX_NUMBER_PREFETCH blocks,
 
271
   * i.e. the last number has been incomplete (and not buffered in stream)
 
272
   * and need to be re-read.  Therefore, always correct the file pointer.
 
273
   */
 
274
  SVN_ERR(svn_io_file_aligned_seek(stream->file, stream->block_size,
 
275
                                   &block_start, stream->next_offset,
 
276
                                   stream->pool));
 
277
 
 
278
  /* prefetch at least one number but, if feasible, don't cross block
 
279
   * boundaries.  This shall prevent jumping back and forth between two
 
280
   * blocks because the extra data was not actually request _now_.
 
281
   */
 
282
  read = sizeof(buffer);
 
283
  block_left = stream->block_size - (stream->next_offset - block_start);
 
284
  if (block_left >= 10 && block_left < read)
 
285
    read = (apr_size_t)block_left;
 
286
 
 
287
  /* Don't read beyond the end of the file section that belongs to this
 
288
   * index / stream. */
 
289
  read = (apr_size_t)MIN(read, stream->stream_end - stream->next_offset);
 
290
 
 
291
  err = apr_file_read(stream->file, buffer, &read);
 
292
  if (err && !APR_STATUS_IS_EOF(err))
 
293
    return stream_error_create(stream, err,
 
294
      _("Can't read index file '%s' at offset 0x%"));
 
295
 
 
296
  /* if the last number is incomplete, trim it from the buffer */
 
297
  while (read > 0 && buffer[read-1] >= 0x80)
 
298
    --read;
 
299
 
 
300
  /* we call read() only if get() requires more data.  So, there must be
 
301
   * at least *one* further number. */
 
302
  if SVN__PREDICT_FALSE(read == 0)
 
303
    return stream_error_create(stream, err,
 
304
      _("Unexpected end of index file %s at offset 0x%"));
 
305
 
 
306
  /* parse file buffer and expand into stream buffer */
 
307
  target = stream->buffer;
 
308
  for (i = 0; i < read;)
 
309
    {
 
310
      if (buffer[i] < 0x80)
 
311
        {
 
312
          /* numbers < 128 are relatively frequent and particularly easy
 
313
           * to decode.  Give them special treatment. */
 
314
          target->value = buffer[i];
 
315
          ++i;
 
316
          target->total_len = i;
 
317
          ++target;
 
318
        }
 
319
      else
 
320
        {
 
321
          apr_uint64_t value = 0;
 
322
          apr_uint64_t shift = 0;
 
323
          while (buffer[i] >= 0x80)
 
324
            {
 
325
              value += ((apr_uint64_t)buffer[i] & 0x7f) << shift;
 
326
              shift += 7;
 
327
              ++i;
 
328
            }
 
329
 
 
330
          target->value = value + ((apr_uint64_t)buffer[i] << shift);
 
331
          ++i;
 
332
          target->total_len = i;
 
333
          ++target;
 
334
 
 
335
          /* let's catch corrupted data early.  It would surely cause
 
336
           * havoc further down the line. */
 
337
          if SVN__PREDICT_FALSE(shift > 8 * sizeof(value))
 
338
            return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
339
                                     _("Corrupt index: number too large"));
 
340
       }
 
341
    }
 
342
 
 
343
  /* update stream state */
 
344
  stream->used = target - stream->buffer;
 
345
  stream->next_offset = stream->start_offset + i;
 
346
  stream->current = 0;
 
347
 
 
348
  return SVN_NO_ERROR;
 
349
}
 
350
 
 
351
/* Create and open a packed number stream reading from offsets START to
 
352
 * END in FILE and return it in *STREAM.  Access the file in chunks of
 
353
 * BLOCK_SIZE bytes.  Expect the stream to be prefixed by STREAM_PREFIX.
 
354
 * Allocate *STREAM in RESULT_POOL and use SCRATCH_POOL for temporaries.
 
355
 */
 
356
static svn_error_t *
 
357
packed_stream_open(svn_fs_x__packed_number_stream_t **stream,
 
358
                   apr_file_t *file,
 
359
                   apr_off_t start,
 
360
                   apr_off_t end,
 
361
                   const char *stream_prefix,
 
362
                   apr_size_t block_size,
 
363
                   apr_pool_t *result_pool,
 
364
                   apr_pool_t *scratch_pool)
 
365
{
 
366
  char buffer[STREAM_PREFIX_LEN + 1] = { 0 };
 
367
  apr_size_t len = strlen(stream_prefix);
 
368
  svn_fs_x__packed_number_stream_t *result;
 
369
 
 
370
  /* If this is violated, we forgot to adjust STREAM_PREFIX_LEN after
 
371
   * changing the index header prefixes. */
 
372
  SVN_ERR_ASSERT(len < sizeof(buffer));
 
373
 
 
374
  /* Read the header prefix and compare it with the expected prefix */
 
375
  SVN_ERR(svn_io_file_aligned_seek(file, block_size, NULL, start,
 
376
                                   scratch_pool));
 
377
  SVN_ERR(svn_io_file_read_full2(file, buffer, len, NULL, NULL,
 
378
                                 scratch_pool));
 
379
 
 
380
  if (strncmp(buffer, stream_prefix, len))
 
381
    return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
382
                             _("Index stream header prefix mismatch.\n"
 
383
                               "  expected: %s"
 
384
                               "  found: %s"), stream_prefix, buffer);
 
385
 
 
386
  /* Construct the actual stream object. */
 
387
  result = apr_palloc(result_pool, sizeof(*result));
 
388
 
 
389
  result->pool = result_pool;
 
390
  result->file = file;
 
391
  result->stream_start = start + len;
 
392
  result->stream_end = end;
 
393
 
 
394
  result->used = 0;
 
395
  result->current = 0;
 
396
  result->start_offset = result->stream_start;
 
397
  result->next_offset = result->stream_start;
 
398
  result->block_size = block_size;
 
399
 
 
400
  *stream = result;
 
401
 
 
402
  return SVN_NO_ERROR;
 
403
}
 
404
 
 
405
/*
 
406
 * The forced inline is required for performance reasons:  This is a very
 
407
 * hot code path (called for every item we read) but e.g. GCC would rather
 
408
 * chose to inline packed_stream_read() here, preventing packed_stream_get
 
409
 * from being inlined itself.
 
410
 */
 
411
SVN__FORCE_INLINE
 
412
static svn_error_t*
 
413
packed_stream_get(apr_uint64_t *value,
 
414
                  svn_fs_x__packed_number_stream_t *stream)
 
415
{
 
416
  if (stream->current == stream->used)
 
417
    SVN_ERR(packed_stream_read(stream));
 
418
 
 
419
  *value = stream->buffer[stream->current].value;
 
420
  ++stream->current;
 
421
 
 
422
  return SVN_NO_ERROR;
 
423
}
 
424
 
 
425
/* Navigate STREAM to packed stream offset OFFSET.  There will be no checks
 
426
 * whether the given OFFSET is valid.
 
427
 */
 
428
static void
 
429
packed_stream_seek(svn_fs_x__packed_number_stream_t *stream,
 
430
                   apr_off_t offset)
 
431
{
 
432
  apr_off_t file_offset = offset + stream->stream_start;
 
433
 
 
434
  if (   stream->used == 0
 
435
      || offset < stream->start_offset
 
436
      || offset >= stream->next_offset)
 
437
    {
 
438
      /* outside buffered data.  Next get() will read() from OFFSET. */
 
439
      stream->start_offset = file_offset;
 
440
      stream->next_offset = file_offset;
 
441
      stream->current = 0;
 
442
      stream->used = 0;
 
443
    }
 
444
  else
 
445
    {
 
446
      /* Find the suitable location in the stream buffer.
 
447
       * Since our buffer is small, it is efficient enough to simply scan
 
448
       * it for the desired position. */
 
449
      apr_size_t i;
 
450
      for (i = 0; i < stream->used; ++i)
 
451
        if (stream->buffer[i].total_len > file_offset - stream->start_offset)
 
452
          break;
 
453
 
 
454
      stream->current = i;
 
455
    }
 
456
}
 
457
 
 
458
/* Return the packed stream offset of at which the next number in the stream
 
459
 * can be found.
 
460
 */
 
461
static apr_off_t
 
462
packed_stream_offset(svn_fs_x__packed_number_stream_t *stream)
 
463
{
 
464
  apr_off_t file_offset
 
465
       = stream->current == 0
 
466
       ? stream->start_offset
 
467
       : stream->buffer[stream->current-1].total_len + stream->start_offset;
 
468
 
 
469
  return file_offset - stream->stream_start;
 
470
}
 
471
 
 
472
/* Write VALUE to the PROTO_INDEX file, using SCRATCH_POOL for temporary
 
473
 * allocations.
 
474
 *
 
475
 * The point of this function is to ensure an architecture-independent
 
476
 * proto-index file format.  All data is written as unsigned 64 bits ints
 
477
 * in little endian byte order.  64 bits is the largest portable integer
 
478
 * we have and unsigned values have well-defined conversions in C.
 
479
 */
 
480
static svn_error_t *
 
481
write_uint64_to_proto_index(apr_file_t *proto_index,
 
482
                            apr_uint64_t value,
 
483
                            apr_pool_t *scratch_pool)
 
484
{
 
485
  apr_byte_t buffer[sizeof(value)];
 
486
  int i;
 
487
  apr_size_t written;
 
488
 
 
489
  /* Split VALUE into 8 bytes using LE ordering. */
 
490
  for (i = 0; i < sizeof(buffer); ++i)
 
491
    {
 
492
      /* Unsigned conversions are well-defined ... */
 
493
      buffer[i] = (apr_byte_t)value;
 
494
      value >>= CHAR_BIT;
 
495
    }
 
496
 
 
497
  /* Write it all to disk. */
 
498
  SVN_ERR(svn_io_file_write_full(proto_index, buffer, sizeof(buffer),
 
499
                                 &written, scratch_pool));
 
500
  SVN_ERR_ASSERT(written == sizeof(buffer));
 
501
 
 
502
  return SVN_NO_ERROR;
 
503
}
 
504
 
 
505
/* Read one unsigned 64 bit value from PROTO_INDEX file and return it in
 
506
 * *VALUE_P.  If EOF is NULL, error out when trying to read beyond EOF.
 
507
 * Use SCRATCH_POOL for temporary allocations.
 
508
 *
 
509
 * This function is the inverse to write_uint64_to_proto_index (see there),
 
510
 * reading the external LE byte order and convert it into host byte order.
 
511
 */
 
512
static svn_error_t *
 
513
read_uint64_from_proto_index(apr_file_t *proto_index,
 
514
                             apr_uint64_t *value_p,
 
515
                             svn_boolean_t *eof,
 
516
                             apr_pool_t *scratch_pool)
 
517
{
 
518
  apr_byte_t buffer[sizeof(*value_p)];
 
519
  apr_size_t read;
 
520
 
 
521
  /* Read the full 8 bytes or our 64 bit value, unless we hit EOF.
 
522
   * Assert that we never read partial values. */
 
523
  SVN_ERR(svn_io_file_read_full2(proto_index, buffer, sizeof(buffer),
 
524
                                 &read, eof, scratch_pool));
 
525
  SVN_ERR_ASSERT((eof && *eof) || read == sizeof(buffer));
 
526
 
 
527
  /* If we did not hit EOF, reconstruct the uint64 value and return it. */
 
528
  if (!eof || !*eof)
 
529
    {
 
530
      int i;
 
531
      apr_uint64_t value;
 
532
 
 
533
      /* This could only overflow if CHAR_BIT had a value that is not
 
534
       * a divisor of 64. */
 
535
      value = 0;
 
536
      for (i = sizeof(buffer) - 1; i >= 0; --i)
 
537
        value = (value << CHAR_BIT) + buffer[i];
 
538
 
 
539
      *value_p = value;
 
540
    }
 
541
 
 
542
  return SVN_NO_ERROR;
 
543
}
 
544
 
 
545
/* Convenience function similar to read_uint64_from_proto_index, but returns
 
546
 * an uint32 value in VALUE_P.  Return an error if the value does not fit.
 
547
 */
 
548
static svn_error_t *
 
549
read_uint32_from_proto_index(apr_file_t *proto_index,
 
550
                             apr_uint32_t *value_p,
 
551
                             svn_boolean_t *eof,
 
552
                             apr_pool_t *scratch_pool)
 
553
{
 
554
  apr_uint64_t value;
 
555
  SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
 
556
                                       scratch_pool));
 
557
  if (!eof || !*eof)
 
558
    {
 
559
      if (value > APR_UINT32_MAX)
 
560
        return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
 
561
                                 _("UINT32 0x%s too large, max = 0x%s"),
 
562
                                 apr_psprintf(scratch_pool,
 
563
                                              "%" APR_UINT64_T_HEX_FMT,
 
564
                                              value),
 
565
                                 apr_psprintf(scratch_pool,
 
566
                                              "%" APR_UINT64_T_HEX_FMT,
 
567
                                             (apr_uint64_t)APR_UINT32_MAX));
 
568
 
 
569
      /* This conversion is not lossy because the value can be represented
 
570
       * in the target type. */
 
571
      *value_p = (apr_uint32_t)value;
 
572
    }
 
573
 
 
574
  return SVN_NO_ERROR;
 
575
}
 
576
 
 
577
/* Convenience function similar to read_uint64_from_proto_index, but returns
 
578
 * an off_t value in VALUE_P.  Return an error if the value does not fit.
 
579
 */
 
580
static svn_error_t *
 
581
read_off_t_from_proto_index(apr_file_t *proto_index,
 
582
                            apr_off_t *value_p,
 
583
                            svn_boolean_t *eof,
 
584
                            apr_pool_t *scratch_pool)
 
585
{
 
586
  apr_uint64_t value;
 
587
  SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
 
588
                                       scratch_pool));
 
589
  if (!eof || !*eof)
 
590
    {
 
591
      if (value > off_t_max)
 
592
        return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
 
593
                                _("File offset 0x%s too large, max = 0x%s"),
 
594
                                 apr_psprintf(scratch_pool,
 
595
                                              "%" APR_UINT64_T_HEX_FMT,
 
596
                                              value),
 
597
                                 apr_psprintf(scratch_pool,
 
598
                                              "%" APR_UINT64_T_HEX_FMT,
 
599
                                              off_t_max));
 
600
 
 
601
      /* Shortening conversion from unsigned to signed int is well-defined
 
602
       * and not lossy in C because the value can be represented in the
 
603
       * target type. */
 
604
      *value_p = (apr_off_t)value;
 
605
    }
 
606
 
 
607
  return SVN_NO_ERROR;
 
608
}
 
609
 
 
610
/*
 
611
 * log-to-phys index
 
612
 */
 
613
svn_error_t *
 
614
svn_fs_x__l2p_proto_index_open(apr_file_t **proto_index,
 
615
                               const char *file_name,
 
616
                               apr_pool_t *result_pool)
 
617
{
 
618
  SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
 
619
                           | APR_CREATE | APR_APPEND | APR_BUFFERED,
 
620
                           APR_OS_DEFAULT, result_pool));
 
621
 
 
622
  return SVN_NO_ERROR;
 
623
}
 
624
 
 
625
/* Append ENTRY to log-to-phys PROTO_INDEX file.
 
626
 * Use SCRATCH_POOL for temporary allocations.
 
627
 */
 
628
static svn_error_t *
 
629
write_l2p_entry_to_proto_index(apr_file_t *proto_index,
 
630
                               l2p_proto_entry_t entry,
 
631
                               apr_pool_t *scratch_pool)
 
632
{
 
633
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry.offset,
 
634
                                      scratch_pool));
 
635
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry.item_index,
 
636
                                      scratch_pool));
 
637
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry.sub_item,
 
638
                                      scratch_pool));
 
639
 
 
640
  return SVN_NO_ERROR;
 
641
}
 
642
 
 
643
/* Read *ENTRY from log-to-phys PROTO_INDEX file and indicate end-of-file
 
644
 * in *EOF, or error out in that case if EOF is NULL.  *ENTRY is in an
 
645
 * undefined state if an end-of-file occurred.
 
646
 * Use SCRATCH_POOL for temporary allocations.
 
647
 */
 
648
static svn_error_t *
 
649
read_l2p_entry_from_proto_index(apr_file_t *proto_index,
 
650
                                l2p_proto_entry_t *entry,
 
651
                                svn_boolean_t *eof,
 
652
                                apr_pool_t *scratch_pool)
 
653
{
 
654
  SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->offset, eof,
 
655
                                       scratch_pool));
 
656
  SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->item_index, eof,
 
657
                                       scratch_pool));
 
658
  SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->sub_item, eof,
 
659
                                       scratch_pool));
 
660
 
 
661
  return SVN_NO_ERROR;
 
662
}
 
663
 
 
664
svn_error_t *
 
665
svn_fs_x__l2p_proto_index_add_revision(apr_file_t *proto_index,
 
666
                                       apr_pool_t *scratch_pool)
 
667
{
 
668
  l2p_proto_entry_t entry = { 0 };
 
669
  return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
 
670
                                                        scratch_pool));
 
671
}
 
672
 
 
673
svn_error_t *
 
674
svn_fs_x__l2p_proto_index_add_entry(apr_file_t *proto_index,
 
675
                                    apr_off_t offset,
 
676
                                    apr_uint32_t sub_item,
 
677
                                    apr_uint64_t item_index,
 
678
                                    apr_pool_t *scratch_pool)
 
679
{
 
680
  l2p_proto_entry_t entry = { 0 };
 
681
 
 
682
  /* make sure the conversion to uint64 works */
 
683
  SVN_ERR_ASSERT(offset >= -1);
 
684
 
 
685
  /* we support offset '-1' as a "not used" indication */
 
686
  entry.offset = (apr_uint64_t)offset + 1;
 
687
 
 
688
  /* make sure we can use item_index as an array index when building the
 
689
   * final index file */
 
690
  SVN_ERR_ASSERT(item_index < UINT_MAX / 2);
 
691
  entry.item_index = item_index;
 
692
 
 
693
  /* no limits on the container sub-item index */
 
694
  entry.sub_item = sub_item;
 
695
 
 
696
  return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
 
697
                                                        scratch_pool));
 
698
}
 
699
 
 
700
/* Encode VALUE as 7/8b into P and return the number of bytes written.
 
701
 * This will be used when _writing_ packed data.  packed_stream_* is for
 
702
 * read operations only.
 
703
 */
 
704
static apr_size_t
 
705
encode_uint(unsigned char *p, apr_uint64_t value)
 
706
{
 
707
  unsigned char *start = p;
 
708
  while (value >= 0x80)
 
709
    {
 
710
      *p = (unsigned char)((value % 0x80) + 0x80);
 
711
      value /= 0x80;
 
712
      ++p;
 
713
    }
 
714
 
 
715
  *p = (unsigned char)(value % 0x80);
 
716
  return (p - start) + 1;
 
717
}
 
718
 
 
719
/* Encode VALUE as 7/8b into P and return the number of bytes written.
 
720
 * This maps signed ints onto unsigned ones.
 
721
 */
 
722
static apr_size_t
 
723
encode_int(unsigned char *p, apr_int64_t value)
 
724
{
 
725
  return encode_uint(p, (apr_uint64_t)(value < 0 ? -1 - 2*value : 2*value));
 
726
}
 
727
 
 
728
/* Append VALUE to STREAM in 7/8b encoding.
 
729
 */
 
730
static svn_error_t *
 
731
stream_write_encoded(svn_stream_t *stream,
 
732
                     apr_uint64_t value)
 
733
{
 
734
  unsigned char encoded[ENCODED_INT_LENGTH];
 
735
 
 
736
  apr_size_t len = encode_uint(encoded, value);
 
737
  return svn_error_trace(svn_stream_write(stream, (char *)encoded, &len));
 
738
}
 
739
 
 
740
/* Run-length-encode the uint64 numbers in ARRAY starting at index START
 
741
 * up to but not including END.  All numbers must be > 0.
 
742
 * Return the number of remaining entries in ARRAY after START.
 
743
 */
 
744
static int
 
745
rle_array(apr_array_header_t *array, int start, int end)
 
746
{
 
747
  int i;
 
748
  int target = start;
 
749
  for (i = start; i < end; ++i)
 
750
    {
 
751
      apr_uint64_t value = APR_ARRAY_IDX(array, i, apr_uint64_t);
 
752
      assert(value > 0);
 
753
 
 
754
      if (value == 1)
 
755
        {
 
756
          int counter;
 
757
          for (counter = 1; i + counter < end; ++counter)
 
758
            if (APR_ARRAY_IDX(array, i + counter, apr_uint64_t) != 1)
 
759
              break;
 
760
 
 
761
          if (--counter)
 
762
            {
 
763
              APR_ARRAY_IDX(array, target, apr_uint64_t) = 0;
 
764
              APR_ARRAY_IDX(array, target + 1, apr_uint64_t) = counter;
 
765
              target += 2;
 
766
              i += counter;
 
767
              continue;
 
768
            }
 
769
        }
 
770
 
 
771
      APR_ARRAY_IDX(array, target, apr_uint64_t) = value;
 
772
      ++target;
 
773
    }
 
774
 
 
775
  return target;
 
776
}
 
777
 
 
778
/* Utility data structure describing an log-2-phys page entry.
 
779
 * This is only used as a transient representation during index creation.
 
780
 */
 
781
typedef struct l2p_page_entry_t
 
782
{
 
783
  apr_uint64_t offset;
 
784
  apr_uint32_t sub_item;
 
785
} l2p_page_entry_t;
 
786
 
 
787
/* qsort-compatible compare function taking two l2p_page_entry_t and
 
788
 * ordering them by offset.
 
789
 */
 
790
static int
 
791
compare_l2p_entries_by_offset(const l2p_page_entry_t *lhs,
 
792
                              const l2p_page_entry_t *rhs)
 
793
{
 
794
  return lhs->offset > rhs->offset ? 1
 
795
                                   : lhs->offset == rhs->offset ? 0 : -1;
 
796
}
 
797
 
 
798
/* Write the log-2-phys index page description for the l2p_page_entry_t
 
799
 * array ENTRIES, starting with element START up to but not including END.
 
800
 * Write the resulting representation into BUFFER.  Use SCRATCH_POOL for
 
801
 * temporary allocations.
 
802
 */
 
803
static svn_error_t *
 
804
encode_l2p_page(apr_array_header_t *entries,
 
805
                int start,
 
806
                int end,
 
807
                svn_spillbuf_t *buffer,
 
808
                apr_pool_t *scratch_pool)
 
809
{
 
810
  unsigned char encoded[ENCODED_INT_LENGTH];
 
811
  apr_hash_t *containers = apr_hash_make(scratch_pool);
 
812
  int count = end - start;
 
813
  int container_count = 0;
 
814
  apr_uint64_t last_offset = 0;
 
815
  int i;
 
816
 
 
817
  apr_size_t data_size = count * sizeof(l2p_page_entry_t);
 
818
  svn_stringbuf_t *container_offsets
 
819
    = svn_stringbuf_create_ensure(count * 2, scratch_pool);
 
820
 
 
821
  /* SORTED: relevant items from ENTRIES, sorted by offset */
 
822
  l2p_page_entry_t *sorted
 
823
    = apr_pmemdup(scratch_pool,
 
824
                  entries->elts + start * sizeof(l2p_page_entry_t),
 
825
                  data_size);
 
826
  qsort(sorted, end - start, sizeof(l2p_page_entry_t),
 
827
        (int (*)(const void *, const void *))compare_l2p_entries_by_offset);
 
828
 
 
829
  /* identify container offsets and create container list */
 
830
  for (i = 0; i < count; ++i)
 
831
    {
 
832
      /* skip "unused" entries */
 
833
      if (sorted[i].offset == 0)
 
834
        continue;
 
835
 
 
836
      /* offset already covered? */
 
837
      if (i > 0 && sorted[i].offset == sorted[i-1].offset)
 
838
        continue;
 
839
 
 
840
      /* is this a container item
 
841
       * (appears more than once or accesses to sub-items other than 0)? */
 
842
      if (   (i != count-1 && sorted[i].offset == sorted[i+1].offset)
 
843
          || (sorted[i].sub_item != 0))
 
844
        {
 
845
          svn_stringbuf_appendbytes(container_offsets, (const char *)encoded,
 
846
                                    encode_uint(encoded,   sorted[i].offset
 
847
                                                         - last_offset));
 
848
          last_offset = sorted[i].offset;
 
849
          apr_hash_set(containers,
 
850
                       &sorted[i].offset,
 
851
                       sizeof(sorted[i].offset),
 
852
                       (void *)(apr_uintptr_t)++container_count);
 
853
        }
 
854
    }
 
855
 
 
856
  /* write container list to BUFFER */
 
857
  SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
858
                              encode_uint(encoded, container_count),
 
859
                              scratch_pool));
 
860
  SVN_ERR(svn_spillbuf__write(buffer, (const char *)container_offsets->data,
 
861
                              container_offsets->len, scratch_pool));
 
862
 
 
863
  /* encode items */
 
864
  for (i = start; i < end; ++i)
 
865
    {
 
866
      l2p_page_entry_t *entry = &APR_ARRAY_IDX(entries, i, l2p_page_entry_t);
 
867
      if (entry->offset == 0)
 
868
        {
 
869
          SVN_ERR(svn_spillbuf__write(buffer, "\0", 1, scratch_pool));
 
870
        }
 
871
      else
 
872
        {
 
873
          void *void_idx = apr_hash_get(containers, &entry->offset,
 
874
                                        sizeof(entry->offset));
 
875
          if (void_idx == NULL)
 
876
            {
 
877
              apr_uint64_t value = entry->offset + container_count;
 
878
              SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
879
                                          encode_uint(encoded, value),
 
880
                                          scratch_pool));
 
881
            }
 
882
          else
 
883
            {
 
884
              apr_uintptr_t idx = (apr_uintptr_t)void_idx;
 
885
              apr_uint64_t value = entry->sub_item;
 
886
              SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
887
                                          encode_uint(encoded, idx),
 
888
                                          scratch_pool));
 
889
              SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
890
                                          encode_uint(encoded, value),
 
891
                                          scratch_pool));
 
892
            }
 
893
        }
 
894
    }
 
895
 
 
896
  return SVN_NO_ERROR;
 
897
}
 
898
 
 
899
svn_error_t *
 
900
svn_fs_x__l2p_index_append(svn_checksum_t **checksum,
 
901
                           svn_fs_t *fs,
 
902
                           apr_file_t *index_file,
 
903
                           const char *proto_file_name,
 
904
                           svn_revnum_t revision,
 
905
                           apr_pool_t * result_pool,
 
906
                           apr_pool_t *scratch_pool)
 
907
{
 
908
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
909
  apr_file_t *proto_index = NULL;
 
910
  svn_stream_t *stream;
 
911
  int i;
 
912
  int end;
 
913
  apr_uint64_t entry;
 
914
  svn_boolean_t eof = FALSE;
 
915
 
 
916
  int last_page_count = 0;          /* total page count at the start of
 
917
                                       the current revision */
 
918
 
 
919
  /* temporary data structures that collect the data which will be moved
 
920
     to the target file in a second step */
 
921
  apr_pool_t *local_pool = svn_pool_create(scratch_pool);
 
922
  apr_pool_t *iterpool = svn_pool_create(local_pool);
 
923
  apr_array_header_t *page_counts
 
924
    = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
 
925
  apr_array_header_t *page_sizes
 
926
    = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
 
927
  apr_array_header_t *entry_counts
 
928
    = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
 
929
 
 
930
  /* collect the item offsets and sub-item value for the current revision */
 
931
  apr_array_header_t *entries
 
932
    = apr_array_make(local_pool, 256, sizeof(l2p_page_entry_t));
 
933
 
 
934
  /* 64k blocks, spill after 16MB */
 
935
  svn_spillbuf_t *buffer
 
936
    = svn_spillbuf__create(0x10000, 0x1000000, local_pool);
 
937
 
 
938
  /* Paranoia check that makes later casting to int32 safe.
 
939
   * The current implementation is limited to 2G entries per page. */
 
940
  if (ffd->l2p_page_size > APR_INT32_MAX)
 
941
    return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
942
                            _("L2P index page size  %s"
 
943
                              " exceeds current limit of 2G entries"),
 
944
                            apr_psprintf(local_pool, "%" APR_UINT64_T_FMT,
 
945
                                         ffd->l2p_page_size));
 
946
 
 
947
  /* start at the beginning of the source file */
 
948
  SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
 
949
                           APR_READ | APR_CREATE | APR_BUFFERED,
 
950
                           APR_OS_DEFAULT, local_pool));
 
951
 
 
952
  /* process all entries until we fail due to EOF */
 
953
  for (entry = 0; !eof; ++entry)
 
954
    {
 
955
      l2p_proto_entry_t proto_entry;
 
956
 
 
957
      /* (attempt to) read the next entry from the source */
 
958
      SVN_ERR(read_l2p_entry_from_proto_index(proto_index, &proto_entry,
 
959
                                              &eof, local_pool));
 
960
 
 
961
      /* handle new revision */
 
962
      if ((entry > 0 && proto_entry.offset == 0) || eof)
 
963
        {
 
964
          /* dump entries, grouped into pages */
 
965
 
 
966
          int entry_count = 0;
 
967
          for (i = 0; i < entries->nelts; i += entry_count)
 
968
            {
 
969
              /* 1 page with up to L2P_PAGE_SIZE entries.
 
970
               * fsfs.conf settings validation guarantees this to fit into
 
971
               * our address space. */
 
972
              apr_size_t last_buffer_size
 
973
                = (apr_size_t)svn_spillbuf__get_size(buffer);
 
974
 
 
975
              svn_pool_clear(iterpool);
 
976
 
 
977
              entry_count = ffd->l2p_page_size < entries->nelts - i
 
978
                          ? (int)ffd->l2p_page_size
 
979
                          : entries->nelts - i;
 
980
              SVN_ERR(encode_l2p_page(entries, i, i + entry_count,
 
981
                                      buffer, iterpool));
 
982
 
 
983
              APR_ARRAY_PUSH(entry_counts, apr_uint64_t) = entry_count;
 
984
              APR_ARRAY_PUSH(page_sizes, apr_uint64_t)
 
985
                = svn_spillbuf__get_size(buffer) - last_buffer_size;
 
986
            }
 
987
 
 
988
          apr_array_clear(entries);
 
989
 
 
990
          /* store the number of pages in this revision */
 
991
          APR_ARRAY_PUSH(page_counts, apr_uint64_t)
 
992
            = page_sizes->nelts - last_page_count;
 
993
 
 
994
          last_page_count = page_sizes->nelts;
 
995
        }
 
996
      else
 
997
        {
 
998
          int idx;
 
999
 
 
1000
          /* store the mapping in our array */
 
1001
          l2p_page_entry_t page_entry = { 0 };
 
1002
 
 
1003
          if (proto_entry.item_index > APR_INT32_MAX)
 
1004
            return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
1005
                                    _("Item index %s too large "
 
1006
                                      "in l2p proto index for revision %ld"),
 
1007
                                    apr_psprintf(local_pool,
 
1008
                                                 "%" APR_UINT64_T_FMT,
 
1009
                                                proto_entry.item_index),
 
1010
                                    revision + page_counts->nelts);
 
1011
 
 
1012
          idx = (int)proto_entry.item_index;
 
1013
          while (idx >= entries->nelts)
 
1014
            APR_ARRAY_PUSH(entries, l2p_page_entry_t) = page_entry;
 
1015
 
 
1016
          page_entry.offset = proto_entry.offset;
 
1017
          page_entry.sub_item = proto_entry.sub_item;
 
1018
          APR_ARRAY_IDX(entries, idx, l2p_page_entry_t) = page_entry;
 
1019
        }
 
1020
    }
 
1021
 
 
1022
  /* we are now done with the source file */
 
1023
  SVN_ERR(svn_io_file_close(proto_index, local_pool));
 
1024
 
 
1025
  /* Paranoia check that makes later casting to int32 safe.
 
1026
   * The current implementation is limited to 2G pages per index. */
 
1027
  if (page_counts->nelts > APR_INT32_MAX)
 
1028
    return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
1029
                            _("L2P index page count  %d"
 
1030
                              " exceeds current limit of 2G pages"),
 
1031
                            page_counts->nelts);
 
1032
 
 
1033
  /* open target stream. */
 
1034
  stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
 
1035
                                                            local_pool),
 
1036
                                   NULL, checksum, svn_checksum_md5, FALSE,
 
1037
                                   result_pool);
 
1038
 
 
1039
 
 
1040
  /* write header info */
 
1041
  SVN_ERR(svn_stream_puts(stream, L2P_STREAM_PREFIX));
 
1042
  SVN_ERR(stream_write_encoded(stream, revision));
 
1043
  SVN_ERR(stream_write_encoded(stream, page_counts->nelts));
 
1044
  SVN_ERR(stream_write_encoded(stream, ffd->l2p_page_size));
 
1045
  SVN_ERR(stream_write_encoded(stream, page_sizes->nelts));
 
1046
 
 
1047
  /* write the revision table */
 
1048
  end = rle_array(page_counts, 0, page_counts->nelts);
 
1049
  for (i = 0; i < end; ++i)
 
1050
    {
 
1051
      apr_uint64_t value = APR_ARRAY_IDX(page_counts, i, apr_uint64_t);
 
1052
      SVN_ERR(stream_write_encoded(stream, value));
 
1053
    }
 
1054
 
 
1055
  /* write the page table */
 
1056
  for (i = 0; i < page_sizes->nelts; ++i)
 
1057
    {
 
1058
      apr_uint64_t value = APR_ARRAY_IDX(page_sizes, i, apr_uint64_t);
 
1059
      SVN_ERR(stream_write_encoded(stream, value));
 
1060
      value = APR_ARRAY_IDX(entry_counts, i, apr_uint64_t);
 
1061
      SVN_ERR(stream_write_encoded(stream, value));
 
1062
    }
 
1063
 
 
1064
  /* append page contents and implicitly close STREAM */
 
1065
  SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
 
1066
                           stream, NULL, NULL, local_pool));
 
1067
 
 
1068
  svn_pool_destroy(local_pool);
 
1069
 
 
1070
  return SVN_NO_ERROR;
 
1071
}
 
1072
 
 
1073
/* Return the base revision used to identify the p2l or lp2 index covering
 
1074
 * REVISION in FS.
 
1075
 */
 
1076
static svn_revnum_t
 
1077
base_revision(svn_fs_t *fs, svn_revnum_t revision)
 
1078
{
 
1079
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1080
  return svn_fs_x__is_packed_rev(fs, revision)
 
1081
       ? revision - (revision % ffd->max_files_per_dir)
 
1082
       : revision;
 
1083
}
 
1084
 
 
1085
/* Data structure that describes which l2p page info shall be extracted
 
1086
 * from the cache and contains the fields that receive the result.
 
1087
 */
 
1088
typedef struct l2p_page_info_baton_t
 
1089
{
 
1090
  /* input data: we want the page covering (REVISION,ITEM_INDEX) */
 
1091
  svn_revnum_t revision;
 
1092
  apr_uint64_t item_index;
 
1093
 
 
1094
  /* out data */
 
1095
  /* page location and size of the page within the l2p index file */
 
1096
  l2p_page_table_entry_t entry;
 
1097
 
 
1098
  /* page number within the pages for REVISION (not l2p index global!) */
 
1099
  apr_uint32_t page_no;
 
1100
 
 
1101
  /* offset of ITEM_INDEX within that page */
 
1102
  apr_uint32_t page_offset;
 
1103
 
 
1104
  /* revision identifying the l2p index file, also the first rev in that */
 
1105
  svn_revnum_t first_revision;
 
1106
} l2p_page_info_baton_t;
 
1107
 
 
1108
 
 
1109
/* Utility function that copies the info requested by BATON->REVISION and
 
1110
 * BATON->ITEM_INDEX and from HEADER and PAGE_TABLE into the output fields
 
1111
 * of *BATON.  Use SCRATCH_POOL for temporary allocations.
 
1112
 */
 
1113
static svn_error_t *
 
1114
l2p_header_copy(l2p_page_info_baton_t *baton,
 
1115
                const l2p_header_t *header,
 
1116
                const l2p_page_table_entry_t *page_table,
 
1117
                const apr_size_t *page_table_index,
 
1118
                apr_pool_t *scratch_pool)
 
1119
{
 
1120
  /* revision offset within the index file */
 
1121
  apr_size_t rel_revision = baton->revision - header->first_revision;
 
1122
  if (rel_revision >= header->revision_count)
 
1123
    return svn_error_createf(SVN_ERR_FS_INDEX_REVISION , NULL,
 
1124
                             _("Revision %ld not covered by item index"),
 
1125
                             baton->revision);
 
1126
 
 
1127
  /* select the relevant page */
 
1128
  if (baton->item_index < header->page_size)
 
1129
    {
 
1130
      /* most revs fit well into a single page */
 
1131
      baton->page_offset = (apr_uint32_t)baton->item_index;
 
1132
      baton->page_no = 0;
 
1133
      baton->entry = page_table[page_table_index[rel_revision]];
 
1134
    }
 
1135
  else
 
1136
    {
 
1137
      const l2p_page_table_entry_t *first_entry;
 
1138
      const l2p_page_table_entry_t *last_entry;
 
1139
      apr_uint64_t max_item_index;
 
1140
 
 
1141
      /* range of pages for this rev */
 
1142
      first_entry = page_table + page_table_index[rel_revision];
 
1143
      last_entry = page_table + page_table_index[rel_revision + 1];
 
1144
 
 
1145
      /* do we hit a valid index page? */
 
1146
      max_item_index =   (apr_uint64_t)header->page_size
 
1147
                       * (last_entry - first_entry);
 
1148
      if (baton->item_index >= max_item_index)
 
1149
        return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
1150
                                _("Item index %s exceeds l2p limit "
 
1151
                                  "of %s for revision %ld"),
 
1152
                                apr_psprintf(scratch_pool,
 
1153
                                             "%" APR_UINT64_T_FMT,
 
1154
                                             baton->item_index),
 
1155
                                apr_psprintf(scratch_pool,
 
1156
                                             "%" APR_UINT64_T_FMT,
 
1157
                                             max_item_index),
 
1158
                                baton->revision);
 
1159
 
 
1160
      /* all pages are of the same size and full, except for the last one */
 
1161
      baton->page_offset = (apr_uint32_t)(baton->item_index % header->page_size);
 
1162
      baton->page_no = (apr_uint32_t)(baton->item_index / header->page_size);
 
1163
      baton->entry = first_entry[baton->page_no];
 
1164
    }
 
1165
 
 
1166
  baton->first_revision = header->first_revision;
 
1167
 
 
1168
  return SVN_NO_ERROR;
 
1169
}
 
1170
 
 
1171
/* Implement svn_cache__partial_getter_func_t: copy the data requested in
 
1172
 * l2p_page_info_baton_t *BATON from l2p_header_t *DATA into the output
 
1173
 * fields in *BATON.
 
1174
 */
 
1175
static svn_error_t *
 
1176
l2p_header_access_func(void **out,
 
1177
                       const void *data,
 
1178
                       apr_size_t data_len,
 
1179
                       void *baton,
 
1180
                       apr_pool_t *result_pool)
 
1181
{
 
1182
  /* resolve all pointer values of in-cache data */
 
1183
  const l2p_header_t *header = data;
 
1184
  const l2p_page_table_entry_t *page_table
 
1185
    = svn_temp_deserializer__ptr(header,
 
1186
                                 (const void *const *)&header->page_table);
 
1187
  const apr_size_t *page_table_index
 
1188
    = svn_temp_deserializer__ptr(header,
 
1189
                           (const void *const *)&header->page_table_index);
 
1190
 
 
1191
  /* copy the info */
 
1192
  return l2p_header_copy(baton, header, page_table, page_table_index,
 
1193
                         result_pool);
 
1194
}
 
1195
 
 
1196
/* Read COUNT run-length-encoded (see rle_array) uint64 from STREAM and
 
1197
 * return them in VALUES.
 
1198
 */
 
1199
static svn_error_t *
 
1200
expand_rle(apr_array_header_t *values,
 
1201
           svn_fs_x__packed_number_stream_t *stream,
 
1202
           apr_size_t count)
 
1203
{
 
1204
  apr_array_clear(values);
 
1205
 
 
1206
  while (count)
 
1207
    {
 
1208
      apr_uint64_t value;
 
1209
      SVN_ERR(packed_stream_get(&value, stream));
 
1210
 
 
1211
      if (value)
 
1212
        {
 
1213
          APR_ARRAY_PUSH(values, apr_uint64_t) = value;
 
1214
          --count;
 
1215
        }
 
1216
      else
 
1217
        {
 
1218
          apr_uint64_t i;
 
1219
          apr_uint64_t repetitions;
 
1220
          SVN_ERR(packed_stream_get(&repetitions, stream));
 
1221
          if (++repetitions > count)
 
1222
            repetitions = count;
 
1223
 
 
1224
          for (i = 0; i < repetitions; ++i)
 
1225
            APR_ARRAY_PUSH(values, apr_uint64_t) = 1;
 
1226
 
 
1227
          count -= repetitions;
 
1228
        }
 
1229
    }
 
1230
 
 
1231
  return SVN_NO_ERROR;
 
1232
}
 
1233
 
 
1234
/* If REV_FILE->L2P_STREAM is NULL, create a new stream for the log-to-phys
 
1235
 * index for REVISION in FS and return it in REV_FILE.
 
1236
 */
 
1237
static svn_error_t *
 
1238
auto_open_l2p_index(svn_fs_x__revision_file_t *rev_file,
 
1239
                    svn_fs_t *fs,
 
1240
                    svn_revnum_t revision)
 
1241
{
 
1242
  if (rev_file->l2p_stream == NULL)
 
1243
    {
 
1244
      svn_fs_x__data_t *ffd = fs->fsap_data;
 
1245
 
 
1246
      SVN_ERR(svn_fs_x__auto_read_footer(rev_file));
 
1247
      SVN_ERR(packed_stream_open(&rev_file->l2p_stream,
 
1248
                                 rev_file->file,
 
1249
                                 rev_file->l2p_offset,
 
1250
                                 rev_file->p2l_offset,
 
1251
                                 L2P_STREAM_PREFIX,
 
1252
                                 (apr_size_t)ffd->block_size,
 
1253
                                 rev_file->pool,
 
1254
                                 rev_file->pool));
 
1255
    }
 
1256
 
 
1257
  return SVN_NO_ERROR;
 
1258
}
 
1259
 
 
1260
/* Read the header data structure of the log-to-phys index for REVISION
 
1261
 * in FS and return it in *HEADER, allocated in RESULT_POOL.  Use REV_FILE
 
1262
 * to access on-disk data.  Use SCRATCH_POOL for temporary allocations.
 
1263
 */
 
1264
static svn_error_t *
 
1265
get_l2p_header_body(l2p_header_t **header,
 
1266
                    svn_fs_x__revision_file_t *rev_file,
 
1267
                    svn_fs_t *fs,
 
1268
                    svn_revnum_t revision,
 
1269
                    apr_pool_t *result_pool,
 
1270
                    apr_pool_t *scratch_pool)
 
1271
{
 
1272
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1273
  apr_uint64_t value;
 
1274
  apr_size_t i;
 
1275
  apr_size_t page, page_count;
 
1276
  apr_off_t offset;
 
1277
  l2p_header_t *result = apr_pcalloc(result_pool, sizeof(*result));
 
1278
  apr_size_t page_table_index;
 
1279
  svn_revnum_t next_rev;
 
1280
  apr_array_header_t *expanded_values
 
1281
    = apr_array_make(scratch_pool, 16, sizeof(apr_uint64_t));
 
1282
 
 
1283
  svn_fs_x__pair_cache_key_t key;
 
1284
  key.revision = rev_file->start_revision;
 
1285
  key.second = rev_file->is_packed;
 
1286
 
 
1287
  SVN_ERR(auto_open_l2p_index(rev_file, fs, revision));
 
1288
  packed_stream_seek(rev_file->l2p_stream, 0);
 
1289
 
 
1290
  /* Read the table sizes.  Check the data for plausibility and
 
1291
   * consistency with other bits. */
 
1292
  SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1293
  result->first_revision = (svn_revnum_t)value;
 
1294
  if (result->first_revision != rev_file->start_revision)
 
1295
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1296
                  _("Index rev / pack file revision numbers do not match"));
 
1297
 
 
1298
  SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1299
  result->revision_count = (int)value;
 
1300
  if (   result->revision_count != 1
 
1301
      && result->revision_count != (apr_uint64_t)ffd->max_files_per_dir)
 
1302
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1303
                            _("Invalid number of revisions in L2P index"));
 
1304
 
 
1305
  SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1306
  result->page_size = (apr_uint32_t)value;
 
1307
  if (!result->page_size || (result->page_size & (result->page_size - 1)))
 
1308
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1309
                            _("L2P index page size is not a power of two"));
 
1310
 
 
1311
  SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1312
  page_count = (apr_size_t)value;
 
1313
  if (page_count < result->revision_count)
 
1314
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1315
                            _("Fewer L2P index pages than revisions"));
 
1316
  if (page_count > (rev_file->p2l_offset - rev_file->l2p_offset) / 2)
 
1317
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1318
                            _("L2P index page count implausibly large"));
 
1319
 
 
1320
  next_rev = result->first_revision + (svn_revnum_t)result->revision_count;
 
1321
  if (result->first_revision > revision || next_rev <= revision)
 
1322
    return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1323
                      _("Corrupt L2P index for r%ld only covers r%ld:%ld"),
 
1324
                      revision, result->first_revision, next_rev);
 
1325
 
 
1326
  /* allocate the page tables */
 
1327
  result->page_table
 
1328
    = apr_pcalloc(result_pool, page_count * sizeof(*result->page_table));
 
1329
  result->page_table_index
 
1330
    = apr_pcalloc(result_pool, (result->revision_count + 1)
 
1331
                             * sizeof(*result->page_table_index));
 
1332
 
 
1333
  /* read per-revision page table sizes (i.e. number of pages per rev) */
 
1334
  page_table_index = 0;
 
1335
  result->page_table_index[0] = page_table_index;
 
1336
  SVN_ERR(expand_rle(expanded_values, rev_file->l2p_stream,
 
1337
                     result->revision_count));
 
1338
  for (i = 0; i < result->revision_count; ++i)
 
1339
    {
 
1340
      value = (apr_size_t)APR_ARRAY_IDX(expanded_values, i, apr_uint64_t);
 
1341
      if (value == 0)
 
1342
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1343
                                _("Revision with no L2P index pages"));
 
1344
 
 
1345
      page_table_index += (apr_size_t)value;
 
1346
      if (page_table_index > page_count)
 
1347
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1348
                                _("L2P page table exceeded"));
 
1349
 
 
1350
      result->page_table_index[i+1] = page_table_index;
 
1351
    }
 
1352
 
 
1353
  if (page_table_index != page_count)
 
1354
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1355
                 _("Revisions do not cover the full L2P index page table"));
 
1356
 
 
1357
  /* read actual page tables */
 
1358
  for (page = 0; page < page_count; ++page)
 
1359
    {
 
1360
      SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1361
      if (value == 0)
 
1362
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1363
                                _("Empty L2P index page"));
 
1364
 
 
1365
      result->page_table[page].size = (apr_uint32_t)value;
 
1366
      SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1367
      if (value > result->page_size)
 
1368
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1369
                                _("Page exceeds L2P index page size"));
 
1370
 
 
1371
      result->page_table[page].entry_count = (apr_uint32_t)value;
 
1372
    }
 
1373
 
 
1374
  /* correct the page description offsets */
 
1375
  offset = packed_stream_offset(rev_file->l2p_stream);
 
1376
  for (page = 0; page < page_count; ++page)
 
1377
    {
 
1378
      result->page_table[page].offset = offset;
 
1379
      offset += result->page_table[page].size;
 
1380
    }
 
1381
 
 
1382
  /* return and cache the header */
 
1383
  *header = result;
 
1384
  SVN_ERR(svn_cache__set(ffd->l2p_header_cache, &key, result, scratch_pool));
 
1385
 
 
1386
  return SVN_NO_ERROR;
 
1387
}
 
1388
 
 
1389
/* Get the page info requested in *BATON from FS and set the output fields
 
1390
 * in *BATON.
 
1391
 * To maximize efficiency, use or return the data stream in *STREAM.
 
1392
 * Use SCRATCH_POOL for temporary allocations.
 
1393
 */
 
1394
static svn_error_t *
 
1395
get_l2p_page_info(l2p_page_info_baton_t *baton,
 
1396
                  svn_fs_x__revision_file_t *rev_file,
 
1397
                  svn_fs_t *fs,
 
1398
                  apr_pool_t *scratch_pool)
 
1399
{
 
1400
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1401
  l2p_header_t *result;
 
1402
  svn_boolean_t is_cached = FALSE;
 
1403
  void *dummy = NULL;
 
1404
 
 
1405
  /* try to find the info in the cache */
 
1406
  svn_fs_x__pair_cache_key_t key;
 
1407
  key.revision = base_revision(fs, baton->revision);
 
1408
  key.second = svn_fs_x__is_packed_rev(fs, baton->revision);
 
1409
  SVN_ERR(svn_cache__get_partial((void**)&dummy, &is_cached,
 
1410
                                 ffd->l2p_header_cache, &key,
 
1411
                                 l2p_header_access_func, baton,
 
1412
                                 scratch_pool));
 
1413
  if (is_cached)
 
1414
    return SVN_NO_ERROR;
 
1415
 
 
1416
  /* read from disk, cache and copy the result */
 
1417
  SVN_ERR(get_l2p_header_body(&result, rev_file, fs, baton->revision,
 
1418
                              scratch_pool, scratch_pool));
 
1419
  SVN_ERR(l2p_header_copy(baton, result, result->page_table,
 
1420
                          result->page_table_index, scratch_pool));
 
1421
 
 
1422
  return SVN_NO_ERROR;
 
1423
}
 
1424
 
 
1425
/* Read the log-to-phys header info of the index covering REVISION from FS
 
1426
 * and return it in *HEADER.  REV_FILE provides the pack / rev status.
 
1427
 * Allocate *HEADER in RESULT_POOL, use SCRATCH_POOL for temporary
 
1428
 * allocations.
 
1429
 */
 
1430
static svn_error_t *
 
1431
get_l2p_header(l2p_header_t **header,
 
1432
               svn_fs_x__revision_file_t *rev_file,
 
1433
               svn_fs_t *fs,
 
1434
               svn_revnum_t revision,
 
1435
               apr_pool_t *result_pool,
 
1436
               apr_pool_t *scratch_pool)
 
1437
{
 
1438
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1439
  svn_boolean_t is_cached = FALSE;
 
1440
 
 
1441
  /* first, try cache lookop */
 
1442
  svn_fs_x__pair_cache_key_t key;
 
1443
  key.revision = rev_file->start_revision;
 
1444
  key.second = rev_file->is_packed;
 
1445
  SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->l2p_header_cache,
 
1446
                         &key, result_pool));
 
1447
  if (is_cached)
 
1448
    return SVN_NO_ERROR;
 
1449
 
 
1450
  /* read from disk and cache the result */
 
1451
  SVN_ERR(get_l2p_header_body(header, rev_file, fs, revision, result_pool,
 
1452
                              scratch_pool));
 
1453
 
 
1454
  return SVN_NO_ERROR;
 
1455
}
 
1456
 
 
1457
/* From the log-to-phys index file starting at START_REVISION in FS, read
 
1458
 * the mapping page identified by TABLE_ENTRY and return it in *PAGE.
 
1459
 * Use REV_FILE to access on-disk files.
 
1460
 * Use RESULT_POOL for allocations.
 
1461
 */
 
1462
static svn_error_t *
 
1463
get_l2p_page(l2p_page_t **page,
 
1464
             svn_fs_x__revision_file_t *rev_file,
 
1465
             svn_fs_t *fs,
 
1466
             svn_revnum_t start_revision,
 
1467
             l2p_page_table_entry_t *table_entry,
 
1468
             apr_pool_t *result_pool)
 
1469
{
 
1470
  apr_uint64_t value, last_value = 0;
 
1471
  apr_uint32_t i;
 
1472
  l2p_page_t *result = apr_pcalloc(result_pool, sizeof(*result));
 
1473
  apr_uint64_t container_count;
 
1474
  apr_off_t *container_offsets;
 
1475
 
 
1476
  /* open index file and select page */
 
1477
  SVN_ERR(auto_open_l2p_index(rev_file, fs, start_revision));
 
1478
  packed_stream_seek(rev_file->l2p_stream, table_entry->offset);
 
1479
 
 
1480
  /* initialize the page content */
 
1481
  result->entry_count = table_entry->entry_count;
 
1482
  result->offsets = apr_pcalloc(result_pool, result->entry_count
 
1483
                                           * sizeof(*result->offsets));
 
1484
  result->sub_items = apr_pcalloc(result_pool, result->entry_count
 
1485
                                             * sizeof(*result->sub_items));
 
1486
 
 
1487
  /* container offsets array */
 
1488
 
 
1489
  SVN_ERR(packed_stream_get(&container_count, rev_file->l2p_stream));
 
1490
  container_offsets = apr_pcalloc(result_pool,
 
1491
                                  container_count * sizeof(*result));
 
1492
  for (i = 0; i < container_count; ++i)
 
1493
    {
 
1494
      SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1495
      last_value += value;
 
1496
      container_offsets[i] = (apr_off_t)last_value - 1;
 
1497
      /* '-1' is represented as '0' in the index file */
 
1498
    }
 
1499
 
 
1500
  /* read all page entries (offsets in rev file and container sub-items) */
 
1501
  for (i = 0; i < result->entry_count; ++i)
 
1502
    {
 
1503
      SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1504
      if (value == 0)
 
1505
        {
 
1506
          result->offsets[i] = -1;
 
1507
          result->sub_items[i] = 0;
 
1508
        }
 
1509
      else if (value <= container_count)
 
1510
        {
 
1511
          result->offsets[i] = container_offsets[value - 1];
 
1512
          SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
 
1513
          result->sub_items[i] = (apr_uint32_t)value;
 
1514
        }
 
1515
      else
 
1516
        {
 
1517
          result->offsets[i] = (apr_off_t)(value - 1 - container_count);
 
1518
          result->sub_items[i] = 0;
 
1519
        }
 
1520
    }
 
1521
 
 
1522
  /* After reading all page entries, the read cursor must have moved by
 
1523
   * TABLE_ENTRY->SIZE bytes. */
 
1524
  if (   packed_stream_offset(rev_file->l2p_stream)
 
1525
      != table_entry->offset + table_entry->size)
 
1526
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
1527
                _("L2P actual page size does not match page table value."));
 
1528
 
 
1529
  *page = result;
 
1530
 
 
1531
  return SVN_NO_ERROR;
 
1532
}
 
1533
 
 
1534
/* Request data structure for l2p_page_access_func.
 
1535
 */
 
1536
typedef struct l2p_page_baton_t
 
1537
{
 
1538
  /* in data */
 
1539
  /* revision. Used for error messages only */
 
1540
  svn_revnum_t revision;
 
1541
 
 
1542
  /* item index to look up. Used for error messages only */
 
1543
  apr_uint64_t item_index;
 
1544
 
 
1545
  /* offset within the cached page */
 
1546
  apr_uint32_t page_offset;
 
1547
 
 
1548
  /* out data */
 
1549
  /* absolute item or container offset in rev / pack file */
 
1550
  apr_off_t offset;
 
1551
 
 
1552
  /* 0 -> container / item itself; sub-item in container otherwise */
 
1553
  apr_uint32_t sub_item;
 
1554
 
 
1555
} l2p_page_baton_t;
 
1556
 
 
1557
/* Return the rev / pack file offset of the item at BATON->PAGE_OFFSET in
 
1558
 * OFFSETS of PAGE and write it to *OFFSET.
 
1559
 * Allocate temporaries in SCRATCH_POOL.
 
1560
 */
 
1561
static svn_error_t *
 
1562
l2p_page_get_offset(l2p_page_baton_t *baton,
 
1563
                    const l2p_page_t *page,
 
1564
                    const apr_off_t *offsets,
 
1565
                    const apr_uint32_t *sub_items,
 
1566
                    apr_pool_t *scratch_pool)
 
1567
{
 
1568
  /* overflow check */
 
1569
  if (page->entry_count <= baton->page_offset)
 
1570
    return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
1571
                             _("Item index %s too large in"
 
1572
                               " revision %ld"),
 
1573
                             apr_psprintf(scratch_pool, "%" APR_UINT64_T_FMT,
 
1574
                                          baton->item_index),
 
1575
                             baton->revision);
 
1576
 
 
1577
  /* return the result */
 
1578
  baton->offset = offsets[baton->page_offset];
 
1579
  baton->sub_item = sub_items[baton->page_offset];
 
1580
 
 
1581
  return SVN_NO_ERROR;
 
1582
}
 
1583
 
 
1584
/* Implement svn_cache__partial_getter_func_t: copy the data requested in
 
1585
 * l2p_page_baton_t *BATON from l2p_page_t *DATA into apr_off_t *OUT.
 
1586
 */
 
1587
static svn_error_t *
 
1588
l2p_page_access_func(void **out,
 
1589
                     const void *data,
 
1590
                     apr_size_t data_len,
 
1591
                     void *baton,
 
1592
                     apr_pool_t *result_pool)
 
1593
{
 
1594
  /* resolve all in-cache pointers */
 
1595
  const l2p_page_t *page = data;
 
1596
  const apr_off_t *offsets
 
1597
    = svn_temp_deserializer__ptr(page, (const void *const *)&page->offsets);
 
1598
  const apr_uint32_t *sub_items
 
1599
    = svn_temp_deserializer__ptr(page, (const void *const *)&page->sub_items);
 
1600
 
 
1601
  /* return the requested data */
 
1602
  return l2p_page_get_offset(baton, page, offsets, sub_items, result_pool);
 
1603
}
 
1604
 
 
1605
/* Data request structure used by l2p_page_table_access_func.
 
1606
 */
 
1607
typedef struct l2p_page_table_baton_t
 
1608
{
 
1609
  /* revision for which to read the page table */
 
1610
  svn_revnum_t revision;
 
1611
 
 
1612
  /* page table entries (of type l2p_page_table_entry_t).
 
1613
   * Must be created by caller and will be filled by callee. */
 
1614
  apr_array_header_t *pages;
 
1615
} l2p_page_table_baton_t;
 
1616
 
 
1617
/* Implement svn_cache__partial_getter_func_t: copy the data requested in
 
1618
 * l2p_page_baton_t *BATON from l2p_page_t *DATA into BATON->PAGES and *OUT.
 
1619
 */
 
1620
static svn_error_t *
 
1621
l2p_page_table_access_func(void **out,
 
1622
                           const void *data,
 
1623
                           apr_size_t data_len,
 
1624
                           void *baton,
 
1625
                           apr_pool_t *result_pool)
 
1626
{
 
1627
  /* resolve in-cache pointers */
 
1628
  l2p_page_table_baton_t *table_baton = baton;
 
1629
  const l2p_header_t *header = (const l2p_header_t *)data;
 
1630
  const l2p_page_table_entry_t *page_table
 
1631
    = svn_temp_deserializer__ptr(header,
 
1632
                                 (const void *const *)&header->page_table);
 
1633
  const apr_size_t *page_table_index
 
1634
    = svn_temp_deserializer__ptr(header,
 
1635
                           (const void *const *)&header->page_table_index);
 
1636
 
 
1637
  /* copy the revision's page table into BATON */
 
1638
  apr_size_t rel_revision = table_baton->revision - header->first_revision;
 
1639
  if (rel_revision < header->revision_count)
 
1640
    {
 
1641
      const l2p_page_table_entry_t *entry
 
1642
        = page_table + page_table_index[rel_revision];
 
1643
      const l2p_page_table_entry_t *last_entry
 
1644
        = page_table + page_table_index[rel_revision + 1];
 
1645
 
 
1646
      for (; entry < last_entry; ++entry)
 
1647
        APR_ARRAY_PUSH(table_baton->pages, l2p_page_table_entry_t)
 
1648
          = *entry;
 
1649
    }
 
1650
 
 
1651
  /* set output as a courtesy to the caller */
 
1652
  *out = table_baton->pages;
 
1653
 
 
1654
  return SVN_NO_ERROR;
 
1655
}
 
1656
 
 
1657
/* Read the l2p index page table for REVISION in FS from cache and return
 
1658
 * it in PAGES.  The later must be provided by the caller (and can be
 
1659
 * re-used); existing entries will be removed before writing the result.
 
1660
 * If the data cannot be found in the cache, the result will be empty
 
1661
 * (it never can be empty for a valid REVISION if the data is cached).
 
1662
 * Use the info from REV_FILE to determine pack / rev file properties.
 
1663
 * Use SCRATCH_POOL for temporary allocations.
 
1664
 */
 
1665
static svn_error_t *
 
1666
get_l2p_page_table(apr_array_header_t *pages,
 
1667
                   svn_fs_t *fs,
 
1668
                   svn_revnum_t revision,
 
1669
                   apr_pool_t *scratch_pool)
 
1670
{
 
1671
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1672
  svn_boolean_t is_cached = FALSE;
 
1673
  l2p_page_table_baton_t baton;
 
1674
 
 
1675
  svn_fs_x__pair_cache_key_t key;
 
1676
  key.revision = base_revision(fs, revision);
 
1677
  key.second = svn_fs_x__is_packed_rev(fs, revision);
 
1678
 
 
1679
  apr_array_clear(pages);
 
1680
  baton.revision = revision;
 
1681
  baton.pages = pages;
 
1682
  SVN_ERR(svn_cache__get_partial((void**)&pages, &is_cached,
 
1683
                                 ffd->l2p_header_cache, &key,
 
1684
                                 l2p_page_table_access_func, &baton,
 
1685
                                 scratch_pool));
 
1686
 
 
1687
  return SVN_NO_ERROR;
 
1688
}
 
1689
 
 
1690
/* Utility function.  Read the l2p index pages for REVISION in FS from
 
1691
 * STREAM and put them into the cache.  Skip page number EXLCUDED_PAGE_NO
 
1692
 * (use -1 for 'skip none') and pages outside the MIN_OFFSET, MAX_OFFSET
 
1693
 * range in the l2p index file.  The index is being identified by
 
1694
 * FIRST_REVISION.  PAGES is a scratch container provided by the caller.
 
1695
 * SCRATCH_POOL is used for temporary allocations.
 
1696
 *
 
1697
 * This function may be a no-op if the header cache lookup fails / misses.
 
1698
 */
 
1699
static svn_error_t *
 
1700
prefetch_l2p_pages(svn_boolean_t *end,
 
1701
                   svn_fs_t *fs,
 
1702
                   svn_fs_x__revision_file_t *rev_file,
 
1703
                   svn_revnum_t first_revision,
 
1704
                   svn_revnum_t revision,
 
1705
                   apr_array_header_t *pages,
 
1706
                   int exlcuded_page_no,
 
1707
                   apr_off_t min_offset,
 
1708
                   apr_off_t max_offset,
 
1709
                   apr_pool_t *scratch_pool)
 
1710
{
 
1711
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1712
  int i;
 
1713
  apr_pool_t *iterpool;
 
1714
  svn_fs_x__page_cache_key_t key = { 0 };
 
1715
 
 
1716
  /* Parameter check. */
 
1717
  if (min_offset < 0)
 
1718
    min_offset = 0;
 
1719
 
 
1720
  if (max_offset <= 0)
 
1721
    {
 
1722
      /* Nothing to do */
 
1723
      *end = TRUE;
 
1724
      return SVN_NO_ERROR;
 
1725
    }
 
1726
 
 
1727
  /* get the page table for REVISION from cache */
 
1728
  *end = FALSE;
 
1729
  SVN_ERR(get_l2p_page_table(pages, fs, revision, scratch_pool));
 
1730
  if (pages->nelts == 0)
 
1731
    {
 
1732
      /* not found -> we can't continue without hitting the disk again */
 
1733
      *end = TRUE;
 
1734
      return SVN_NO_ERROR;
 
1735
    }
 
1736
 
 
1737
  /* prefetch pages individually until all are done or we found one in
 
1738
   * the cache */
 
1739
  iterpool = svn_pool_create(scratch_pool);
 
1740
  assert(revision <= APR_UINT32_MAX);
 
1741
  key.revision = (apr_uint32_t)revision;
 
1742
  key.is_packed = svn_fs_x__is_packed_rev(fs, revision);
 
1743
 
 
1744
  for (i = 0; i < pages->nelts && !*end; ++i)
 
1745
    {
 
1746
      svn_boolean_t is_cached;
 
1747
 
 
1748
      l2p_page_table_entry_t *entry
 
1749
        = &APR_ARRAY_IDX(pages, i, l2p_page_table_entry_t);
 
1750
      svn_pool_clear(iterpool);
 
1751
 
 
1752
      if (i == exlcuded_page_no)
 
1753
        continue;
 
1754
 
 
1755
      /* skip pages outside the specified index file range */
 
1756
      if (   entry->offset < (apr_uint64_t)min_offset
 
1757
          || entry->offset + entry->size > (apr_uint64_t)max_offset)
 
1758
        {
 
1759
          *end = TRUE;
 
1760
          continue;
 
1761
        }
 
1762
 
 
1763
      /* page already in cache? */
 
1764
      key.page = i;
 
1765
      SVN_ERR(svn_cache__has_key(&is_cached, ffd->l2p_page_cache,
 
1766
                                 &key, iterpool));
 
1767
      if (!is_cached)
 
1768
        {
 
1769
          /* no in cache -> read from stream (data already buffered in APR)
 
1770
           * and cache the result */
 
1771
          l2p_page_t *page = NULL;
 
1772
          SVN_ERR(get_l2p_page(&page, rev_file, fs, first_revision,
 
1773
                               entry, iterpool));
 
1774
 
 
1775
          SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page,
 
1776
                                 iterpool));
 
1777
        }
 
1778
    }
 
1779
 
 
1780
  svn_pool_destroy(iterpool);
 
1781
 
 
1782
  return SVN_NO_ERROR;
 
1783
}
 
1784
 
 
1785
/* Using the log-to-phys indexes in FS, find the absolute offset in the
 
1786
 * rev file for (REVISION, ITEM_INDEX) and return it in *OFFSET.
 
1787
 * Use SCRATCH_POOL for temporary allocations.
 
1788
 */
 
1789
static svn_error_t *
 
1790
l2p_index_lookup(apr_off_t *offset,
 
1791
                 apr_uint32_t *sub_item,
 
1792
                 svn_fs_t *fs,
 
1793
                 svn_fs_x__revision_file_t *rev_file,
 
1794
                 svn_revnum_t revision,
 
1795
                 apr_uint64_t item_index,
 
1796
                 apr_pool_t *scratch_pool)
 
1797
{
 
1798
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
1799
  l2p_page_info_baton_t info_baton;
 
1800
  l2p_page_baton_t page_baton;
 
1801
  l2p_page_t *page = NULL;
 
1802
  svn_fs_x__page_cache_key_t key = { 0 };
 
1803
  svn_boolean_t is_cached = FALSE;
 
1804
  void *dummy = NULL;
 
1805
 
 
1806
  /* read index master data structure and extract the info required to
 
1807
   * access the l2p index page for (REVISION,ITEM_INDEX)*/
 
1808
  info_baton.revision = revision;
 
1809
  info_baton.item_index = item_index;
 
1810
  SVN_ERR(get_l2p_page_info(&info_baton, rev_file, fs, scratch_pool));
 
1811
 
 
1812
  /* try to find the page in the cache and get the OFFSET from it */
 
1813
  page_baton.revision = revision;
 
1814
  page_baton.item_index = item_index;
 
1815
  page_baton.page_offset = info_baton.page_offset;
 
1816
 
 
1817
  assert(revision <= APR_UINT32_MAX);
 
1818
  key.revision = (apr_uint32_t)revision;
 
1819
  key.is_packed = svn_fs_x__is_packed_rev(fs, revision);
 
1820
  key.page = info_baton.page_no;
 
1821
 
 
1822
  SVN_ERR(svn_cache__get_partial(&dummy, &is_cached,
 
1823
                                 ffd->l2p_page_cache, &key,
 
1824
                                 l2p_page_access_func, &page_baton,
 
1825
                                 scratch_pool));
 
1826
 
 
1827
  if (!is_cached)
 
1828
    {
 
1829
      /* we need to read the info from disk (might already be in the
 
1830
       * APR file buffer, though) */
 
1831
      apr_array_header_t *pages;
 
1832
      svn_revnum_t prefetch_revision;
 
1833
      svn_revnum_t last_revision
 
1834
        = info_baton.first_revision
 
1835
          + svn_fs_x__pack_size(fs, info_baton.first_revision);
 
1836
      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
1837
      svn_boolean_t end;
 
1838
      apr_off_t max_offset
 
1839
        = APR_ALIGN(info_baton.entry.offset + info_baton.entry.size,
 
1840
                    ffd->block_size);
 
1841
      apr_off_t min_offset = max_offset - ffd->block_size;
 
1842
 
 
1843
      /* read the relevant page */
 
1844
      SVN_ERR(get_l2p_page(&page, rev_file, fs, info_baton.first_revision,
 
1845
                           &info_baton.entry, scratch_pool));
 
1846
 
 
1847
      /* cache the page and extract the result we need */
 
1848
      SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page, scratch_pool));
 
1849
      SVN_ERR(l2p_page_get_offset(&page_baton, page, page->offsets,
 
1850
                                  page->sub_items, scratch_pool));
 
1851
 
 
1852
      /* prefetch pages from following and preceding revisions */
 
1853
      pages = apr_array_make(scratch_pool, 16,
 
1854
                             sizeof(l2p_page_table_entry_t));
 
1855
      end = FALSE;
 
1856
      for (prefetch_revision = revision;
 
1857
           prefetch_revision < last_revision && !end;
 
1858
           ++prefetch_revision)
 
1859
        {
 
1860
          int excluded_page_no = prefetch_revision == revision
 
1861
                               ? info_baton.page_no
 
1862
                               : -1;
 
1863
          svn_pool_clear(iterpool);
 
1864
 
 
1865
          SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
 
1866
                                     info_baton.first_revision,
 
1867
                                     prefetch_revision, pages,
 
1868
                                     excluded_page_no, min_offset,
 
1869
                                     max_offset, iterpool));
 
1870
        }
 
1871
 
 
1872
      end = FALSE;
 
1873
      for (prefetch_revision = revision-1;
 
1874
           prefetch_revision >= info_baton.first_revision && !end;
 
1875
           --prefetch_revision)
 
1876
        {
 
1877
          svn_pool_clear(iterpool);
 
1878
 
 
1879
          SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
 
1880
                                     info_baton.first_revision,
 
1881
                                     prefetch_revision, pages, -1,
 
1882
                                     min_offset, max_offset, iterpool));
 
1883
        }
 
1884
 
 
1885
      svn_pool_destroy(iterpool);
 
1886
    }
 
1887
 
 
1888
  *offset = page_baton.offset;
 
1889
  *sub_item = page_baton.sub_item;
 
1890
 
 
1891
  return SVN_NO_ERROR;
 
1892
}
 
1893
 
 
1894
/* Using the log-to-phys proto index in transaction TXN_ID in FS, find the
 
1895
 * absolute offset in the proto rev file for the given ITEM_INDEX and return
 
1896
 * it in *OFFSET.  Use SCRATCH_POOL for temporary allocations.
 
1897
 */
 
1898
static svn_error_t *
 
1899
l2p_proto_index_lookup(apr_off_t *offset,
 
1900
                       apr_uint32_t *sub_item,
 
1901
                       svn_fs_t *fs,
 
1902
                       svn_fs_x__txn_id_t txn_id,
 
1903
                       apr_uint64_t item_index,
 
1904
                       apr_pool_t *scratch_pool)
 
1905
{
 
1906
  svn_boolean_t eof = FALSE;
 
1907
  apr_file_t *file = NULL;
 
1908
  SVN_ERR(svn_io_file_open(&file,
 
1909
                           svn_fs_x__path_l2p_proto_index(fs, txn_id,
 
1910
                                                          scratch_pool),
 
1911
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
 
1912
                           scratch_pool));
 
1913
 
 
1914
  /* process all entries until we fail due to EOF */
 
1915
  *offset = -1;
 
1916
  while (!eof)
 
1917
    {
 
1918
      l2p_proto_entry_t entry;
 
1919
 
 
1920
      /* (attempt to) read the next entry from the source */
 
1921
      SVN_ERR(read_l2p_entry_from_proto_index(file, &entry, &eof,
 
1922
                                              scratch_pool));
 
1923
 
 
1924
      /* handle new revision */
 
1925
      if (!eof && entry.item_index == item_index)
 
1926
        {
 
1927
          *offset = (apr_off_t)entry.offset - 1;
 
1928
          *sub_item = entry.sub_item;
 
1929
          break;
 
1930
        }
 
1931
    }
 
1932
 
 
1933
  SVN_ERR(svn_io_file_close(file, scratch_pool));
 
1934
 
 
1935
  return SVN_NO_ERROR;
 
1936
}
 
1937
 
 
1938
svn_error_t *
 
1939
svn_fs_x__l2p_get_max_ids(apr_array_header_t **max_ids,
 
1940
                          svn_fs_t *fs,
 
1941
                          svn_revnum_t start_rev,
 
1942
                          apr_size_t count,
 
1943
                          apr_pool_t *result_pool,
 
1944
                          apr_pool_t *scratch_pool)
 
1945
{
 
1946
  l2p_header_t *header = NULL;
 
1947
  svn_revnum_t revision;
 
1948
  svn_revnum_t last_rev = (svn_revnum_t)(start_rev + count);
 
1949
  svn_fs_x__revision_file_t *rev_file;
 
1950
  apr_pool_t *header_pool = svn_pool_create(scratch_pool);
 
1951
 
 
1952
  /* read index master data structure for the index covering START_REV */
 
1953
  SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, start_rev,
 
1954
                                          header_pool, header_pool));
 
1955
  SVN_ERR(get_l2p_header(&header, rev_file, fs, start_rev, header_pool,
 
1956
                         header_pool));
 
1957
  SVN_ERR(svn_fs_x__close_revision_file(rev_file));
 
1958
 
 
1959
  /* Determine the length of the item index list for each rev.
 
1960
   * Read new index headers as required. */
 
1961
  *max_ids = apr_array_make(result_pool, (int)count, sizeof(apr_uint64_t));
 
1962
  for (revision = start_rev; revision < last_rev; ++revision)
 
1963
    {
 
1964
      apr_uint64_t full_page_count;
 
1965
      apr_uint64_t item_count;
 
1966
      apr_size_t first_page_index, last_page_index;
 
1967
 
 
1968
      if (revision >= header->first_revision + header->revision_count)
 
1969
        {
 
1970
          /* need to read the next index. Clear up memory used for the
 
1971
           * previous one.  Note that intermittent pack runs do not change
 
1972
           * the number of items in a revision, i.e. there is no consistency
 
1973
           * issue here. */
 
1974
          svn_pool_clear(header_pool);
 
1975
          SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, revision,
 
1976
                                                  header_pool, header_pool));
 
1977
          SVN_ERR(get_l2p_header(&header, rev_file, fs, revision,
 
1978
                                 header_pool, header_pool));
 
1979
          SVN_ERR(svn_fs_x__close_revision_file(rev_file));
 
1980
        }
 
1981
 
 
1982
      /* in a revision with N index pages, the first N-1 index pages are
 
1983
       * "full", i.e. contain HEADER->PAGE_SIZE entries */
 
1984
      first_page_index
 
1985
         = header->page_table_index[revision - header->first_revision];
 
1986
      last_page_index
 
1987
         = header->page_table_index[revision - header->first_revision + 1];
 
1988
      full_page_count = last_page_index - first_page_index - 1;
 
1989
      item_count = full_page_count * header->page_size
 
1990
                 + header->page_table[last_page_index - 1].entry_count;
 
1991
 
 
1992
      APR_ARRAY_PUSH(*max_ids, apr_uint64_t) = item_count;
 
1993
    }
 
1994
 
 
1995
  svn_pool_destroy(header_pool);
 
1996
  return SVN_NO_ERROR;
 
1997
}
 
1998
 
 
1999
/*
 
2000
 * phys-to-log index
 
2001
 */
 
2002
svn_fs_x__p2l_entry_t *
 
2003
svn_fs_x__p2l_entry_dup(const svn_fs_x__p2l_entry_t *entry,
 
2004
                        apr_pool_t *result_pool)
 
2005
{
 
2006
  svn_fs_x__p2l_entry_t *new_entry = apr_pmemdup(result_pool, entry,
 
2007
                                                 sizeof(*new_entry));
 
2008
  if (new_entry->item_count)
 
2009
    new_entry->items = apr_pmemdup(result_pool,
 
2010
                                   entry->items,
 
2011
                                   entry->item_count * sizeof(*entry->items));
 
2012
 
 
2013
  return new_entry;
 
2014
}
 
2015
 
 
2016
/*
 
2017
 * phys-to-log index
 
2018
 */
 
2019
svn_error_t *
 
2020
svn_fs_x__p2l_proto_index_open(apr_file_t **proto_index,
 
2021
                               const char *file_name,
 
2022
                               apr_pool_t *result_pool)
 
2023
{
 
2024
  SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
 
2025
                           | APR_CREATE | APR_APPEND | APR_BUFFERED,
 
2026
                           APR_OS_DEFAULT, result_pool));
 
2027
 
 
2028
  return SVN_NO_ERROR;
 
2029
}
 
2030
 
 
2031
 
 
2032
svn_error_t *
 
2033
svn_fs_x__p2l_proto_index_add_entry(apr_file_t *proto_index,
 
2034
                                    const svn_fs_x__p2l_entry_t *entry,
 
2035
                                    apr_pool_t *scratch_pool)
 
2036
{
 
2037
  apr_uint64_t revision;
 
2038
  apr_int32_t i;
 
2039
 
 
2040
  /* Make sure all signed elements of ENTRY have non-negative values.
 
2041
   *
 
2042
   * For file offsets and sizes, this is a given as we use them to describe
 
2043
   * absolute positions and sizes.  For revisions, SVN_INVALID_REVNUM is
 
2044
   * valid, hence we have to shift it by 1.
 
2045
   */
 
2046
  SVN_ERR_ASSERT(entry->offset >= 0);
 
2047
  SVN_ERR_ASSERT(entry->size >= 0);
 
2048
 
 
2049
  /* Now, all values will nicely convert to uint64. */
 
2050
  /* Make sure to keep P2L_PROTO_INDEX_ENTRY_SIZE consistent with this: */
 
2051
 
 
2052
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry->offset,
 
2053
                                      scratch_pool));
 
2054
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry->size,
 
2055
                                      scratch_pool));
 
2056
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry->type,
 
2057
                                      scratch_pool));
 
2058
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry->fnv1_checksum,
 
2059
                                      scratch_pool));
 
2060
  SVN_ERR(write_uint64_to_proto_index(proto_index, entry->item_count,
 
2061
                                      scratch_pool));
 
2062
 
 
2063
  /* Add sub-items. */
 
2064
  for (i = 0; i < entry->item_count; ++i)
 
2065
    {
 
2066
      const svn_fs_x__id_t *sub_item = &entry->items[i];
 
2067
 
 
2068
      /* Make sure all signed elements of ENTRY have non-negative values.
 
2069
       *
 
2070
       * For file offsets and sizes, this is a given as we use them to
 
2071
       * describe absolute positions and sizes.  For revisions,
 
2072
       * SVN_INVALID_REVNUM is valid, hence we have to shift it by 1.
 
2073
      */
 
2074
      SVN_ERR_ASSERT(   sub_item->change_set >= 0
 
2075
                     || sub_item->change_set == SVN_INVALID_REVNUM);
 
2076
 
 
2077
      /* Write sub- record. */
 
2078
      revision = sub_item->change_set == SVN_INVALID_REVNUM
 
2079
               ? 0
 
2080
               : ((apr_uint64_t)sub_item->change_set + 1);
 
2081
 
 
2082
      SVN_ERR(write_uint64_to_proto_index(proto_index, revision,
 
2083
                                          scratch_pool));
 
2084
      SVN_ERR(write_uint64_to_proto_index(proto_index, sub_item->number,
 
2085
                                          scratch_pool));
 
2086
    }
 
2087
 
 
2088
  /* Add trailer: rev / pack file offset of the next item */
 
2089
  SVN_ERR(write_uint64_to_proto_index(proto_index,
 
2090
                                      entry->offset + entry->size,
 
2091
                                      scratch_pool));
 
2092
 
 
2093
  return SVN_NO_ERROR;
 
2094
}
 
2095
 
 
2096
/* Read *ENTRY from log-to-phys PROTO_INDEX file and indicate end-of-file
 
2097
 * in *EOF, or error out in that case if EOF is NULL.  *ENTRY is in an
 
2098
 * undefined state if an end-of-file occurred.
 
2099
 * Use SCRATCH_POOL for temporary allocations.
 
2100
 */
 
2101
static svn_error_t *
 
2102
read_p2l_entry_from_proto_index(apr_file_t *proto_index,
 
2103
                                svn_fs_x__p2l_entry_t *entry,
 
2104
                                svn_boolean_t *eof,
 
2105
                                apr_pool_t *scratch_pool)
 
2106
{
 
2107
  SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->offset,
 
2108
                                      eof, scratch_pool));
 
2109
  SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->size,
 
2110
                                      eof, scratch_pool));
 
2111
  SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->type,
 
2112
                                       eof, scratch_pool));
 
2113
  SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->fnv1_checksum,
 
2114
                                       eof, scratch_pool));
 
2115
  SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->item_count,
 
2116
                                       eof, scratch_pool));
 
2117
 
 
2118
  return SVN_NO_ERROR;
 
2119
}
 
2120
 
 
2121
static svn_error_t *
 
2122
read_p2l_sub_items_from_proto_index(apr_file_t *proto_index,
 
2123
                                    svn_fs_x__p2l_entry_t *entry,
 
2124
                                    svn_boolean_t *eof,
 
2125
                                    apr_pool_t *scratch_pool)
 
2126
{
 
2127
  apr_int32_t i;
 
2128
  for (i = 0; i < entry->item_count; ++i)
 
2129
    {
 
2130
      apr_uint64_t revision;
 
2131
      svn_fs_x__id_t *sub_item = &entry->items[i];
 
2132
 
 
2133
      SVN_ERR(read_uint64_from_proto_index(proto_index, &revision,
 
2134
                                           eof, scratch_pool));
 
2135
      SVN_ERR(read_uint64_from_proto_index(proto_index, &sub_item->number,
 
2136
                                           eof, scratch_pool));
 
2137
 
 
2138
      /* Do the inverse REVSION number conversion (see
 
2139
       * svn_fs_x__p2l_proto_index_add_entry), if we actually read a
 
2140
       * complete record.
 
2141
      */
 
2142
      if (!eof || !*eof)
 
2143
        {
 
2144
          /* Be careful with the arithmetics here (overflows and wrap-around):
 
2145
           */
 
2146
          if (revision > 0 && revision - 1 > LONG_MAX)
 
2147
            return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
 
2148
                                     _("Revision 0x%s too large, max = 0x%s"),
 
2149
                                     apr_psprintf(scratch_pool,
 
2150
                                                  "%" APR_UINT64_T_FMT,
 
2151
                                                  revision),
 
2152
                                     apr_psprintf(scratch_pool,
 
2153
                                                  "%" APR_UINT64_T_FMT,
 
2154
                                                  (apr_uint64_t)LONG_MAX));
 
2155
 
 
2156
          /* Shortening conversion from unsigned to signed int is well-
 
2157
           * defined and not lossy in C because the value can be represented
 
2158
           * in the target type.  Also, cast to 'long' instead of
 
2159
           * 'svn_revnum_t' here to provoke a compiler warning if those
 
2160
           * types should differ and we would need to change the overflow
 
2161
           * checking logic.
 
2162
           */
 
2163
          sub_item->change_set = revision == 0
 
2164
                               ? SVN_INVALID_REVNUM
 
2165
                               : (long)(revision - 1);
 
2166
        }
 
2167
 
 
2168
    }
 
2169
 
 
2170
  return SVN_NO_ERROR;
 
2171
}
 
2172
 
 
2173
svn_error_t *
 
2174
svn_fs_x__p2l_proto_index_next_offset(apr_off_t *next_offset,
 
2175
                                      apr_file_t *proto_index,
 
2176
                                      apr_pool_t *scratch_pool)
 
2177
{
 
2178
  apr_off_t offset = 0;
 
2179
 
 
2180
  /* Empty index file? */
 
2181
  SVN_ERR(svn_io_file_seek(proto_index, APR_END, &offset, scratch_pool));
 
2182
  if (offset == 0)
 
2183
    {
 
2184
      *next_offset = 0;
 
2185
    }
 
2186
  else
 
2187
    {
 
2188
      /* The last 64 bits contain the value we are looking for. */
 
2189
      offset -= sizeof(apr_uint64_t);
 
2190
      SVN_ERR(svn_io_file_seek(proto_index, APR_SET, &offset, scratch_pool));
 
2191
      SVN_ERR(read_off_t_from_proto_index(proto_index, next_offset, NULL,
 
2192
                                          scratch_pool));
 
2193
    }
 
2194
 
 
2195
  return SVN_NO_ERROR;
 
2196
}
 
2197
 
 
2198
svn_error_t *
 
2199
svn_fs_x__p2l_index_append(svn_checksum_t **checksum,
 
2200
                           svn_fs_t *fs,
 
2201
                           apr_file_t *index_file,
 
2202
                           const char *proto_file_name,
 
2203
                           svn_revnum_t revision,
 
2204
                           apr_pool_t *result_pool,
 
2205
                           apr_pool_t *scratch_pool)
 
2206
{
 
2207
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
2208
  apr_uint64_t page_size = ffd->p2l_page_size;
 
2209
  apr_file_t *proto_index = NULL;
 
2210
  svn_stream_t *stream;
 
2211
  int i;
 
2212
  apr_uint32_t sub_item;
 
2213
  svn_boolean_t eof = FALSE;
 
2214
  unsigned char encoded[ENCODED_INT_LENGTH];
 
2215
 
 
2216
  apr_uint64_t last_entry_end = 0;
 
2217
  apr_uint64_t last_page_end = 0;
 
2218
  apr_size_t last_buffer_size = 0;  /* byte offset in the spill buffer at
 
2219
                                       the begin of the current revision */
 
2220
  apr_uint64_t file_size = 0;
 
2221
 
 
2222
  /* temporary data structures that collect the data which will be moved
 
2223
     to the target file in a second step */
 
2224
  apr_pool_t *local_pool = svn_pool_create(scratch_pool);
 
2225
  apr_array_header_t *table_sizes
 
2226
     = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
 
2227
 
 
2228
  /* 64k blocks, spill after 16MB */
 
2229
  svn_spillbuf_t *buffer
 
2230
     = svn_spillbuf__create(0x10000, 0x1000000, local_pool);
 
2231
 
 
2232
  /* for loop temps ... */
 
2233
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
2234
 
 
2235
  /* start at the beginning of the source file */
 
2236
  SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
 
2237
                           APR_READ | APR_CREATE | APR_BUFFERED,
 
2238
                           APR_OS_DEFAULT, local_pool));
 
2239
 
 
2240
  /* process all entries until we fail due to EOF */
 
2241
  while (!eof)
 
2242
    {
 
2243
      svn_fs_x__p2l_entry_t entry;
 
2244
      apr_uint64_t entry_end;
 
2245
      svn_boolean_t new_page = svn_spillbuf__get_size(buffer) == 0;
 
2246
      svn_revnum_t last_revision = revision;
 
2247
      apr_uint64_t last_number = 0;
 
2248
 
 
2249
      svn_pool_clear(iterpool);
 
2250
 
 
2251
      /* (attempt to) read the next entry from the source */
 
2252
      SVN_ERR(read_p2l_entry_from_proto_index(proto_index, &entry,
 
2253
                                              &eof, iterpool));
 
2254
 
 
2255
      if (entry.item_count && !eof)
 
2256
        {
 
2257
          entry.items = apr_palloc(iterpool,
 
2258
                                   entry.item_count * sizeof(*entry.items));
 
2259
          SVN_ERR(read_p2l_sub_items_from_proto_index(proto_index, &entry,
 
2260
                                                      &eof, iterpool));
 
2261
        }
 
2262
 
 
2263
      /* Read entry trailer. However, we won't need its content. */
 
2264
      if (!eof)
 
2265
        {
 
2266
          apr_uint64_t trailer;
 
2267
          SVN_ERR(read_uint64_from_proto_index(proto_index, &trailer, &eof,
 
2268
                                               scratch_pool));
 
2269
        }
 
2270
 
 
2271
      /* "unused" (and usually non-existent) section to cover the offsets
 
2272
         at the end the of the last page. */
 
2273
      if (eof)
 
2274
        {
 
2275
          file_size = last_entry_end;
 
2276
 
 
2277
          entry.offset = last_entry_end;
 
2278
          entry.size = APR_ALIGN(entry.offset, page_size) - entry.offset;
 
2279
          entry.type = SVN_FS_X__ITEM_TYPE_UNUSED;
 
2280
          entry.fnv1_checksum = 0;
 
2281
          entry.item_count = 0;
 
2282
          entry.items = NULL;
 
2283
        }
 
2284
 
 
2285
      for (sub_item = 0; sub_item < entry.item_count; ++sub_item)
 
2286
        if (entry.items[sub_item].change_set == SVN_FS_X__INVALID_CHANGE_SET)
 
2287
          entry.items[sub_item].change_set
 
2288
            = svn_fs_x__change_set_by_rev(revision);
 
2289
 
 
2290
      /* end pages if entry is extending beyond their boundaries */
 
2291
      entry_end = entry.offset + entry.size;
 
2292
      while (entry_end - last_page_end > page_size)
 
2293
        {
 
2294
          apr_uint64_t buffer_size = svn_spillbuf__get_size(buffer);
 
2295
          APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
 
2296
             = buffer_size - last_buffer_size;
 
2297
 
 
2298
          last_buffer_size = buffer_size;
 
2299
          last_page_end += page_size;
 
2300
          new_page = TRUE;
 
2301
        }
 
2302
 
 
2303
      /* this entry starts a new table -> store its offset
 
2304
         (all following entries in the same table will store sizes only) */
 
2305
      if (new_page)
 
2306
        {
 
2307
          SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2308
                                      encode_uint(encoded, entry.offset),
 
2309
                                      iterpool));
 
2310
          last_revision = revision;
 
2311
        }
 
2312
 
 
2313
      /* write simple item / container entry */
 
2314
      SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2315
                                  encode_uint(encoded, entry.size),
 
2316
                                  iterpool));
 
2317
      SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2318
                                  encode_uint(encoded, entry.type + entry.item_count * 16),
 
2319
                                  iterpool));
 
2320
      SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2321
                                  encode_uint(encoded, entry.fnv1_checksum),
 
2322
                                  iterpool));
 
2323
 
 
2324
      /* container contents (only one for non-container items) */
 
2325
      for (sub_item = 0; sub_item < entry.item_count; ++sub_item)
 
2326
        {
 
2327
          svn_revnum_t item_rev
 
2328
            = svn_fs_x__get_revnum(entry.items[sub_item].change_set);
 
2329
          apr_int64_t diff = item_rev - last_revision;
 
2330
          SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2331
                                      encode_int(encoded, diff),
 
2332
                                      iterpool));
 
2333
          last_revision = item_rev;
 
2334
        }
 
2335
 
 
2336
      for (sub_item = 0; sub_item < entry.item_count; ++sub_item)
 
2337
        {
 
2338
          apr_int64_t diff = entry.items[sub_item].number - last_number;
 
2339
          SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
 
2340
                                      encode_int(encoded, diff),
 
2341
                                      iterpool));
 
2342
          last_number = entry.items[sub_item].number;
 
2343
        }
 
2344
 
 
2345
      last_entry_end = entry_end;
 
2346
    }
 
2347
 
 
2348
  /* close the source file */
 
2349
  SVN_ERR(svn_io_file_close(proto_index, local_pool));
 
2350
 
 
2351
  /* store length of last table */
 
2352
  APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
 
2353
      = svn_spillbuf__get_size(buffer) - last_buffer_size;
 
2354
 
 
2355
  /* Open target stream. */
 
2356
  stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
 
2357
                                                            local_pool),
 
2358
                                   NULL, checksum, svn_checksum_md5, FALSE,
 
2359
                                   result_pool);
 
2360
 
 
2361
  /* write the start revision, file size and page size */
 
2362
  SVN_ERR(svn_stream_puts(stream, P2L_STREAM_PREFIX));
 
2363
  SVN_ERR(stream_write_encoded(stream, revision));
 
2364
  SVN_ERR(stream_write_encoded(stream, file_size));
 
2365
  SVN_ERR(stream_write_encoded(stream, page_size));
 
2366
 
 
2367
  /* write the page table (actually, the sizes of each page description) */
 
2368
  SVN_ERR(stream_write_encoded(stream, table_sizes->nelts));
 
2369
  for (i = 0; i < table_sizes->nelts; ++i)
 
2370
    {
 
2371
      apr_uint64_t value = APR_ARRAY_IDX(table_sizes, i, apr_uint64_t);
 
2372
      SVN_ERR(stream_write_encoded(stream, value));
 
2373
    }
 
2374
 
 
2375
  /* append page contents and implicitly close STREAM */
 
2376
  SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
 
2377
                           stream, NULL, NULL, local_pool));
 
2378
 
 
2379
  svn_pool_destroy(iterpool);
 
2380
  svn_pool_destroy(local_pool);
 
2381
 
 
2382
  return SVN_NO_ERROR;
 
2383
}
 
2384
 
 
2385
/* If REV_FILE->P2L_STREAM is NULL, create a new stream for the phys-to-log
 
2386
 * index for REVISION in FS using the rev / pack file provided by REV_FILE.
 
2387
 */
 
2388
static svn_error_t *
 
2389
auto_open_p2l_index(svn_fs_x__revision_file_t *rev_file,
 
2390
                    svn_fs_t *fs,
 
2391
                    svn_revnum_t revision)
 
2392
{
 
2393
  if (rev_file->p2l_stream == NULL)
 
2394
    {
 
2395
      svn_fs_x__data_t *ffd = fs->fsap_data;
 
2396
 
 
2397
      SVN_ERR(svn_fs_x__auto_read_footer(rev_file));
 
2398
      SVN_ERR(packed_stream_open(&rev_file->p2l_stream,
 
2399
                                 rev_file->file,
 
2400
                                 rev_file->p2l_offset,
 
2401
                                 rev_file->footer_offset,
 
2402
                                 P2L_STREAM_PREFIX,
 
2403
                                 (apr_size_t)ffd->block_size,
 
2404
                                 rev_file->pool,
 
2405
                                 rev_file->pool));
 
2406
    }
 
2407
 
 
2408
  return SVN_NO_ERROR;
 
2409
}
 
2410
 
 
2411
/* Data structure that describes which p2l page info shall be extracted
 
2412
 * from the cache and contains the fields that receive the result.
 
2413
 */
 
2414
typedef struct p2l_page_info_baton_t
 
2415
{
 
2416
  /* input variables */
 
2417
  /* revision identifying the index file */
 
2418
  svn_revnum_t revision;
 
2419
 
 
2420
  /* offset within the page in rev / pack file */
 
2421
  apr_off_t offset;
 
2422
 
 
2423
  /* output variables */
 
2424
  /* page containing OFFSET */
 
2425
  apr_size_t page_no;
 
2426
 
 
2427
  /* first revision in this p2l index */
 
2428
  svn_revnum_t first_revision;
 
2429
 
 
2430
  /* offset within the p2l index file describing this page */
 
2431
  apr_off_t start_offset;
 
2432
 
 
2433
  /* offset within the p2l index file describing the following page */
 
2434
  apr_off_t next_offset;
 
2435
 
 
2436
  /* PAGE_NO * PAGE_SIZE (if <= OFFSET) */
 
2437
  apr_off_t page_start;
 
2438
 
 
2439
  /* total number of pages indexed */
 
2440
  apr_size_t page_count;
 
2441
 
 
2442
  /* size of each page in pack / rev file */
 
2443
  apr_uint64_t page_size;
 
2444
} p2l_page_info_baton_t;
 
2445
 
 
2446
/* From HEADER and the list of all OFFSETS, fill BATON with the page info
 
2447
 * requested by BATON->OFFSET.
 
2448
 */
 
2449
static void
 
2450
p2l_page_info_copy(p2l_page_info_baton_t *baton,
 
2451
                   const p2l_header_t *header,
 
2452
                   const apr_off_t *offsets)
 
2453
{
 
2454
  /* if the requested offset is out of bounds, return info for
 
2455
   * a zero-sized empty page right behind the last page.
 
2456
   */
 
2457
  if (baton->offset / header->page_size < header->page_count)
 
2458
    {
 
2459
      /* This cast is safe because the value is < header->page_count. */
 
2460
      baton->page_no = (apr_size_t)(baton->offset / header->page_size);
 
2461
      baton->start_offset = offsets[baton->page_no];
 
2462
      baton->next_offset = offsets[baton->page_no + 1];
 
2463
      baton->page_size = header->page_size;
 
2464
    }
 
2465
  else
 
2466
    {
 
2467
      /* Beyond the last page. */
 
2468
      baton->page_no = header->page_count;
 
2469
      baton->start_offset = offsets[baton->page_no];
 
2470
      baton->next_offset = offsets[baton->page_no];
 
2471
      baton->page_size = 0;
 
2472
    }
 
2473
 
 
2474
  baton->first_revision = header->first_revision;
 
2475
  baton->page_start = (apr_off_t)(header->page_size * baton->page_no);
 
2476
  baton->page_count = header->page_count;
 
2477
}
 
2478
 
 
2479
/* Implement svn_cache__partial_getter_func_t: extract the p2l page info
 
2480
 * requested by BATON and return it in BATON.
 
2481
 */
 
2482
static svn_error_t *
 
2483
p2l_page_info_func(void **out,
 
2484
                   const void *data,
 
2485
                   apr_size_t data_len,
 
2486
                   void *baton,
 
2487
                   apr_pool_t *result_pool)
 
2488
{
 
2489
  /* all the pointers to cached data we need */
 
2490
  const p2l_header_t *header = data;
 
2491
  const apr_off_t *offsets
 
2492
    = svn_temp_deserializer__ptr(header,
 
2493
                                 (const void *const *)&header->offsets);
 
2494
 
 
2495
  /* copy data from cache to BATON */
 
2496
  p2l_page_info_copy(baton, header, offsets);
 
2497
  return SVN_NO_ERROR;
 
2498
}
 
2499
 
 
2500
/* Read the header data structure of the phys-to-log index for REVISION in
 
2501
 * FS and return it in *HEADER, allocated in RESULT_POOL. Use REV_FILE to
 
2502
 * access on-disk data.  Use SCRATCH_POOL for temporary allocations.
 
2503
 */
 
2504
static svn_error_t *
 
2505
get_p2l_header(p2l_header_t **header,
 
2506
               svn_fs_x__revision_file_t *rev_file,
 
2507
               svn_fs_t *fs,
 
2508
               svn_revnum_t revision,
 
2509
               apr_pool_t *result_pool,
 
2510
               apr_pool_t *scratch_pool)
 
2511
{
 
2512
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
2513
  apr_uint64_t value;
 
2514
  apr_size_t i;
 
2515
  apr_off_t offset;
 
2516
  p2l_header_t *result;
 
2517
  svn_boolean_t is_cached = FALSE;
 
2518
 
 
2519
  /* look for the header data in our cache */
 
2520
  svn_fs_x__pair_cache_key_t key;
 
2521
  key.revision = rev_file->start_revision;
 
2522
  key.second = rev_file->is_packed;
 
2523
 
 
2524
  SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->p2l_header_cache,
 
2525
                         &key, result_pool));
 
2526
  if (is_cached)
 
2527
    return SVN_NO_ERROR;
 
2528
 
 
2529
  /* not found -> must read it from disk.
 
2530
   * Open index file or position read pointer to the begin of the file */
 
2531
  SVN_ERR(auto_open_p2l_index(rev_file, fs, key.revision));
 
2532
  packed_stream_seek(rev_file->p2l_stream, 0);
 
2533
 
 
2534
  /* allocate result data structure */
 
2535
  result = apr_pcalloc(result_pool, sizeof(*result));
 
2536
 
 
2537
  /* Read table sizes, check them for plausibility and allocate page array. */
 
2538
  SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2539
  result->first_revision = (svn_revnum_t)value;
 
2540
  if (result->first_revision != rev_file->start_revision)
 
2541
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2542
                  _("Index rev / pack file revision numbers do not match"));
 
2543
 
 
2544
  SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2545
  result->file_size = value;
 
2546
  if (result->file_size != (apr_uint64_t)rev_file->l2p_offset)
 
2547
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2548
                   _("Index offset and rev / pack file size do not match"));
 
2549
 
 
2550
  SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2551
  result->page_size = value;
 
2552
  if (!result->page_size || (result->page_size & (result->page_size - 1)))
 
2553
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2554
                            _("P2L index page size is not a power of two"));
 
2555
 
 
2556
  SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2557
  result->page_count = (apr_size_t)value;
 
2558
  if (result->page_count != (result->file_size - 1) / result->page_size + 1)
 
2559
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2560
                   _("P2L page count does not match rev / pack file size"));
 
2561
 
 
2562
  result->offsets
 
2563
    = apr_pcalloc(result_pool, (result->page_count + 1) * sizeof(*result->offsets));
 
2564
 
 
2565
  /* read page sizes and derive page description offsets from them */
 
2566
  result->offsets[0] = 0;
 
2567
  for (i = 0; i < result->page_count; ++i)
 
2568
    {
 
2569
      SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2570
      result->offsets[i+1] = result->offsets[i] + (apr_off_t)value;
 
2571
    }
 
2572
 
 
2573
  /* correct the offset values */
 
2574
  offset = packed_stream_offset(rev_file->p2l_stream);
 
2575
  for (i = 0; i <= result->page_count; ++i)
 
2576
    result->offsets[i] += offset;
 
2577
 
 
2578
  /* cache the header data */
 
2579
  SVN_ERR(svn_cache__set(ffd->p2l_header_cache, &key, result, scratch_pool));
 
2580
 
 
2581
  /* return the result */
 
2582
  *header = result;
 
2583
 
 
2584
  return SVN_NO_ERROR;
 
2585
}
 
2586
 
 
2587
/* Read the header data structure of the phys-to-log index for revision
 
2588
 * BATON->REVISION in FS.  Return in *BATON all info relevant to read the
 
2589
 * index page for the rev / pack file offset BATON->OFFSET.  Use REV_FILE
 
2590
 * to access on-disk data.  Use SCRATCH_POOL for temporary allocations.
 
2591
 */
 
2592
static svn_error_t *
 
2593
get_p2l_page_info(p2l_page_info_baton_t *baton,
 
2594
                  svn_fs_x__revision_file_t *rev_file,
 
2595
                  svn_fs_t *fs,
 
2596
                  apr_pool_t *scratch_pool)
 
2597
{
 
2598
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
2599
  p2l_header_t *header;
 
2600
  svn_boolean_t is_cached = FALSE;
 
2601
  void *dummy = NULL;
 
2602
 
 
2603
  /* look for the header data in our cache */
 
2604
  svn_fs_x__pair_cache_key_t key;
 
2605
  key.revision = base_revision(fs, baton->revision);
 
2606
  key.second = svn_fs_x__is_packed_rev(fs, baton->revision);
 
2607
 
 
2608
  SVN_ERR(svn_cache__get_partial(&dummy, &is_cached, ffd->p2l_header_cache,
 
2609
                                 &key, p2l_page_info_func, baton,
 
2610
                                 scratch_pool));
 
2611
  if (is_cached)
 
2612
    return SVN_NO_ERROR;
 
2613
 
 
2614
  SVN_ERR(get_p2l_header(&header, rev_file, fs, baton->revision,
 
2615
                         scratch_pool, scratch_pool));
 
2616
 
 
2617
  /* copy the requested info into *BATON */
 
2618
  p2l_page_info_copy(baton, header, header->offsets);
 
2619
 
 
2620
  return SVN_NO_ERROR;
 
2621
}
 
2622
 
 
2623
/* Read a mapping entry from the phys-to-log index STREAM and append it to
 
2624
 * RESULT.  *ITEM_INDEX contains the phys offset for the entry and will
 
2625
 * be moved forward by the size of entry.
 
2626
 */
 
2627
static svn_error_t *
 
2628
read_entry(svn_fs_x__packed_number_stream_t *stream,
 
2629
           apr_off_t *item_offset,
 
2630
           svn_revnum_t revision,
 
2631
           apr_array_header_t *result)
 
2632
{
 
2633
  apr_uint64_t value;
 
2634
  apr_uint64_t number = 0;
 
2635
  apr_uint32_t sub_item;
 
2636
 
 
2637
  svn_fs_x__p2l_entry_t entry;
 
2638
 
 
2639
  entry.offset = *item_offset;
 
2640
  SVN_ERR(packed_stream_get(&value, stream));
 
2641
  entry.size = (apr_off_t)value;
 
2642
  SVN_ERR(packed_stream_get(&value, stream));
 
2643
  entry.type = (int)value % 16;
 
2644
  entry.item_count = (apr_uint32_t)(value / 16);
 
2645
 
 
2646
  /* Verify item type. */
 
2647
  if (entry.type > SVN_FS_X__ITEM_TYPE_REPS_CONT)
 
2648
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2649
                            _("Invalid item type in P2L index"));
 
2650
 
 
2651
  SVN_ERR(packed_stream_get(&value, stream));
 
2652
  entry.fnv1_checksum = (apr_uint32_t)value;
 
2653
 
 
2654
  /* Truncating the checksum to 32 bits may have hidden random data in the
 
2655
   * unused extra bits of the on-disk representation (7/8 bit representation
 
2656
   * uses 5 bytes on disk for the 32 bit value, leaving 3 bits unused). */
 
2657
  if (value > APR_UINT32_MAX)
 
2658
    return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2659
                            _("Invalid FNV1 checksum in P2L index"));
 
2660
 
 
2661
  /* Some of the index data for empty rev / pack file sections will not be
 
2662
   * used during normal operation.  Thus, we have strict rules for the
 
2663
   * contents of those unused fields. */
 
2664
  if (entry.type == SVN_FS_X__ITEM_TYPE_UNUSED)
 
2665
    if (   entry.fnv1_checksum != 0
 
2666
        || entry.item_count != 0)
 
2667
      return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2668
                 _("Unused regions must be empty and have checksum 0"));
 
2669
 
 
2670
  if (entry.item_count == 0)
 
2671
    {
 
2672
      entry.items = NULL;
 
2673
    }
 
2674
  else
 
2675
    {
 
2676
      entry.items
 
2677
        = apr_pcalloc(result->pool, entry.item_count * sizeof(*entry.items));
 
2678
 
 
2679
      if (   entry.item_count > 1
 
2680
          && entry.type < SVN_FS_X__ITEM_TYPE_CHANGES_CONT)
 
2681
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2682
                      _("Only containers may have more than one sub-item"));
 
2683
 
 
2684
      for (sub_item = 0; sub_item < entry.item_count; ++sub_item)
 
2685
        {
 
2686
          SVN_ERR(packed_stream_get(&value, stream));
 
2687
          revision += (svn_revnum_t)(value % 2 ? -1 - value / 2 : value / 2);
 
2688
          entry.items[sub_item].change_set
 
2689
            = svn_fs_x__change_set_by_rev(revision);
 
2690
        }
 
2691
 
 
2692
      for (sub_item = 0; sub_item < entry.item_count; ++sub_item)
 
2693
        {
 
2694
          SVN_ERR(packed_stream_get(&value, stream));
 
2695
          number += value % 2 ? -1 - value / 2 : value / 2;
 
2696
          entry.items[sub_item].number = number;
 
2697
 
 
2698
          if (   (   entry.type == SVN_FS_X__ITEM_TYPE_CHANGES
 
2699
                  || entry.type == SVN_FS_X__ITEM_TYPE_CHANGES_CONT)
 
2700
              && number != SVN_FS_X__ITEM_INDEX_CHANGES)
 
2701
            return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2702
                            _("Changed path list must have item number 1"));
 
2703
        }
 
2704
    }
 
2705
 
 
2706
  APR_ARRAY_PUSH(result, svn_fs_x__p2l_entry_t) = entry;
 
2707
  *item_offset += entry.size;
 
2708
 
 
2709
  return SVN_NO_ERROR;
 
2710
}
 
2711
 
 
2712
/* Read the phys-to-log mappings for the cluster beginning at rev file
 
2713
 * offset PAGE_START from the index for START_REVISION in FS.  The data
 
2714
 * can be found in the index page beginning at START_OFFSET with the next
 
2715
 * page beginning at NEXT_OFFSET.  PAGE_SIZE is the L2P index page size.
 
2716
 * Return the relevant index entries in *ENTRIES.  Use REV_FILE to access
 
2717
 * on-disk data.  Allocate *ENTRIES in RESULT_POOL.
 
2718
 */
 
2719
static svn_error_t *
 
2720
get_p2l_page(apr_array_header_t **entries,
 
2721
             svn_fs_x__revision_file_t *rev_file,
 
2722
             svn_fs_t *fs,
 
2723
             svn_revnum_t start_revision,
 
2724
             apr_off_t start_offset,
 
2725
             apr_off_t next_offset,
 
2726
             apr_off_t page_start,
 
2727
             apr_uint64_t page_size,
 
2728
             apr_pool_t *result_pool)
 
2729
{
 
2730
  apr_uint64_t value;
 
2731
  apr_array_header_t *result
 
2732
    = apr_array_make(result_pool, 16, sizeof(svn_fs_x__p2l_entry_t));
 
2733
  apr_off_t item_offset;
 
2734
  apr_off_t offset;
 
2735
 
 
2736
  /* open index and navigate to page start */
 
2737
  SVN_ERR(auto_open_p2l_index(rev_file, fs, start_revision));
 
2738
  packed_stream_seek(rev_file->p2l_stream, start_offset);
 
2739
 
 
2740
  /* read rev file offset of the first page entry (all page entries will
 
2741
   * only store their sizes). */
 
2742
  SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2743
  item_offset = (apr_off_t)value;
 
2744
 
 
2745
  /* Special case: empty pages. */
 
2746
  if (start_offset == next_offset)
 
2747
    {
 
2748
      /* Empty page. This only happens if the first entry of the next page
 
2749
       * also covers this page (and possibly more) completely. */
 
2750
      SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset, start_revision,
 
2751
                         result));
 
2752
    }
 
2753
  else
 
2754
    {
 
2755
      /* Read non-empty page. */
 
2756
      do
 
2757
        {
 
2758
          SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
 
2759
                             start_revision, result));
 
2760
          offset = packed_stream_offset(rev_file->p2l_stream);
 
2761
        }
 
2762
      while (offset < next_offset);
 
2763
 
 
2764
      /* We should now be exactly at the next offset, i.e. the numbers in
 
2765
       * the stream cannot overlap into the next page description. */
 
2766
      if (offset != next_offset)
 
2767
        return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
 
2768
             _("P2L page description overlaps with next page description"));
 
2769
 
 
2770
      /* if we haven't covered the cluster end yet, we must read the first
 
2771
       * entry of the next page */
 
2772
      if (item_offset < page_start + page_size)
 
2773
        {
 
2774
          SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
 
2775
          item_offset = (apr_off_t)value;
 
2776
          SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
 
2777
                             start_revision, result));
 
2778
        }
 
2779
    }
 
2780
 
 
2781
  *entries = result;
 
2782
 
 
2783
  return SVN_NO_ERROR;
 
2784
}
 
2785
 
 
2786
/* If it cannot be found in FS's caches, read the p2l index page selected
 
2787
 * by BATON->OFFSET from *STREAM.  If the latter is yet to be constructed,
 
2788
 * do so in STREAM_POOL.  Don't read the page if it precedes MIN_OFFSET.
 
2789
 * Set *END to TRUE if the caller should stop refeching.
 
2790
 *
 
2791
 * *BATON will be updated with the selected page's info and SCRATCH_POOL
 
2792
 * will be used for temporary allocations.  If the data is alread in the
 
2793
 * cache, descrease *LEAKING_BUCKET and increase it otherwise.  With that
 
2794
 * pattern we will still read all pages from the block even if some of
 
2795
 * them survived in the cached.
 
2796
 */
 
2797
static svn_error_t *
 
2798
prefetch_p2l_page(svn_boolean_t *end,
 
2799
                  int *leaking_bucket,
 
2800
                  svn_fs_t *fs,
 
2801
                  svn_fs_x__revision_file_t *rev_file,
 
2802
                  p2l_page_info_baton_t *baton,
 
2803
                  apr_off_t min_offset,
 
2804
                  apr_pool_t *scratch_pool)
 
2805
{
 
2806
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
2807
  svn_boolean_t already_cached;
 
2808
  apr_array_header_t *page;
 
2809
  svn_fs_x__page_cache_key_t key = { 0 };
 
2810
 
 
2811
  /* fetch the page info */
 
2812
  *end = FALSE;
 
2813
  baton->revision = baton->first_revision;
 
2814
  SVN_ERR(get_p2l_page_info(baton, rev_file, fs, scratch_pool));
 
2815
  if (baton->start_offset < min_offset)
 
2816
    {
 
2817
      /* page outside limits -> stop prefetching */
 
2818
      *end = TRUE;
 
2819
      return SVN_NO_ERROR;
 
2820
    }
 
2821
 
 
2822
  /* do we have that page in our caches already? */
 
2823
  assert(baton->first_revision <= APR_UINT32_MAX);
 
2824
  key.revision = (apr_uint32_t)baton->first_revision;
 
2825
  key.is_packed = svn_fs_x__is_packed_rev(fs, baton->first_revision);
 
2826
  key.page = baton->page_no;
 
2827
  SVN_ERR(svn_cache__has_key(&already_cached, ffd->p2l_page_cache,
 
2828
                             &key, scratch_pool));
 
2829
 
 
2830
  /* yes, already cached */
 
2831
  if (already_cached)
 
2832
    {
 
2833
      /* stop prefetching if most pages are already cached. */
 
2834
      if (!--*leaking_bucket)
 
2835
        *end = TRUE;
 
2836
 
 
2837
      return SVN_NO_ERROR;
 
2838
    }
 
2839
 
 
2840
  ++*leaking_bucket;
 
2841
 
 
2842
  /* read from disk */
 
2843
  SVN_ERR(get_p2l_page(&page, rev_file, fs,
 
2844
                       baton->first_revision,
 
2845
                       baton->start_offset,
 
2846
                       baton->next_offset,
 
2847
                       baton->page_start,
 
2848
                       baton->page_size,
 
2849
                       scratch_pool));
 
2850
 
 
2851
  /* and put it into our cache */
 
2852
  SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page, scratch_pool));
 
2853
 
 
2854
  return SVN_NO_ERROR;
 
2855
}
 
2856
 
 
2857
/* Lookup & construct the baton and key information that we will need for
 
2858
 * a P2L page cache lookup.  We want the page covering OFFSET in the rev /
 
2859
 * pack file containing REVSION in FS.  Return the results in *PAGE_INFO_P
 
2860
 * and *KEY_P.  Read data through REV_FILE.  Use SCRATCH_POOL for temporary
 
2861
 * allocations.
 
2862
 */
 
2863
static svn_error_t *
 
2864
get_p2l_keys(p2l_page_info_baton_t *page_info_p,
 
2865
             svn_fs_x__page_cache_key_t *key_p,
 
2866
             svn_fs_x__revision_file_t *rev_file,
 
2867
             svn_fs_t *fs,
 
2868
             svn_revnum_t revision,
 
2869
             apr_off_t offset,
 
2870
             apr_pool_t *scratch_pool)
 
2871
{
 
2872
  p2l_page_info_baton_t page_info;
 
2873
 
 
2874
  /* request info for the index pages that describes the pack / rev file
 
2875
   * contents at pack / rev file position OFFSET. */
 
2876
  page_info.offset = offset;
 
2877
  page_info.revision = revision;
 
2878
  SVN_ERR(get_p2l_page_info(&page_info, rev_file, fs, scratch_pool));
 
2879
 
 
2880
  /* if the offset refers to a non-existent page, bail out */
 
2881
  if (page_info.page_count <= page_info.page_no)
 
2882
    return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
2883
                             _("Offset %s too large in revision %ld"),
 
2884
                             apr_off_t_toa(scratch_pool, offset), revision);
 
2885
 
 
2886
  /* return results */
 
2887
  if (page_info_p)
 
2888
    *page_info_p = page_info;
 
2889
 
 
2890
  /* construct cache key */
 
2891
  if (key_p)
 
2892
    {
 
2893
      svn_fs_x__page_cache_key_t key = { 0 };
 
2894
      assert(page_info.first_revision <= APR_UINT32_MAX);
 
2895
      key.revision = (apr_uint32_t)page_info.first_revision;
 
2896
      key.is_packed = svn_fs_x__is_packed_rev(fs, revision);
 
2897
      key.page = page_info.page_no;
 
2898
 
 
2899
      *key_p = key;
 
2900
    }
 
2901
 
 
2902
  return SVN_NO_ERROR;
 
2903
}
 
2904
 
 
2905
/* qsort-compatible compare function that compares the OFFSET of the
 
2906
 * svn_fs_x__p2l_entry_t in *LHS with the apr_off_t in *RHS. */
 
2907
static int
 
2908
compare_start_p2l_entry(const void *lhs,
 
2909
                        const void *rhs)
 
2910
{
 
2911
  const svn_fs_x__p2l_entry_t *entry = lhs;
 
2912
  apr_off_t start = *(const apr_off_t*)rhs;
 
2913
  apr_off_t diff = entry->offset - start;
 
2914
 
 
2915
  /* restrict result to int */
 
2916
  return diff < 0 ? -1 : (diff == 0 ? 0 : 1);
 
2917
}
 
2918
 
 
2919
/* From the PAGE_ENTRIES array of svn_fs_x__p2l_entry_t, ordered
 
2920
 * by their OFFSET member, copy all elements overlapping the range
 
2921
 * [BLOCK_START, BLOCK_END) to ENTRIES.  If RESOLVE_PTR is set, the ITEMS
 
2922
 * sub-array in each entry needs to be de-serialized. */
 
2923
static void
 
2924
append_p2l_entries(apr_array_header_t *entries,
 
2925
                   apr_array_header_t *page_entries,
 
2926
                   apr_off_t block_start,
 
2927
                   apr_off_t block_end,
 
2928
                   svn_boolean_t resolve_ptr)
 
2929
{
 
2930
  const svn_fs_x__p2l_entry_t *entry;
 
2931
  int idx = svn_sort__bsearch_lower_bound(page_entries, &block_start,
 
2932
                                          compare_start_p2l_entry);
 
2933
 
 
2934
  /* start at the first entry that overlaps with BLOCK_START */
 
2935
  if (idx > 0)
 
2936
    {
 
2937
      entry = &APR_ARRAY_IDX(page_entries, idx - 1, svn_fs_x__p2l_entry_t);
 
2938
      if (entry->offset + entry->size > block_start)
 
2939
        --idx;
 
2940
    }
 
2941
 
 
2942
  /* copy all entries covering the requested range */
 
2943
  for ( ; idx < page_entries->nelts; ++idx)
 
2944
    {
 
2945
      svn_fs_x__p2l_entry_t *copy;
 
2946
      entry = &APR_ARRAY_IDX(page_entries, idx, svn_fs_x__p2l_entry_t);
 
2947
      if (entry->offset >= block_end)
 
2948
        break;
 
2949
 
 
2950
      /* Copy the entry record. */
 
2951
      copy = apr_array_push(entries);
 
2952
      *copy = *entry;
 
2953
 
 
2954
      /* Copy the items of that entries. */
 
2955
      if (entry->item_count)
 
2956
        {
 
2957
          const svn_fs_x__id_t *items
 
2958
            = resolve_ptr
 
2959
            ? svn_temp_deserializer__ptr(page_entries->elts,
 
2960
                                         (const void * const *)&entry->items)
 
2961
            : entry->items;
 
2962
 
 
2963
          copy->items = apr_pmemdup(entries->pool, items,
 
2964
                                    entry->item_count * sizeof(*items));
 
2965
        }
 
2966
    }
 
2967
}
 
2968
 
 
2969
/* Auxilliary struct passed to p2l_entries_func selecting the relevant
 
2970
 * data range. */
 
2971
typedef struct p2l_entries_baton_t
 
2972
{
 
2973
  apr_off_t start;
 
2974
  apr_off_t end;
 
2975
} p2l_entries_baton_t;
 
2976
 
 
2977
/* Implement svn_cache__partial_getter_func_t: extract p2l entries from
 
2978
 * the page in DATA which overlap the p2l_entries_baton_t in BATON.
 
2979
 * The target array is already provided in *OUT.
 
2980
 */
 
2981
static svn_error_t *
 
2982
p2l_entries_func(void **out,
 
2983
                 const void *data,
 
2984
                 apr_size_t data_len,
 
2985
                 void *baton,
 
2986
                 apr_pool_t *result_pool)
 
2987
{
 
2988
  apr_array_header_t *entries = *(apr_array_header_t **)out;
 
2989
  const apr_array_header_t *raw_page = data;
 
2990
  p2l_entries_baton_t *block = baton;
 
2991
 
 
2992
  /* Make PAGE a readable APR array. */
 
2993
  apr_array_header_t page = *raw_page;
 
2994
  page.elts = (void *)svn_temp_deserializer__ptr(raw_page,
 
2995
                                    (const void * const *)&raw_page->elts);
 
2996
 
 
2997
  /* append relevant information to result */
 
2998
  append_p2l_entries(entries, &page, block->start, block->end, TRUE);
 
2999
 
 
3000
  return SVN_NO_ERROR;
 
3001
}
 
3002
 
 
3003
 
 
3004
/* Body of svn_fs_x__p2l_index_lookup.  However, do a single index page
 
3005
 * lookup and append the result to the ENTRIES array provided by the caller.
 
3006
 * Use successive calls to cover larger ranges.
 
3007
 */
 
3008
static svn_error_t *
 
3009
p2l_index_lookup(apr_array_header_t *entries,
 
3010
                 svn_fs_x__revision_file_t *rev_file,
 
3011
                 svn_fs_t *fs,
 
3012
                 svn_revnum_t revision,
 
3013
                 apr_off_t block_start,
 
3014
                 apr_off_t block_end,
 
3015
                 apr_pool_t *scratch_pool)
 
3016
{
 
3017
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
3018
  svn_fs_x__page_cache_key_t key;
 
3019
  svn_boolean_t is_cached = FALSE;
 
3020
  p2l_page_info_baton_t page_info;
 
3021
  apr_array_header_t *local_result = entries;
 
3022
 
 
3023
  /* baton selecting the relevant entries from the one page we access */
 
3024
  p2l_entries_baton_t block;
 
3025
  block.start = block_start;
 
3026
  block.end = block_end;
 
3027
 
 
3028
  /* if we requested an empty range, the result would be empty */
 
3029
  SVN_ERR_ASSERT(block_start < block_end);
 
3030
 
 
3031
  /* look for the fist page of the range in our cache */
 
3032
  SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, block_start,
 
3033
                       scratch_pool));
 
3034
  SVN_ERR(svn_cache__get_partial((void**)&local_result, &is_cached,
 
3035
                                 ffd->p2l_page_cache, &key, p2l_entries_func,
 
3036
                                 &block, scratch_pool));
 
3037
 
 
3038
  if (!is_cached)
 
3039
    {
 
3040
      svn_boolean_t end;
 
3041
      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
3042
      apr_off_t original_page_start = page_info.page_start;
 
3043
      int leaking_bucket = 4;
 
3044
      p2l_page_info_baton_t prefetch_info = page_info;
 
3045
      apr_array_header_t *page_entries;
 
3046
 
 
3047
      apr_off_t max_offset
 
3048
        = APR_ALIGN(page_info.next_offset, ffd->block_size);
 
3049
      apr_off_t min_offset
 
3050
        = APR_ALIGN(page_info.start_offset, ffd->block_size) - ffd->block_size;
 
3051
 
 
3052
      /* Since we read index data in larger chunks, we probably got more
 
3053
       * page data than we requested.  Parse & cache that until either we
 
3054
       * encounter pages already cached or reach the end of the buffer.
 
3055
       */
 
3056
 
 
3057
      /* pre-fetch preceding pages */
 
3058
      end = FALSE;
 
3059
      prefetch_info.offset = original_page_start;
 
3060
      while (prefetch_info.offset >= prefetch_info.page_size && !end)
 
3061
        {
 
3062
          prefetch_info.offset -= prefetch_info.page_size;
 
3063
          SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
 
3064
                                    &prefetch_info, min_offset, iterpool));
 
3065
          svn_pool_clear(iterpool);
 
3066
        }
 
3067
 
 
3068
      /* fetch page from disk and put it into the cache */
 
3069
      SVN_ERR(get_p2l_page(&page_entries, rev_file, fs,
 
3070
                           page_info.first_revision,
 
3071
                           page_info.start_offset,
 
3072
                           page_info.next_offset,
 
3073
                           page_info.page_start,
 
3074
                           page_info.page_size, iterpool));
 
3075
 
 
3076
      /* The last cache entry must not end beyond the range covered by
 
3077
       * this index.  The same applies for any subset of entries. */
 
3078
      if (page_entries->nelts)
 
3079
        {
 
3080
          const svn_fs_x__p2l_entry_t *entry
 
3081
            = &APR_ARRAY_IDX(page_entries, page_entries->nelts - 1,
 
3082
                             svn_fs_x__p2l_entry_t);
 
3083
          if (  entry->offset + entry->size
 
3084
              > page_info.page_size * page_info.page_count)
 
3085
            return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
 
3086
                                     _("Last P2L index entry extends beyond "
 
3087
                                       "the last page in revision %ld."),
 
3088
                                     revision);
 
3089
        }
 
3090
 
 
3091
      SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page_entries,
 
3092
                             iterpool));
 
3093
 
 
3094
      /* append relevant information to result */
 
3095
      append_p2l_entries(entries, page_entries, block_start, block_end, FALSE);
 
3096
 
 
3097
      /* pre-fetch following pages */
 
3098
      end = FALSE;
 
3099
      leaking_bucket = 4;
 
3100
      prefetch_info = page_info;
 
3101
      prefetch_info.offset = original_page_start;
 
3102
      while (   prefetch_info.next_offset < max_offset
 
3103
             && prefetch_info.page_no + 1 < prefetch_info.page_count
 
3104
             && !end)
 
3105
        {
 
3106
          prefetch_info.offset += prefetch_info.page_size;
 
3107
          SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
 
3108
                                    &prefetch_info, min_offset, iterpool));
 
3109
          svn_pool_clear(iterpool);
 
3110
        }
 
3111
 
 
3112
      svn_pool_destroy(iterpool);
 
3113
    }
 
3114
 
 
3115
  /* We access a valid page (otherwise, we had seen an error in the
 
3116
   * get_p2l_keys request).  Hence, at least one entry must be found. */
 
3117
  SVN_ERR_ASSERT(entries->nelts > 0);
 
3118
 
 
3119
  /* Add an "unused" entry if it extends beyond the end of the data file.
 
3120
   * Since the index page size might be smaller than the current data
 
3121
   * read block size, the trailing "unused" entry in this index may not
 
3122
   * fully cover the end of the last block. */
 
3123
  if (page_info.page_no + 1 >= page_info.page_count)
 
3124
    {
 
3125
      svn_fs_x__p2l_entry_t *entry
 
3126
        = &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_x__p2l_entry_t);
 
3127
 
 
3128
      apr_off_t entry_end = entry->offset + entry->size;
 
3129
      if (entry_end < block_end)
 
3130
        {
 
3131
          if (entry->type == SVN_FS_X__ITEM_TYPE_UNUSED)
 
3132
            {
 
3133
              /* extend the terminal filler */
 
3134
              entry->size = block_end - entry->offset;
 
3135
            }
 
3136
          else
 
3137
            {
 
3138
              /* No terminal filler. Add one. */
 
3139
              entry = apr_array_push(entries);
 
3140
              entry->offset = entry_end;
 
3141
              entry->size = block_end - entry_end;
 
3142
              entry->type = SVN_FS_X__ITEM_TYPE_UNUSED;
 
3143
              entry->fnv1_checksum = 0;
 
3144
              entry->item_count = 0;
 
3145
              entry->items = NULL;
 
3146
            }
 
3147
        }
 
3148
    }
 
3149
 
 
3150
  return SVN_NO_ERROR;
 
3151
}
 
3152
 
 
3153
svn_error_t *
 
3154
svn_fs_x__p2l_index_lookup(apr_array_header_t **entries,
 
3155
                           svn_fs_t *fs,
 
3156
                           svn_fs_x__revision_file_t *rev_file,
 
3157
                           svn_revnum_t revision,
 
3158
                           apr_off_t block_start,
 
3159
                           apr_off_t block_size,
 
3160
                           apr_pool_t *result_pool,
 
3161
                           apr_pool_t *scratch_pool)
 
3162
{
 
3163
  apr_off_t block_end = block_start + block_size;
 
3164
 
 
3165
  /* the receiving container */
 
3166
  int last_count = 0;
 
3167
  apr_array_header_t *result = apr_array_make(result_pool, 16,
 
3168
                                              sizeof(svn_fs_x__p2l_entry_t));
 
3169
 
 
3170
  /* Fetch entries page-by-page.  Since the p2l index is supposed to cover
 
3171
   * every single byte in the rev / pack file - even unused sections -
 
3172
   * every iteration must result in some progress. */
 
3173
  while (block_start < block_end)
 
3174
    {
 
3175
      svn_fs_x__p2l_entry_t *entry;
 
3176
      SVN_ERR(p2l_index_lookup(result, rev_file, fs, revision, block_start,
 
3177
                               block_end, scratch_pool));
 
3178
      SVN_ERR_ASSERT(result->nelts > 0);
 
3179
 
 
3180
      /* continue directly behind last item */
 
3181
      entry = &APR_ARRAY_IDX(result, result->nelts-1, svn_fs_x__p2l_entry_t);
 
3182
      block_start = entry->offset + entry->size;
 
3183
 
 
3184
      /* Some paranoia check.  Successive iterations should never return
 
3185
       * duplicates but if it did, we might get into trouble later on. */
 
3186
      if (last_count > 0 && last_count < result->nelts)
 
3187
        {
 
3188
           entry = &APR_ARRAY_IDX(result, last_count - 1,
 
3189
                                  svn_fs_x__p2l_entry_t);
 
3190
           SVN_ERR_ASSERT(APR_ARRAY_IDX(result, last_count,
 
3191
                                        svn_fs_x__p2l_entry_t).offset
 
3192
                          >= entry->offset + entry->size);
 
3193
        }
 
3194
 
 
3195
      last_count = result->nelts;
 
3196
    }
 
3197
 
 
3198
  *entries = result;
 
3199
  return SVN_NO_ERROR;
 
3200
}
 
3201
 
 
3202
/* compare_fn_t comparing a svn_fs_x__p2l_entry_t at LHS with an offset
 
3203
 * RHS.
 
3204
 */
 
3205
static int
 
3206
compare_p2l_entry_offsets(const void *lhs, const void *rhs)
 
3207
{
 
3208
  const svn_fs_x__p2l_entry_t *entry = (const svn_fs_x__p2l_entry_t *)lhs;
 
3209
  apr_off_t offset = *(const apr_off_t *)rhs;
 
3210
 
 
3211
  return entry->offset < offset ? -1 : (entry->offset == offset ? 0 : 1);
 
3212
}
 
3213
 
 
3214
/* Cached data extraction utility.  DATA is a P2L index page, e.g. an APR
 
3215
 * array of svn_fs_fs__p2l_entry_t elements.  Return the entry for the item,
 
3216
 * allocated in RESULT_POOL, starting at OFFSET or NULL if that's not an
 
3217
 * the start offset of any item. Use SCRATCH_POOL for temporary allocations.
 
3218
 */
 
3219
static svn_fs_x__p2l_entry_t *
 
3220
get_p2l_entry_from_cached_page(const void *data,
 
3221
                               apr_off_t offset,
 
3222
                               apr_pool_t *result_pool,
 
3223
                               apr_pool_t *scratch_pool)
 
3224
{
 
3225
  /* resolve all pointer values of in-cache data */
 
3226
  const apr_array_header_t *page = data;
 
3227
  apr_array_header_t *entries = apr_pmemdup(scratch_pool, page,
 
3228
                                            sizeof(*page));
 
3229
  svn_fs_x__p2l_entry_t *entry;
 
3230
 
 
3231
  entries->elts = (char *)svn_temp_deserializer__ptr(page,
 
3232
                                     (const void *const *)&page->elts);
 
3233
 
 
3234
  /* search of the offset we want */
 
3235
  entry = svn_sort__array_lookup(entries, &offset, NULL,
 
3236
      (int (*)(const void *, const void *))compare_p2l_entry_offsets);
 
3237
 
 
3238
  /* return it, if it is a perfect match */
 
3239
  if (entry)
 
3240
    {
 
3241
      svn_fs_x__p2l_entry_t *result
 
3242
        = apr_pmemdup(result_pool, entry, sizeof(*result));
 
3243
      result->items
 
3244
        = (svn_fs_x__id_t *)svn_temp_deserializer__ptr(entries->elts,
 
3245
                                     (const void *const *)&entry->items);
 
3246
      return result;
 
3247
    }
 
3248
 
 
3249
  return NULL;
 
3250
}
 
3251
 
 
3252
/* Implements svn_cache__partial_getter_func_t for P2L index pages, copying
 
3253
 * the entry for the apr_off_t at BATON into *OUT.  *OUT will be NULL if
 
3254
 * there is no matching entry in the index page at DATA.
 
3255
 */
 
3256
static svn_error_t *
 
3257
p2l_entry_lookup_func(void **out,
 
3258
                      const void *data,
 
3259
                      apr_size_t data_len,
 
3260
                      void *baton,
 
3261
                      apr_pool_t *result_pool)
 
3262
{
 
3263
  svn_fs_x__p2l_entry_t *entry
 
3264
    = get_p2l_entry_from_cached_page(data, *(apr_off_t *)baton, result_pool,
 
3265
                                     result_pool);
 
3266
 
 
3267
  *out = entry && entry->offset == *(apr_off_t *)baton
 
3268
       ? svn_fs_x__p2l_entry_dup(entry, result_pool)
 
3269
       : NULL;
 
3270
 
 
3271
  return SVN_NO_ERROR;
 
3272
}
 
3273
 
 
3274
static svn_error_t *
 
3275
p2l_entry_lookup(svn_fs_x__p2l_entry_t **entry_p,
 
3276
                 svn_fs_x__revision_file_t *rev_file,
 
3277
                 svn_fs_t *fs,
 
3278
                 svn_revnum_t revision,
 
3279
                 apr_off_t offset,
 
3280
                 apr_pool_t *result_pool,
 
3281
                 apr_pool_t *scratch_pool)
 
3282
{
 
3283
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
3284
  svn_fs_x__page_cache_key_t key = { 0 };
 
3285
  svn_boolean_t is_cached = FALSE;
 
3286
  p2l_page_info_baton_t page_info;
 
3287
 
 
3288
  /* look for this info in our cache */
 
3289
  SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, offset,
 
3290
                       scratch_pool));
 
3291
  SVN_ERR(svn_cache__get_partial((void**)entry_p, &is_cached,
 
3292
                                 ffd->p2l_page_cache, &key,
 
3293
                                 p2l_entry_lookup_func, &offset,
 
3294
                                 result_pool));
 
3295
  if (!is_cached)
 
3296
    {
 
3297
      /* do a standard index lookup.  This is will automatically prefetch
 
3298
       * data to speed up future lookups. */
 
3299
      apr_array_header_t *entries = apr_array_make(result_pool, 1,
 
3300
                                                   sizeof(**entry_p));
 
3301
      SVN_ERR(p2l_index_lookup(entries, rev_file, fs, revision, offset,
 
3302
                               offset + 1, scratch_pool));
 
3303
 
 
3304
      /* Find the entry that we want. */
 
3305
      *entry_p = svn_sort__array_lookup(entries, &offset, NULL,
 
3306
          (int (*)(const void *, const void *))compare_p2l_entry_offsets);
 
3307
    }
 
3308
 
 
3309
  return SVN_NO_ERROR;
 
3310
}
 
3311
 
 
3312
svn_error_t *
 
3313
svn_fs_x__p2l_entry_lookup(svn_fs_x__p2l_entry_t **entry_p,
 
3314
                           svn_fs_t *fs,
 
3315
                           svn_fs_x__revision_file_t *rev_file,
 
3316
                           svn_revnum_t revision,
 
3317
                           apr_off_t offset,
 
3318
                           apr_pool_t *result_pool,
 
3319
                           apr_pool_t *scratch_pool)
 
3320
{
 
3321
  /* look for this info in our cache */
 
3322
  SVN_ERR(p2l_entry_lookup(entry_p, rev_file, fs, revision, offset,
 
3323
                           result_pool, scratch_pool));
 
3324
 
 
3325
  return SVN_NO_ERROR;
 
3326
}
 
3327
 
 
3328
/* Baton structure for p2l_item_lookup_func.  It describes which sub_item
 
3329
 * info shall be returned.
 
3330
 */
 
3331
typedef struct p2l_item_lookup_baton_t
 
3332
{
 
3333
  /* file offset to find the P2L index entry for */
 
3334
  apr_off_t offset;
 
3335
 
 
3336
  /* return the sub-item at this position within that entry */
 
3337
  apr_uint32_t sub_item;
 
3338
} p2l_item_lookup_baton_t;
 
3339
 
 
3340
/* Implements svn_cache__partial_getter_func_t for P2L index pages, copying
 
3341
 * the svn_fs_x__id_t for the item described 2l_item_lookup_baton_t
 
3342
 * *BATON.  *OUT will be NULL if there is no matching index entry or the
 
3343
 * sub-item is out of range.
 
3344
 */
 
3345
static svn_error_t *
 
3346
p2l_item_lookup_func(void **out,
 
3347
                     const void *data,
 
3348
                     apr_size_t data_len,
 
3349
                     void *baton,
 
3350
                     apr_pool_t *result_pool)
 
3351
{
 
3352
  p2l_item_lookup_baton_t *lookup_baton = baton;
 
3353
  svn_fs_x__p2l_entry_t *entry
 
3354
    = get_p2l_entry_from_cached_page(data, lookup_baton->offset, result_pool,
 
3355
                                     result_pool);
 
3356
 
 
3357
  *out =    entry
 
3358
         && entry->offset == lookup_baton->offset
 
3359
         && entry->item_count > lookup_baton->sub_item
 
3360
       ? apr_pmemdup(result_pool,
 
3361
                     entry->items + lookup_baton->sub_item,
 
3362
                     sizeof(*entry->items))
 
3363
       : NULL;
 
3364
 
 
3365
  return SVN_NO_ERROR;
 
3366
}
 
3367
 
 
3368
svn_error_t *
 
3369
svn_fs_x__p2l_item_lookup(svn_fs_x__id_t **item,
 
3370
                          svn_fs_t *fs,
 
3371
                          svn_fs_x__revision_file_t *rev_file,
 
3372
                          svn_revnum_t revision,
 
3373
                          apr_off_t offset,
 
3374
                          apr_uint32_t sub_item,
 
3375
                          apr_pool_t *result_pool,
 
3376
                          apr_pool_t *scratch_pool)
 
3377
{
 
3378
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
3379
  svn_fs_x__page_cache_key_t key = { 0 };
 
3380
  svn_boolean_t is_cached = FALSE;
 
3381
  p2l_page_info_baton_t page_info;
 
3382
  p2l_item_lookup_baton_t baton;
 
3383
 
 
3384
  *item = NULL;
 
3385
 
 
3386
  /* look for this info in our cache */
 
3387
  SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, offset,
 
3388
                       scratch_pool));
 
3389
  baton.offset = offset;
 
3390
  baton.sub_item = sub_item;
 
3391
  SVN_ERR(svn_cache__get_partial((void**)item, &is_cached,
 
3392
                                 ffd->p2l_page_cache, &key,
 
3393
                                 p2l_item_lookup_func, &baton, result_pool));
 
3394
  if (!is_cached)
 
3395
    {
 
3396
      /* do a standard index lookup.  This is will automatically prefetch
 
3397
       * data to speed up future lookups. */
 
3398
      svn_fs_x__p2l_entry_t *entry;
 
3399
      SVN_ERR(p2l_entry_lookup(&entry, rev_file, fs, revision, offset,
 
3400
                               result_pool, scratch_pool));
 
3401
 
 
3402
      /* return result */
 
3403
      if (entry && entry->item_count > sub_item)
 
3404
        *item = apr_pmemdup(result_pool, entry->items + sub_item,
 
3405
                            sizeof(**item));
 
3406
    }
 
3407
 
 
3408
  return SVN_NO_ERROR;
 
3409
}
 
3410
 
 
3411
/* Implements svn_cache__partial_getter_func_t for P2L headers, setting *OUT
 
3412
 * to the largest the first offset not covered by this P2L index.
 
3413
 */
 
3414
static svn_error_t *
 
3415
p2l_get_max_offset_func(void **out,
 
3416
                        const void *data,
 
3417
                        apr_size_t data_len,
 
3418
                        void *baton,
 
3419
                        apr_pool_t *result_pool)
 
3420
{
 
3421
  const p2l_header_t *header = data;
 
3422
  apr_off_t max_offset = header->file_size;
 
3423
  *out = apr_pmemdup(result_pool, &max_offset, sizeof(max_offset));
 
3424
 
 
3425
  return SVN_NO_ERROR;
 
3426
}
 
3427
 
 
3428
svn_error_t *
 
3429
svn_fs_x__p2l_get_max_offset(apr_off_t *offset,
 
3430
                             svn_fs_t *fs,
 
3431
                             svn_fs_x__revision_file_t *rev_file,
 
3432
                             svn_revnum_t revision,
 
3433
                             apr_pool_t *scratch_pool)
 
3434
{
 
3435
  svn_fs_x__data_t *ffd = fs->fsap_data;
 
3436
  p2l_header_t *header;
 
3437
  svn_boolean_t is_cached = FALSE;
 
3438
  apr_off_t *offset_p;
 
3439
 
 
3440
  /* look for the header data in our cache */
 
3441
  svn_fs_x__pair_cache_key_t key;
 
3442
  key.revision = base_revision(fs, revision);
 
3443
  key.second = svn_fs_x__is_packed_rev(fs, revision);
 
3444
 
 
3445
  SVN_ERR(svn_cache__get_partial((void **)&offset_p, &is_cached,
 
3446
                                 ffd->p2l_header_cache, &key,
 
3447
                                 p2l_get_max_offset_func, NULL,
 
3448
                                 scratch_pool));
 
3449
  if (is_cached)
 
3450
    {
 
3451
      *offset = *offset_p;
 
3452
      return SVN_NO_ERROR;
 
3453
    }
 
3454
 
 
3455
  SVN_ERR(get_p2l_header(&header, rev_file, fs, revision, scratch_pool,
 
3456
                         scratch_pool));
 
3457
  *offset = header->file_size;
 
3458
 
 
3459
  return SVN_NO_ERROR;
 
3460
}
 
3461
 
 
3462
svn_error_t *
 
3463
svn_fs_x__item_offset(apr_off_t *absolute_position,
 
3464
                      apr_uint32_t *sub_item,
 
3465
                      svn_fs_t *fs,
 
3466
                      svn_fs_x__revision_file_t *rev_file,
 
3467
                      const svn_fs_x__id_t *item_id,
 
3468
                      apr_pool_t *scratch_pool)
 
3469
{
 
3470
  if (svn_fs_x__is_txn(item_id->change_set))
 
3471
    SVN_ERR(l2p_proto_index_lookup(absolute_position, sub_item, fs,
 
3472
                                   svn_fs_x__get_txn_id(item_id->change_set),
 
3473
                                   item_id->number, scratch_pool));
 
3474
  else
 
3475
    SVN_ERR(l2p_index_lookup(absolute_position, sub_item, fs, rev_file,
 
3476
                             svn_fs_x__get_revnum(item_id->change_set),
 
3477
                             item_id->number, scratch_pool));
 
3478
 
 
3479
  return SVN_NO_ERROR;
 
3480
}
 
3481
 
 
3482
/* Calculate the FNV1 checksum over the offset range in REV_FILE, covered by
 
3483
 * ENTRY.  Store the result in ENTRY->FNV1_CHECKSUM.  Use SCRATCH_POOL for
 
3484
 * temporary allocations. */
 
3485
static svn_error_t *
 
3486
calc_fnv1(svn_fs_x__p2l_entry_t *entry,
 
3487
          svn_fs_x__revision_file_t *rev_file,
 
3488
          apr_pool_t *scratch_pool)
 
3489
{
 
3490
  unsigned char buffer[4096];
 
3491
  svn_checksum_t *checksum;
 
3492
  svn_checksum_ctx_t *context
 
3493
    = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, scratch_pool);
 
3494
  apr_off_t size = entry->size;
 
3495
 
 
3496
  /* Special rules apply to unused sections / items.  The data must be a
 
3497
   * sequence of NUL bytes (not checked here) and the checksum is fixed to 0.
 
3498
   */
 
3499
  if (entry->type == SVN_FS_X__ITEM_TYPE_UNUSED)
 
3500
    {
 
3501
      entry->fnv1_checksum = 0;
 
3502
      return SVN_NO_ERROR;
 
3503
    }
 
3504
 
 
3505
  /* Read the block and feed it to the checksum calculator. */
 
3506
  SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &entry->offset,
 
3507
                           scratch_pool));
 
3508
  while (size > 0)
 
3509
    {
 
3510
      apr_size_t to_read = size > sizeof(buffer)
 
3511
                         ? sizeof(buffer)
 
3512
                         : (apr_size_t)size;
 
3513
      SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, to_read, NULL,
 
3514
                                     NULL, scratch_pool));
 
3515
      SVN_ERR(svn_checksum_update(context, buffer, to_read));
 
3516
      size -= to_read;
 
3517
    }
 
3518
 
 
3519
  /* Store final checksum in ENTRY. */
 
3520
  SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
 
3521
  entry->fnv1_checksum = ntohl(*(const apr_uint32_t *)checksum->digest);
 
3522
 
 
3523
  return SVN_NO_ERROR;
 
3524
}
 
3525
 
 
3526
/*
 
3527
 * Index (re-)creation utilities.
 
3528
 */
 
3529
 
 
3530
svn_error_t *
 
3531
svn_fs_x__p2l_index_from_p2l_entries(const char **protoname,
 
3532
                                     svn_fs_t *fs,
 
3533
                                     svn_fs_x__revision_file_t *rev_file,
 
3534
                                     apr_array_header_t *entries,
 
3535
                                     apr_pool_t *result_pool,
 
3536
                                     apr_pool_t *scratch_pool)
 
3537
{
 
3538
  apr_file_t *proto_index;
 
3539
 
 
3540
  /* Use a subpool for immediate temp file cleanup at the end of this
 
3541
   * function. */
 
3542
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
3543
  int i;
 
3544
 
 
3545
  /* Create a proto-index file. */
 
3546
  SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
 
3547
                                   svn_io_file_del_on_pool_cleanup,
 
3548
                                   result_pool, scratch_pool));
 
3549
  SVN_ERR(svn_fs_x__p2l_proto_index_open(&proto_index, *protoname,
 
3550
                                         scratch_pool));
 
3551
 
 
3552
  /* Write ENTRIES to proto-index file and calculate checksums as we go. */
 
3553
  for (i = 0; i < entries->nelts; ++i)
 
3554
    {
 
3555
      svn_fs_x__p2l_entry_t *entry
 
3556
        = APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t *);
 
3557
      svn_pool_clear(iterpool);
 
3558
 
 
3559
      SVN_ERR(calc_fnv1(entry, rev_file, iterpool));
 
3560
      SVN_ERR(svn_fs_x__p2l_proto_index_add_entry(proto_index, entry,
 
3561
                                                  iterpool));
 
3562
    }
 
3563
 
 
3564
  /* Convert proto-index into final index and move it into position.
 
3565
   * Note that REV_FILE contains the start revision of the shard file if it
 
3566
   * has been packed while REVISION may be somewhere in the middle.  For
 
3567
   * non-packed shards, they will have identical values. */
 
3568
  SVN_ERR(svn_io_file_close(proto_index, iterpool));
 
3569
 
 
3570
  /* Temp file cleanup. */
 
3571
  svn_pool_destroy(iterpool);
 
3572
 
 
3573
  return SVN_NO_ERROR;
 
3574
}
 
3575
 
 
3576
/* Decorator for svn_fs_x__p2l_entry_t that associates it with a sorted
 
3577
 * variant of its ITEMS array.
 
3578
 */
 
3579
typedef struct sub_item_ordered_t
 
3580
{
 
3581
  /* ENTRY that got wrapped */
 
3582
  svn_fs_x__p2l_entry_t *entry;
 
3583
 
 
3584
  /* Array of pointers into ENTRY->ITEMS, sorted by their revision member
 
3585
   * _descending_ order.  May be NULL if ENTRY->ITEM_COUNT < 2. */
 
3586
  svn_fs_x__id_t **order;
 
3587
} sub_item_ordered_t;
 
3588
 
 
3589
/* implements compare_fn_t. Place LHS before RHS, if the latter is younger.
 
3590
 * Used to sort sub_item_ordered_t::order
 
3591
 */
 
3592
static int
 
3593
compare_sub_items(const svn_fs_x__id_t * const * lhs,
 
3594
                  const svn_fs_x__id_t * const * rhs)
 
3595
{
 
3596
  return (*lhs)->change_set < (*rhs)->change_set
 
3597
       ? 1
 
3598
       : ((*lhs)->change_set > (*rhs)->change_set ? -1 : 0);
 
3599
}
 
3600
 
 
3601
/* implements compare_fn_t. Place LHS before RHS, if the latter belongs to
 
3602
 * a newer revision.
 
3603
 */
 
3604
static int
 
3605
compare_p2l_info_rev(const sub_item_ordered_t * lhs,
 
3606
                     const sub_item_ordered_t * rhs)
 
3607
{
 
3608
  svn_fs_x__id_t *lhs_part;
 
3609
  svn_fs_x__id_t *rhs_part;
 
3610
 
 
3611
  assert(lhs != rhs);
 
3612
  if (lhs->entry->item_count == 0)
 
3613
    return rhs->entry->item_count == 0 ? 0 : -1;
 
3614
  if (rhs->entry->item_count == 0)
 
3615
    return 1;
 
3616
 
 
3617
  lhs_part = lhs->order ? lhs->order[lhs->entry->item_count - 1]
 
3618
                        : &lhs->entry->items[0];
 
3619
  rhs_part = rhs->order ? rhs->order[rhs->entry->item_count - 1]
 
3620
                        : &rhs->entry->items[0];
 
3621
 
 
3622
  if (lhs_part->change_set == rhs_part->change_set)
 
3623
    return 0;
 
3624
 
 
3625
  return lhs_part->change_set < rhs_part->change_set ? -1 : 1;
 
3626
}
 
3627
 
 
3628
svn_error_t *
 
3629
svn_fs_x__l2p_index_from_p2l_entries(const char **protoname,
 
3630
                                     svn_fs_t *fs,
 
3631
                                     apr_array_header_t *entries,
 
3632
                                     apr_pool_t *result_pool,
 
3633
                                     apr_pool_t *scratch_pool)
 
3634
{
 
3635
  apr_file_t *proto_index;
 
3636
 
 
3637
  /* Use a subpool for immediate temp file cleanup at the end of this
 
3638
   * function. */
 
3639
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
3640
  svn_revnum_t prev_rev = SVN_INVALID_REVNUM;
 
3641
  int i;
 
3642
  apr_uint32_t k;
 
3643
  svn_priority_queue__t *queue;
 
3644
  apr_size_t count = 0;
 
3645
  apr_array_header_t *sub_item_orders;
 
3646
 
 
3647
  /* Create the temporary proto-rev file. */
 
3648
  SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
 
3649
                                   svn_io_file_del_on_pool_cleanup,
 
3650
                                   result_pool, scratch_pool));
 
3651
  SVN_ERR(svn_fs_x__l2p_proto_index_open(&proto_index, *protoname,
 
3652
                                         scratch_pool));
 
3653
 
 
3654
 
 
3655
  /* wrap P2L entries such that we have access to the sub-items in revision
 
3656
     order.  The ENTRY_COUNT member will point to the next item to read+1. */
 
3657
  sub_item_orders = apr_array_make(scratch_pool, entries->nelts,
 
3658
                                   sizeof(sub_item_ordered_t));
 
3659
  sub_item_orders->nelts = entries->nelts;
 
3660
 
 
3661
  for (i = 0; i < entries->nelts; ++i)
 
3662
    {
 
3663
      svn_fs_x__p2l_entry_t *entry
 
3664
        = APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t *);
 
3665
      sub_item_ordered_t *ordered
 
3666
        = &APR_ARRAY_IDX(sub_item_orders, i, sub_item_ordered_t);
 
3667
 
 
3668
      /* skip unused regions (e.g. padding) */
 
3669
      if (entry->item_count == 0)
 
3670
        {
 
3671
          --sub_item_orders->nelts;
 
3672
          continue;
 
3673
        }
 
3674
 
 
3675
      assert(entry);
 
3676
      ordered->entry = entry;
 
3677
      count += entry->item_count;
 
3678
 
 
3679
      if (entry->item_count > 1)
 
3680
        {
 
3681
          ordered->order
 
3682
            = apr_palloc(scratch_pool,
 
3683
                         sizeof(*ordered->order) * entry->item_count);
 
3684
          for (k = 0; k < entry->item_count; ++k)
 
3685
            ordered->order[k] = &entry->items[k];
 
3686
 
 
3687
          qsort(ordered->order, entry->item_count, sizeof(*ordered->order),
 
3688
                (int (*)(const void *, const void *))compare_sub_items);
 
3689
        }
 
3690
    }
 
3691
 
 
3692
  /* we need to write the index in ascending revision order */
 
3693
  queue = svn_priority_queue__create
 
3694
            (sub_item_orders,
 
3695
             (int (*)(const void *, const void *))compare_p2l_info_rev);
 
3696
 
 
3697
  /* write index entries */
 
3698
  for (i = 0; i < count; ++i)
 
3699
    {
 
3700
      svn_fs_x__id_t *sub_item;
 
3701
      sub_item_ordered_t *ordered = svn_priority_queue__peek(queue);
 
3702
 
 
3703
      if (ordered->entry->item_count > 0)
 
3704
        {
 
3705
          /* if there is only one item, we skip the overhead of having an
 
3706
             extra array for the item order */
 
3707
          sub_item = ordered->order
 
3708
                   ? ordered->order[ordered->entry->item_count - 1]
 
3709
                   : &ordered->entry->items[0];
 
3710
 
 
3711
          /* next revision? */
 
3712
          if (prev_rev != svn_fs_x__get_revnum(sub_item->change_set))
 
3713
            {
 
3714
              prev_rev = svn_fs_x__get_revnum(sub_item->change_set);
 
3715
              SVN_ERR(svn_fs_x__l2p_proto_index_add_revision
 
3716
                          (proto_index, iterpool));
 
3717
            }
 
3718
 
 
3719
          /* add entry */
 
3720
          SVN_ERR(svn_fs_x__l2p_proto_index_add_entry
 
3721
                      (proto_index, ordered->entry->offset,
 
3722
                      (apr_uint32_t)(sub_item - ordered->entry->items),
 
3723
                      sub_item->number, iterpool));
 
3724
 
 
3725
          /* make ITEM_COUNT point the next sub-item to use+1 */
 
3726
          --ordered->entry->item_count;
 
3727
        }
 
3728
 
 
3729
      /* process remaining sub-items (if any) of that container later */
 
3730
      if (ordered->entry->item_count)
 
3731
        svn_priority_queue__update(queue);
 
3732
      else
 
3733
        svn_priority_queue__pop(queue);
 
3734
 
 
3735
      /* keep memory usage in check */
 
3736
      if (i % 256 == 0)
 
3737
        svn_pool_clear(iterpool);
 
3738
    }
 
3739
 
 
3740
  /* Convert proto-index into final index and move it into position. */
 
3741
  SVN_ERR(svn_io_file_close(proto_index, iterpool));
 
3742
 
 
3743
  /* Temp file cleanup. */
 
3744
  svn_pool_destroy(iterpool);
 
3745
 
 
3746
  return SVN_NO_ERROR;
 
3747
}
 
3748
 
 
3749
 
 
3750
/*
 
3751
 * Standard (de-)serialization functions
 
3752
 */
 
3753
 
 
3754
svn_error_t *
 
3755
svn_fs_x__serialize_l2p_header(void **data,
 
3756
                               apr_size_t *data_len,
 
3757
                               void *in,
 
3758
                               apr_pool_t *pool)
 
3759
{
 
3760
  l2p_header_t *header = in;
 
3761
  svn_temp_serializer__context_t *context;
 
3762
  svn_stringbuf_t *serialized;
 
3763
  apr_size_t page_count = header->page_table_index[header->revision_count];
 
3764
  apr_size_t page_table_size = page_count * sizeof(*header->page_table);
 
3765
  apr_size_t index_size
 
3766
    = (header->revision_count + 1) * sizeof(*header->page_table_index);
 
3767
  apr_size_t data_size = sizeof(*header) + index_size + page_table_size;
 
3768
 
 
3769
  /* serialize header and all its elements */
 
3770
  context = svn_temp_serializer__init(header,
 
3771
                                      sizeof(*header),
 
3772
                                      data_size + 32,
 
3773
                                      pool);
 
3774
 
 
3775
  /* page table index array */
 
3776
  svn_temp_serializer__add_leaf(context,
 
3777
                                (const void * const *)&header->page_table_index,
 
3778
                                index_size);
 
3779
 
 
3780
  /* page table array */
 
3781
  svn_temp_serializer__add_leaf(context,
 
3782
                                (const void * const *)&header->page_table,
 
3783
                                page_table_size);
 
3784
 
 
3785
  /* return the serialized result */
 
3786
  serialized = svn_temp_serializer__get(context);
 
3787
 
 
3788
  *data = serialized->data;
 
3789
  *data_len = serialized->len;
 
3790
 
 
3791
  return SVN_NO_ERROR;
 
3792
}
 
3793
 
 
3794
svn_error_t *
 
3795
svn_fs_x__deserialize_l2p_header(void **out,
 
3796
                                 void *data,
 
3797
                                 apr_size_t data_len,
 
3798
                                 apr_pool_t *pool)
 
3799
{
 
3800
  l2p_header_t *header = (l2p_header_t *)data;
 
3801
 
 
3802
  /* resolve the pointers in the struct */
 
3803
  svn_temp_deserializer__resolve(header, (void**)&header->page_table_index);
 
3804
  svn_temp_deserializer__resolve(header, (void**)&header->page_table);
 
3805
 
 
3806
  /* done */
 
3807
  *out = header;
 
3808
 
 
3809
  return SVN_NO_ERROR;
 
3810
}
 
3811
 
 
3812
svn_error_t *
 
3813
svn_fs_x__serialize_l2p_page(void **data,
 
3814
                             apr_size_t *data_len,
 
3815
                             void *in,
 
3816
                             apr_pool_t *pool)
 
3817
{
 
3818
  l2p_page_t *page = in;
 
3819
  svn_temp_serializer__context_t *context;
 
3820
  svn_stringbuf_t *serialized;
 
3821
  apr_size_t of_table_size = page->entry_count * sizeof(*page->offsets);
 
3822
  apr_size_t si_table_size = page->entry_count * sizeof(*page->sub_items);
 
3823
 
 
3824
  /* serialize struct and all its elements */
 
3825
  context = svn_temp_serializer__init(page,
 
3826
                                      sizeof(*page),
 
3827
                                        of_table_size + si_table_size
 
3828
                                      + sizeof(*page) + 32,
 
3829
                                      pool);
 
3830
 
 
3831
  /* offsets and sub_items arrays */
 
3832
  svn_temp_serializer__add_leaf(context,
 
3833
                                (const void * const *)&page->offsets,
 
3834
                                of_table_size);
 
3835
  svn_temp_serializer__add_leaf(context,
 
3836
                                (const void * const *)&page->sub_items,
 
3837
                                si_table_size);
 
3838
 
 
3839
  /* return the serialized result */
 
3840
  serialized = svn_temp_serializer__get(context);
 
3841
 
 
3842
  *data = serialized->data;
 
3843
  *data_len = serialized->len;
 
3844
 
 
3845
  return SVN_NO_ERROR;
 
3846
}
 
3847
 
 
3848
svn_error_t *
 
3849
svn_fs_x__deserialize_l2p_page(void **out,
 
3850
                               void *data,
 
3851
                               apr_size_t data_len,
 
3852
                               apr_pool_t *pool)
 
3853
{
 
3854
  l2p_page_t *page = data;
 
3855
 
 
3856
  /* resolve the pointers in the struct */
 
3857
  svn_temp_deserializer__resolve(page, (void**)&page->offsets);
 
3858
  svn_temp_deserializer__resolve(page, (void**)&page->sub_items);
 
3859
 
 
3860
  /* done */
 
3861
  *out = page;
 
3862
 
 
3863
  return SVN_NO_ERROR;
 
3864
}
 
3865
 
 
3866
svn_error_t *
 
3867
svn_fs_x__serialize_p2l_header(void **data,
 
3868
                               apr_size_t *data_len,
 
3869
                               void *in,
 
3870
                               apr_pool_t *pool)
 
3871
{
 
3872
  p2l_header_t *header = in;
 
3873
  svn_temp_serializer__context_t *context;
 
3874
  svn_stringbuf_t *serialized;
 
3875
  apr_size_t table_size = (header->page_count + 1) * sizeof(*header->offsets);
 
3876
 
 
3877
  /* serialize header and all its elements */
 
3878
  context = svn_temp_serializer__init(header,
 
3879
                                      sizeof(*header),
 
3880
                                      table_size + sizeof(*header) + 32,
 
3881
                                      pool);
 
3882
 
 
3883
  /* offsets array */
 
3884
  svn_temp_serializer__add_leaf(context,
 
3885
                                (const void * const *)&header->offsets,
 
3886
                                table_size);
 
3887
 
 
3888
  /* return the serialized result */
 
3889
  serialized = svn_temp_serializer__get(context);
 
3890
 
 
3891
  *data = serialized->data;
 
3892
  *data_len = serialized->len;
 
3893
 
 
3894
  return SVN_NO_ERROR;
 
3895
}
 
3896
 
 
3897
svn_error_t *
 
3898
svn_fs_x__deserialize_p2l_header(void **out,
 
3899
                                 void *data,
 
3900
                                 apr_size_t data_len,
 
3901
                                 apr_pool_t *pool)
 
3902
{
 
3903
  p2l_header_t *header = data;
 
3904
 
 
3905
  /* resolve the only pointer in the struct */
 
3906
  svn_temp_deserializer__resolve(header, (void**)&header->offsets);
 
3907
 
 
3908
  /* done */
 
3909
  *out = header;
 
3910
 
 
3911
  return SVN_NO_ERROR;
 
3912
}
 
3913
 
 
3914
svn_error_t *
 
3915
svn_fs_x__serialize_p2l_page(void **data,
 
3916
                             apr_size_t *data_len,
 
3917
                             void *in,
 
3918
                             apr_pool_t *pool)
 
3919
{
 
3920
  apr_array_header_t *page = in;
 
3921
  svn_temp_serializer__context_t *context;
 
3922
  svn_stringbuf_t *serialized;
 
3923
  apr_size_t table_size = page->elt_size * page->nelts;
 
3924
  svn_fs_x__p2l_entry_t *entries = (svn_fs_x__p2l_entry_t *)page->elts;
 
3925
  int i;
 
3926
 
 
3927
  /* serialize array header and all its elements */
 
3928
  context = svn_temp_serializer__init(page,
 
3929
                                      sizeof(*page),
 
3930
                                      table_size + sizeof(*page) + 32,
 
3931
                                      pool);
 
3932
 
 
3933
  /* items in the array */
 
3934
  svn_temp_serializer__push(context,
 
3935
                            (const void * const *)&page->elts,
 
3936
                            table_size);
 
3937
 
 
3938
  for (i = 0; i < page->nelts; ++i)
 
3939
    svn_temp_serializer__add_leaf(context,
 
3940
                                  (const void * const *)&entries[i].items,
 
3941
                                    entries[i].item_count
 
3942
                                  * sizeof(*entries[i].items));
 
3943
 
 
3944
  svn_temp_serializer__pop(context);
 
3945
 
 
3946
  /* return the serialized result */
 
3947
  serialized = svn_temp_serializer__get(context);
 
3948
 
 
3949
  *data = serialized->data;
 
3950
  *data_len = serialized->len;
 
3951
 
 
3952
  return SVN_NO_ERROR;
 
3953
}
 
3954
 
 
3955
svn_error_t *
 
3956
svn_fs_x__deserialize_p2l_page(void **out,
 
3957
                               void *data,
 
3958
                               apr_size_t data_len,
 
3959
                               apr_pool_t *pool)
 
3960
{
 
3961
  apr_array_header_t *page = (apr_array_header_t *)data;
 
3962
  svn_fs_x__p2l_entry_t *entries;
 
3963
  int i;
 
3964
 
 
3965
  /* resolve the only pointer in the struct */
 
3966
  svn_temp_deserializer__resolve(page, (void**)&page->elts);
 
3967
 
 
3968
  /* resolve sub-struct pointers*/
 
3969
  entries = (svn_fs_x__p2l_entry_t *)page->elts;
 
3970
  for (i = 0; i < page->nelts; ++i)
 
3971
    svn_temp_deserializer__resolve(entries, (void**)&entries[i].items);
 
3972
 
 
3973
  /* patch up members */
 
3974
  page->pool = pool;
 
3975
  page->nalloc = page->nelts;
 
3976
 
 
3977
  /* done */
 
3978
  *out = page;
 
3979
 
 
3980
  return SVN_NO_ERROR;
 
3981
}