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