]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_x/verify.c
Update Subversion to 1.14.0 LTS. See contrib/subversion/CHANGES for a
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_fs_x / verify.c
1 /* verify.c --- verification of FSX filesystems
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 "verify.h"
24 #include "fs_x.h"
25 #include "svn_time.h"
26 #include "private/svn_subr_private.h"
27
28 #include "cached_data.h"
29 #include "rep-cache.h"
30 #include "revprops.h"
31 #include "util.h"
32 #include "index.h"
33
34 #include "../libsvn_fs/fs-loader.h"
35
36 #include "svn_private_config.h"
37
38 \f
39 /** Verifying. **/
40
41 /* Baton type expected by verify_walker().  The purpose is to limit the
42  * number of notifications sent.
43  */
44 typedef struct verify_walker_baton_t
45 {
46   /* number of calls to verify_walker() since the last clean */
47   int iteration_count;
48
49   /* progress notification callback to invoke periodically (may be NULL) */
50   svn_fs_progress_notify_func_t notify_func;
51
52   /* baton to use with NOTIFY_FUNC */
53   void *notify_baton;
54
55   /* remember the last revision for which we called notify_func */
56   svn_revnum_t last_notified_revision;
57 } verify_walker_baton_t;
58
59 /* Used by svn_fs_x__verify().
60    Implements svn_fs_x__walk_rep_reference().walker.  */
61 static svn_error_t *
62 verify_walker(svn_fs_x__representation_t *rep,
63               void *baton,
64               svn_fs_t *fs,
65               apr_pool_t *scratch_pool)
66 {
67   verify_walker_baton_t *walker_baton = baton;
68
69   /* notify and free resources periodically */
70   if (walker_baton->iteration_count > 1000)
71     {
72       svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set);
73       if (   walker_baton->notify_func
74           && revision != walker_baton->last_notified_revision)
75         {
76           walker_baton->notify_func(revision,
77                                     walker_baton->notify_baton,
78                                     scratch_pool);
79           walker_baton->last_notified_revision = revision;
80         }
81
82       walker_baton->iteration_count = 0;
83     }
84
85   /* access the repo data */
86   SVN_ERR(svn_fs_x__check_rep(rep, fs, scratch_pool));
87
88   /* update resource usage counters */
89   walker_baton->iteration_count++;
90
91   return SVN_NO_ERROR;
92 }
93
94 /* Verify the rep cache DB's consistency with our rev / pack data.
95  * The function signature is similar to svn_fs_x__verify.
96  * The values of START and END have already been auto-selected and
97  * verified.
98  */
99 static svn_error_t *
100 verify_rep_cache(svn_fs_t *fs,
101                  svn_revnum_t start,
102                  svn_revnum_t end,
103                  svn_fs_progress_notify_func_t notify_func,
104                  void *notify_baton,
105                  svn_cancel_func_t cancel_func,
106                  void *cancel_baton,
107                  apr_pool_t *scratch_pool)
108 {
109   svn_boolean_t exists;
110
111   /* rep-cache verification. */
112   SVN_ERR(svn_fs_x__exists_rep_cache(&exists, fs, scratch_pool));
113   if (exists)
114     {
115       /* provide a baton to allow the reuse of open file handles between
116          iterations (saves 2/3 of OS level file operations). */
117       verify_walker_baton_t *baton
118         = apr_pcalloc(scratch_pool, sizeof(*baton));
119
120       baton->last_notified_revision = SVN_INVALID_REVNUM;
121       baton->notify_func = notify_func;
122       baton->notify_baton = notify_baton;
123
124       /* tell the user that we are now ready to do *something* */
125       if (notify_func)
126         notify_func(SVN_INVALID_REVNUM, notify_baton, scratch_pool);
127
128       /* Do not attempt to walk the rep-cache database if its file does
129          not exist,  since doing so would create it --- which may confuse
130          the administrator.   Don't take any lock. */
131       SVN_ERR(svn_fs_x__walk_rep_reference(fs, start, end,
132                                            verify_walker, baton,
133                                            cancel_func, cancel_baton,
134                                            scratch_pool));
135     }
136
137   return SVN_NO_ERROR;
138 }
139
140 /* Verify that the MD5 checksum of the data between offsets START and END
141  * in FILE matches the EXPECTED checksum.  If there is a mismatch use the
142  * indedx NAME in the error message.  Supports cancellation with CANCEL_FUNC
143  * and CANCEL_BATON.  SCRATCH_POOL is for temporary allocations. */
144 static svn_error_t *
145 verify_index_checksum(svn_fs_x__revision_file_t *file,
146                       const char *name,
147                       svn_fs_x__index_info_t *index_info,
148                       svn_cancel_func_t cancel_func,
149                       void *cancel_baton,
150                       apr_pool_t *scratch_pool)
151 {
152   unsigned char buffer[SVN__STREAM_CHUNK_SIZE];
153   apr_off_t size = index_info->end - index_info->start;
154   svn_checksum_t *actual;
155   svn_checksum_ctx_t *context
156     = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
157
158   /* Calculate the index checksum. */
159   SVN_ERR(svn_fs_x__rev_file_seek(file, NULL, index_info->start));
160   while (size > 0)
161     {
162       apr_size_t to_read = size > sizeof(buffer)
163                          ? sizeof(buffer)
164                          : (apr_size_t)size;
165       SVN_ERR(svn_fs_x__rev_file_read(file, buffer, to_read));
166       SVN_ERR(svn_checksum_update(context, buffer, to_read));
167       size -= to_read;
168
169       if (cancel_func)
170         SVN_ERR(cancel_func(cancel_baton));
171     }
172
173   SVN_ERR(svn_checksum_final(&actual, context, scratch_pool));
174
175   /* Verify that it matches the expected checksum. */
176   if (!svn_checksum_match(index_info->checksum, actual))
177     {
178       const char *file_name;
179
180       SVN_ERR(svn_fs_x__rev_file_name(&file_name, file, scratch_pool));
181       SVN_ERR(svn_checksum_mismatch_err(index_info->checksum, actual,
182                                         scratch_pool,
183                                         _("%s checksum mismatch in file %s"),
184                                         name, file_name));
185     }
186
187   return SVN_NO_ERROR;
188 }
189
190 /* Verify the MD5 checksums of the index data in the rev / pack file
191  * containing revision START in FS.  If given, invoke CANCEL_FUNC with
192  * CANCEL_BATON at regular intervals.  Use SCRATCH_POOL for temporary
193  * allocations.
194  */
195 static svn_error_t *
196 verify_index_checksums(svn_fs_t *fs,
197                        svn_revnum_t start,
198                        svn_cancel_func_t cancel_func,
199                        void *cancel_baton,
200                        apr_pool_t *scratch_pool)
201 {
202   svn_fs_x__revision_file_t *rev_file;
203   svn_fs_x__index_info_t l2p_index_info;
204   svn_fs_x__index_info_t p2l_index_info;
205
206   /* Open the rev / pack file and read the footer */
207   SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));
208   SVN_ERR(svn_fs_x__rev_file_l2p_info(&l2p_index_info, rev_file));
209   SVN_ERR(svn_fs_x__rev_file_p2l_info(&p2l_index_info, rev_file));
210
211   /* Verify the index contents against the checksum from the footer. */
212   SVN_ERR(verify_index_checksum(rev_file, "L2P index", &l2p_index_info,
213                                 cancel_func, cancel_baton, scratch_pool));
214   SVN_ERR(verify_index_checksum(rev_file, "P2L index", &p2l_index_info,
215                                 cancel_func, cancel_baton, scratch_pool));
216
217   /* Done. */
218   SVN_ERR(svn_fs_x__close_revision_file(rev_file));
219
220   return SVN_NO_ERROR;
221 }
222
223 /* Verify that for all log-to-phys index entries for revisions START to
224  * START + COUNT-1 in FS there is a consistent entry in the phys-to-log
225  * index.  If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
226  * intervals. Use SCRATCH_POOL for temporary allocations.
227  */
228 static svn_error_t *
229 compare_l2p_to_p2l_index(svn_fs_t *fs,
230                          svn_revnum_t start,
231                          svn_revnum_t count,
232                          svn_cancel_func_t cancel_func,
233                          void *cancel_baton,
234                          apr_pool_t *scratch_pool)
235 {
236   svn_revnum_t i;
237   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
238   apr_array_header_t *max_ids;
239
240   /* common file access structure */
241   svn_fs_x__revision_file_t *rev_file;
242   SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));
243
244   /* determine the range of items to check for each revision */
245   SVN_ERR(svn_fs_x__l2p_get_max_ids(&max_ids, fs, start, count, scratch_pool,
246                                     iterpool));
247
248   /* check all items in all revisions if the given range */
249   for (i = 0; i < max_ids->nelts; ++i)
250     {
251       apr_uint64_t k;
252       apr_uint64_t max_id = APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
253       svn_revnum_t revision = start + i;
254
255       for (k = 0; k < max_id; ++k)
256         {
257           apr_off_t offset;
258           apr_uint32_t sub_item;
259           svn_fs_x__id_t l2p_item;
260           svn_fs_x__id_t *p2l_item;
261
262           l2p_item.change_set = svn_fs_x__change_set_by_rev(revision);
263           l2p_item.number = k;
264
265           /* get L2P entry.  Ignore unused entries. */
266           SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file,
267                                         &l2p_item, iterpool));
268           if (offset == -1)
269             continue;
270
271           /* find the corresponding P2L entry */
272           SVN_ERR(svn_fs_x__p2l_item_lookup(&p2l_item, fs, rev_file,
273                                             revision, offset, sub_item,
274                                             iterpool, iterpool));
275
276           if (p2l_item == NULL)
277             return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
278                                      NULL,
279                                      _("p2l index entry not found for "
280                                        "PHYS o%s:s%ld returned by "
281                                        "l2p index for LOG r%ld:i%ld"),
282                                      apr_off_t_toa(scratch_pool, offset),
283                                      (long)sub_item, revision, (long)k);
284
285           if (!svn_fs_x__id_eq(&l2p_item, p2l_item))
286             return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
287                                      NULL,
288                                      _("p2l index info LOG r%ld:i%ld"
289                                        " does not match "
290                                        "l2p index for LOG r%ld:i%ld"),
291                                      svn_fs_x__get_revnum(p2l_item->change_set),
292                                      (long)p2l_item->number, revision,
293                                      (long)k);
294
295           svn_pool_clear(iterpool);
296         }
297
298       if (cancel_func)
299         SVN_ERR(cancel_func(cancel_baton));
300     }
301
302   svn_pool_destroy(iterpool);
303
304   SVN_ERR(svn_fs_x__close_revision_file(rev_file));
305
306   return SVN_NO_ERROR;
307 }
308
309 /* Verify that for all phys-to-log index entries for revisions START to
310  * START + COUNT-1 in FS there is a consistent entry in the log-to-phys
311  * index.  If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
312  * intervals. Use SCRATCH_POOL for temporary allocations.
313  *
314  * Please note that we can only check on pack / rev file granularity and
315  * must only be called for a single rev / pack file.
316  */
317 static svn_error_t *
318 compare_p2l_to_l2p_index(svn_fs_t *fs,
319                          svn_revnum_t start,
320                          svn_revnum_t count,
321                          svn_cancel_func_t cancel_func,
322                          void *cancel_baton,
323                          apr_pool_t *scratch_pool)
324 {
325   svn_fs_x__data_t *ffd = fs->fsap_data;
326   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
327   apr_pool_t *iterpool2 = svn_pool_create(scratch_pool);
328   apr_off_t max_offset;
329   apr_off_t offset = 0;
330
331   /* common file access structure */
332   svn_fs_x__revision_file_t *rev_file;
333   SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));
334
335   /* get the size of the rev / pack file as covered by the P2L index */
336   SVN_ERR(svn_fs_x__p2l_get_max_offset(&max_offset, fs, rev_file, start,
337                                        scratch_pool));
338
339   /* for all offsets in the file, get the P2L index entries and check
340      them against the L2P index */
341   for (offset = 0; offset < max_offset; )
342     {
343       apr_array_header_t *entries;
344       svn_fs_x__p2l_entry_t *last_entry;
345       int i;
346
347       svn_pool_clear(iterpool);
348
349       /* get all entries for the current block */
350       SVN_ERR(svn_fs_x__p2l_index_lookup(&entries, fs, rev_file, start,
351                                          offset, ffd->p2l_page_size,
352                                          iterpool, iterpool));
353       if (entries->nelts == 0)
354         return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION,
355                                  NULL,
356                                  _("p2l does not cover offset %s"
357                                    " for revision %ld"),
358                                   apr_off_t_toa(scratch_pool, offset), start);
359
360       /* process all entries (and later continue with the next block) */
361       last_entry
362         = &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_x__p2l_entry_t);
363       offset = last_entry->offset + last_entry->size;
364
365       for (i = 0; i < entries->nelts; ++i)
366         {
367           apr_uint32_t k;
368           svn_fs_x__p2l_entry_t *entry
369             = &APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t);
370
371           /* check all sub-items for consist entries in the L2P index */
372           for (k = 0; k < entry->item_count; ++k)
373             {
374               apr_off_t l2p_offset;
375               apr_uint32_t sub_item;
376               svn_fs_x__id_t *p2l_item = &entry->items[k];
377               svn_revnum_t revision
378                 = svn_fs_x__get_revnum(p2l_item->change_set);
379
380               svn_pool_clear(iterpool2);
381               SVN_ERR(svn_fs_x__item_offset(&l2p_offset, &sub_item, fs,
382                                             rev_file, p2l_item, iterpool2));
383
384               if (sub_item != k || l2p_offset != entry->offset)
385                 return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
386                                          NULL,
387                                          _("l2p index entry PHYS o%s:s%ld "
388                                            "does not match p2l index value "
389                                            "LOG r%ld:i%ld for PHYS o%s:s%ld"),
390                                          apr_off_t_toa(scratch_pool,
391                                                        l2p_offset),
392                                          (long)sub_item,
393                                          revision,
394                                          (long)p2l_item->number,
395                                          apr_off_t_toa(scratch_pool,
396                                                        entry->offset),
397                                          (long)k);
398             }
399         }
400
401       if (cancel_func)
402         SVN_ERR(cancel_func(cancel_baton));
403     }
404
405   svn_pool_destroy(iterpool2);
406   svn_pool_destroy(iterpool);
407
408   SVN_ERR(svn_fs_x__close_revision_file(rev_file));
409
410   return SVN_NO_ERROR;
411 }
412
413 /* Items smaller than this can be read at once into a buffer and directly
414  * be checksummed.  Larger items require stream processing.
415  * Must be a multiple of 8. */
416 #define STREAM_THRESHOLD 4096
417
418 /* Verify that the next SIZE bytes read from FILE are NUL.  SIZE must not
419  * exceed STREAM_THRESHOLD.  Use SCRATCH_POOL for temporary allocations.
420  */
421 static svn_error_t *
422 expect_buffer_nul(svn_fs_x__revision_file_t *file,
423                   apr_off_t size,
424                   apr_pool_t *scratch_pool)
425 {
426   union
427   {
428     unsigned char buffer[STREAM_THRESHOLD];
429     apr_uint64_t chunks[STREAM_THRESHOLD / sizeof(apr_uint64_t)];
430   } data;
431
432   apr_size_t i;
433   SVN_ERR_ASSERT(size <= STREAM_THRESHOLD);
434
435   /* read the whole data block; error out on failure */
436   data.chunks[(size - 1)/ sizeof(apr_uint64_t)] = 0;
437   SVN_ERR(svn_fs_x__rev_file_read(file, data.buffer, size));
438
439   /* chunky check */
440   for (i = 0; i < size / sizeof(apr_uint64_t); ++i)
441     if (data.chunks[i] != 0)
442       break;
443
444   /* byte-wise check upon mismatch or at the end of the block */
445   for (i *= sizeof(apr_uint64_t); i < size; ++i)
446     if (data.buffer[i] != 0)
447       {
448         const char *file_name;
449         apr_off_t offset;
450
451         SVN_ERR(svn_fs_x__rev_file_name(&file_name, file, scratch_pool));
452         SVN_ERR(svn_fs_x__rev_file_offset(&offset, file));
453         offset -= size - i;
454
455         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
456                                  _("Empty section in file %s contains "
457                                    "non-NUL data at offset %s"),
458                                  file_name,
459                                  apr_off_t_toa(scratch_pool, offset));
460       }
461
462   return SVN_NO_ERROR;
463 }
464
465 /* Verify that the next SIZE bytes read from FILE are NUL.
466  * Use SCRATCH_POOL for temporary allocations.
467  */
468 static svn_error_t *
469 read_all_nul(svn_fs_x__revision_file_t *file,
470              apr_off_t size,
471              apr_pool_t *scratch_pool)
472 {
473   for (; size >= STREAM_THRESHOLD; size -= STREAM_THRESHOLD)
474     SVN_ERR(expect_buffer_nul(file, STREAM_THRESHOLD, scratch_pool));
475
476   if (size)
477     SVN_ERR(expect_buffer_nul(file, size, scratch_pool));
478
479   return SVN_NO_ERROR;
480 }
481
482 /* Compare the ACTUAL checksum with the one expected by ENTRY.
483  * Return an error in case of mismatch.  Use the name of FILE
484  * in error message.  Allocate temporary data in SCRATCH_POOL.
485  */
486 static svn_error_t *
487 expected_checksum(svn_fs_x__revision_file_t *file,
488                   svn_fs_x__p2l_entry_t *entry,
489                   apr_uint32_t actual,
490                   apr_pool_t *scratch_pool)
491 {
492   if (actual != entry->fnv1_checksum)
493     {
494       const char *file_name;
495
496       SVN_ERR(svn_fs_x__rev_file_name(&file_name, file, scratch_pool));
497       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
498                                _("Checksum mismatch in item at offset %s of "
499                                  "length %s bytes in file %s"),
500                                apr_off_t_toa(scratch_pool, entry->offset),
501                                apr_off_t_toa(scratch_pool, entry->size),
502                                file_name);
503     }
504
505   return SVN_NO_ERROR;
506 }
507
508 /* Verify that the FNV checksum over the next ENTRY->SIZE bytes read
509  * from FILE will match ENTRY's expected checksum.  SIZE must not
510  * exceed STREAM_THRESHOLD.  Use SCRATCH_POOL for temporary allocations.
511  */
512 static svn_error_t *
513 expected_buffered_checksum(svn_fs_x__revision_file_t *file,
514                            svn_fs_x__p2l_entry_t *entry,
515                            apr_pool_t *scratch_pool)
516 {
517   unsigned char buffer[STREAM_THRESHOLD];
518   SVN_ERR_ASSERT(entry->size <= STREAM_THRESHOLD);
519
520   SVN_ERR(svn_fs_x__rev_file_read(file, buffer, (apr_size_t)entry->size));
521   SVN_ERR(expected_checksum(file, entry,
522                             svn__fnv1a_32x4(buffer, (apr_size_t)entry->size),
523                             scratch_pool));
524
525   return SVN_NO_ERROR;
526 }
527
528 /* Verify that the FNV checksum over the next ENTRY->SIZE bytes read from
529  * FILE will match ENTRY's expected checksum.
530  * Use SCRATCH_POOL for temporary allocations.
531  */
532 static svn_error_t *
533 expected_streamed_checksum(svn_fs_x__revision_file_t *file,
534                            svn_fs_x__p2l_entry_t *entry,
535                            apr_pool_t *scratch_pool)
536 {
537   unsigned char buffer[STREAM_THRESHOLD];
538   svn_checksum_t *checksum;
539   svn_checksum_ctx_t *context
540     = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, scratch_pool);
541   apr_off_t size = entry->size;
542
543   while (size > 0)
544     {
545       apr_size_t to_read = size > sizeof(buffer)
546                          ? sizeof(buffer)
547                          : (apr_size_t)size;
548       SVN_ERR(svn_fs_x__rev_file_read(file, buffer, to_read));
549       SVN_ERR(svn_checksum_update(context, buffer, to_read));
550       size -= to_read;
551     }
552
553   SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
554   SVN_ERR(expected_checksum(file, entry,
555                             ntohl(*(const apr_uint32_t *)checksum->digest),
556                             scratch_pool));
557
558   return SVN_NO_ERROR;
559 }
560
561 /* Verify that for all phys-to-log index entries for revisions START to
562  * START + COUNT-1 in FS match the actual pack / rev file contents.
563  * If given, invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
564  * Use SCRATCH_POOL for temporary allocations.
565  *
566  * Please note that we can only check on pack / rev file granularity and
567  * must only be called for a single rev / pack file.
568  */
569 static svn_error_t *
570 compare_p2l_to_rev(svn_fs_t *fs,
571                    svn_revnum_t start,
572                    svn_revnum_t count,
573                    svn_cancel_func_t cancel_func,
574                    void *cancel_baton,
575                    apr_pool_t *scratch_pool)
576 {
577   svn_fs_x__data_t *ffd = fs->fsap_data;
578   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
579   apr_off_t max_offset;
580   apr_off_t offset = 0;
581   svn_fs_x__revision_file_t *rev_file;
582   svn_fs_x__index_info_t l2p_index_info;
583
584   /* open the pack / rev file that is covered by the p2l index */
585   SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));
586
587   /* check file size vs. range covered by index */
588   SVN_ERR(svn_fs_x__rev_file_l2p_info(&l2p_index_info, rev_file));
589   SVN_ERR(svn_fs_x__p2l_get_max_offset(&max_offset, fs, rev_file, start,
590                                        scratch_pool));
591
592   if (l2p_index_info.start != max_offset)
593     return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT, NULL,
594                              _("File size of %s for revision r%ld does "
595                                "not match p2l index size of %s"),
596                              apr_off_t_toa(scratch_pool,
597                                            l2p_index_info.start),
598                              start,
599                              apr_off_t_toa(scratch_pool,
600                                            max_offset));
601
602   SVN_ERR(svn_fs_x__rev_file_seek(rev_file, NULL, 0));
603
604   /* for all offsets in the file, get the P2L index entries and check
605      them against the L2P index */
606   for (offset = 0; offset < max_offset; )
607     {
608       apr_array_header_t *entries;
609       int i;
610
611       svn_pool_clear(iterpool);
612
613       /* get all entries for the current block */
614       SVN_ERR(svn_fs_x__p2l_index_lookup(&entries, fs, rev_file, start,
615                                          offset, ffd->p2l_page_size,
616                                          iterpool, iterpool));
617
618       /* The above might have moved the file pointer.
619        * Ensure we actually start reading at OFFSET.  */
620       SVN_ERR(svn_fs_x__rev_file_seek(rev_file, NULL, offset));
621
622       /* process all entries (and later continue with the next block) */
623       for (i = 0; i < entries->nelts; ++i)
624         {
625           svn_fs_x__p2l_entry_t *entry
626             = &APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t);
627
628           /* skip bits we previously checked */
629           if (i == 0 && entry->offset < offset)
630             continue;
631
632           /* skip zero-sized entries */
633           if (entry->size == 0)
634             continue;
635
636           /* p2l index must cover all rev / pack file offsets exactly once */
637           if (entry->offset != offset)
638             return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
639                                      NULL,
640                                      _("p2l index entry for revision r%ld"
641                                        " is non-contiguous between offsets "
642                                        " %s and %s"),
643                                      start,
644                                      apr_off_t_toa(scratch_pool, offset),
645                                      apr_off_t_toa(scratch_pool,
646                                                    entry->offset));
647
648           /* empty sections must contain NUL bytes only */
649           if (entry->type == SVN_FS_X__ITEM_TYPE_UNUSED)
650             {
651               /* skip filler entry at the end of the p2l index */
652               if (entry->offset != max_offset)
653                 SVN_ERR(read_all_nul(rev_file, entry->size, iterpool));
654             }
655           else
656             {
657               if (entry->size < STREAM_THRESHOLD)
658                 SVN_ERR(expected_buffered_checksum(rev_file, entry,
659                                                    iterpool));
660               else
661                 SVN_ERR(expected_streamed_checksum(rev_file, entry,
662                                                    iterpool));
663             }
664
665           /* advance offset */
666           offset += entry->size;
667         }
668
669       if (cancel_func)
670         SVN_ERR(cancel_func(cancel_baton));
671     }
672
673   svn_pool_destroy(iterpool);
674
675   return SVN_NO_ERROR;
676 }
677
678 /* Verify that the revprops of the revisions START to END in FS can be
679  * accessed.  Invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
680  *
681  * The values of START and END have already been auto-selected and
682  * verified.
683  */
684 static svn_error_t *
685 verify_revprops(svn_fs_t *fs,
686                 svn_revnum_t start,
687                 svn_revnum_t end,
688                 svn_cancel_func_t cancel_func,
689                 void *cancel_baton,
690                 apr_pool_t *scratch_pool)
691 {
692   svn_revnum_t revision;
693   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
694
695   /* Invalidate the revprop generation once.
696    * Use the cache inside the loop to speed up packed revprop access. */
697   svn_fs_x__invalidate_revprop_generation(fs);
698
699   for (revision = start; revision < end; ++revision)
700     {
701       svn_string_t *date;
702       apr_time_t timetemp;
703
704       svn_pool_clear(iterpool);
705
706       /* Access the svn:date revprop.
707        * This implies parsing all revprops for that revision. */
708       SVN_ERR(svn_fs_x__revision_prop(&date, fs, revision,
709                                       SVN_PROP_REVISION_DATE, FALSE,
710                                       iterpool, iterpool));
711
712       /* The time stamp is the only revprop that, if given, needs to
713        * have a valid content. */
714       if (date)
715         SVN_ERR(svn_time_from_cstring(&timetemp, date->data, iterpool));
716
717       if (cancel_func)
718         SVN_ERR(cancel_func(cancel_baton));
719     }
720
721   svn_pool_destroy(iterpool);
722
723   return SVN_NO_ERROR;
724 }
725
726 /* Verify that on-disk representation has not been tempered with (in a way
727  * that leaves the repository in a corrupted state).  This compares log-to-
728  * phys with phys-to-log indexes, verifies the low-level checksums and
729  * checks that all revprops are available.  The function signature is
730  * similar to svn_fs_x__verify.
731  *
732  * The values of START and END have already been auto-selected and
733  * verified.
734  */
735 static svn_error_t *
736 verify_metadata_consistency(svn_fs_t *fs,
737                             svn_revnum_t start,
738                             svn_revnum_t end,
739                             svn_fs_progress_notify_func_t notify_func,
740                             void *notify_baton,
741                             svn_cancel_func_t cancel_func,
742                             void *cancel_baton,
743                             apr_pool_t *scratch_pool)
744 {
745   svn_error_t *err;
746   svn_fs_x__data_t *ffd = fs->fsap_data;
747   svn_revnum_t revision, next_revision;
748   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
749
750   for (revision = start; revision <= end; revision = next_revision)
751     {
752       svn_revnum_t count = svn_fs_x__packed_base_rev(fs, revision);
753       svn_revnum_t pack_start = count;
754       svn_revnum_t pack_end = pack_start + svn_fs_x__pack_size(fs, revision);
755
756       svn_pool_clear(iterpool);
757
758       if (notify_func && (pack_start % ffd->max_files_per_dir == 0))
759         notify_func(pack_start, notify_baton, iterpool);
760
761       /* Check for external corruption to the indexes. */
762       err = verify_index_checksums(fs, pack_start, cancel_func,
763                                    cancel_baton, iterpool);
764
765       /* two-way index check */
766       if (!err)
767         err = compare_l2p_to_p2l_index(fs, pack_start, pack_end - pack_start,
768                                        cancel_func, cancel_baton, iterpool);
769       if (!err)
770         err = compare_p2l_to_l2p_index(fs, pack_start, pack_end - pack_start,
771                                        cancel_func, cancel_baton, iterpool);
772
773       /* verify in-index checksums and types vs. actual rev / pack files */
774       if (!err)
775         err = compare_p2l_to_rev(fs, pack_start, pack_end - pack_start,
776                                  cancel_func, cancel_baton, iterpool);
777
778       /* ensure that revprops are available and accessible */
779       if (!err)
780         err = verify_revprops(fs, pack_start, pack_end,
781                               cancel_func, cancel_baton, iterpool);
782
783       /* concurrent packing is one of the reasons why verification may fail.
784          Make sure, we operate on up-to-date information. */
785       if (err)
786         {
787           svn_error_t *err2
788             = svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev,
789                                               fs, scratch_pool);
790
791           /* Be careful to not leak ERR. */
792           if (err2)
793             return svn_error_trace(svn_error_compose_create(err, err2));
794         }
795
796       /* retry the whole shard if it got packed in the meantime */
797       if (err && count != svn_fs_x__pack_size(fs, revision))
798         {
799           svn_error_clear(err);
800
801           /* We could simply assign revision here but the code below is
802              more intuitive to maintainers. */
803           next_revision = svn_fs_x__packed_base_rev(fs, revision);
804         }
805       else
806         {
807           SVN_ERR(err);
808           next_revision = pack_end;
809         }
810     }
811
812   svn_pool_destroy(iterpool);
813
814   return SVN_NO_ERROR;
815 }
816
817 svn_error_t *
818 svn_fs_x__verify(svn_fs_t *fs,
819                  svn_revnum_t start,
820                  svn_revnum_t end,
821                  svn_fs_progress_notify_func_t notify_func,
822                  void *notify_baton,
823                  svn_cancel_func_t cancel_func,
824                  void *cancel_baton,
825                  apr_pool_t *scratch_pool)
826 {
827   /* Input validation. */
828   if (! SVN_IS_VALID_REVNUM(start))
829     start = 0;
830   if (! SVN_IS_VALID_REVNUM(end))
831     {
832       SVN_ERR(svn_fs_x__youngest_rev(&end, fs, scratch_pool));
833     }
834
835   SVN_ERR(svn_fs_x__ensure_revision_exists(start, fs, scratch_pool));
836   SVN_ERR(svn_fs_x__ensure_revision_exists(end, fs, scratch_pool));
837
838   /* log/phys index consistency.  We need to check them first to make
839      sure we can access the rev / pack files in format7. */
840   SVN_ERR(verify_metadata_consistency(fs, start, end,
841                                       notify_func, notify_baton,
842                                       cancel_func, cancel_baton,
843                                       scratch_pool));
844
845   /* rep cache consistency */
846   SVN_ERR(verify_rep_cache(fs, start, end, notify_func, notify_baton,
847                             cancel_func, cancel_baton, scratch_pool));
848
849   return SVN_NO_ERROR;
850 }