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