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