summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/verify.c')
-rw-r--r--subversion/libsvn_fs_x/verify.c850
1 files changed, 850 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/verify.c b/subversion/libsvn_fs_x/verify.c
new file mode 100644
index 0000000..4ea0728
--- /dev/null
+++ b/subversion/libsvn_fs_x/verify.c
@@ -0,0 +1,850 @@
+/* verify.c --- verification of FSX filesystems
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "verify.h"
+#include "fs_x.h"
+#include "svn_time.h"
+#include "private/svn_subr_private.h"
+
+#include "cached_data.h"
+#include "rep-cache.h"
+#include "util.h"
+#include "index.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+
+/** Verifying. **/
+
+/* Baton type expected by verify_walker(). The purpose is to limit the
+ * number of notifications sent.
+ */
+typedef struct verify_walker_baton_t
+{
+ /* number of calls to verify_walker() since the last clean */
+ int iteration_count;
+
+ /* progress notification callback to invoke periodically (may be NULL) */
+ svn_fs_progress_notify_func_t notify_func;
+
+ /* baton to use with NOTIFY_FUNC */
+ void *notify_baton;
+
+ /* remember the last revision for which we called notify_func */
+ svn_revnum_t last_notified_revision;
+} verify_walker_baton_t;
+
+/* Used by svn_fs_x__verify().
+ Implements svn_fs_x__walk_rep_reference().walker. */
+static svn_error_t *
+verify_walker(svn_fs_x__representation_t *rep,
+ void *baton,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ verify_walker_baton_t *walker_baton = baton;
+
+ /* notify and free resources periodically */
+ if (walker_baton->iteration_count > 1000)
+ {
+ svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set);
+ if ( walker_baton->notify_func
+ && revision != walker_baton->last_notified_revision)
+ {
+ walker_baton->notify_func(revision,
+ walker_baton->notify_baton,
+ scratch_pool);
+ walker_baton->last_notified_revision = revision;
+ }
+
+ walker_baton->iteration_count = 0;
+ }
+
+ /* access the repo data */
+ SVN_ERR(svn_fs_x__check_rep(rep, fs, scratch_pool));
+
+ /* update resource usage counters */
+ walker_baton->iteration_count++;
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify the rep cache DB's consistency with our rev / pack data.
+ * The function signature is similar to svn_fs_x__verify.
+ * The values of START and END have already been auto-selected and
+ * verified.
+ */
+static svn_error_t *
+verify_rep_cache(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t exists;
+
+ /* rep-cache verification. */
+ SVN_ERR(svn_fs_x__exists_rep_cache(&exists, fs, scratch_pool));
+ if (exists)
+ {
+ /* provide a baton to allow the reuse of open file handles between
+ iterations (saves 2/3 of OS level file operations). */
+ verify_walker_baton_t *baton
+ = apr_pcalloc(scratch_pool, sizeof(*baton));
+
+ baton->last_notified_revision = SVN_INVALID_REVNUM;
+ baton->notify_func = notify_func;
+ baton->notify_baton = notify_baton;
+
+ /* tell the user that we are now ready to do *something* */
+ if (notify_func)
+ notify_func(SVN_INVALID_REVNUM, notify_baton, scratch_pool);
+
+ /* Do not attempt to walk the rep-cache database if its file does
+ not exist, since doing so would create it --- which may confuse
+ the administrator. Don't take any lock. */
+ SVN_ERR(svn_fs_x__walk_rep_reference(fs, start, end,
+ verify_walker, baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the MD5 checksum of the data between offsets START and END
+ * in FILE matches the EXPECTED checksum. If there is a mismatch use the
+ * indedx NAME in the error message. Supports cancellation with CANCEL_FUNC
+ * and CANCEL_BATON. SCRATCH_POOL is for temporary allocations. */
+static svn_error_t *
+verify_index_checksum(apr_file_t *file,
+ const char *name,
+ apr_off_t start,
+ apr_off_t end,
+ svn_checksum_t *expected,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char buffer[SVN__STREAM_CHUNK_SIZE];
+ apr_off_t size = end - start;
+ svn_checksum_t *actual;
+ svn_checksum_ctx_t *context
+ = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
+
+ /* Calculate the index checksum. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &start, scratch_pool));
+ while (size > 0)
+ {
+ apr_size_t to_read = size > sizeof(buffer)
+ ? sizeof(buffer)
+ : (apr_size_t)size;
+ SVN_ERR(svn_io_file_read_full2(file, buffer, to_read, NULL, NULL,
+ scratch_pool));
+ SVN_ERR(svn_checksum_update(context, buffer, to_read));
+ size -= to_read;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ SVN_ERR(svn_checksum_final(&actual, context, scratch_pool));
+
+ /* Verify that it matches the expected checksum. */
+ if (!svn_checksum_match(expected, actual))
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, scratch_pool));
+ SVN_ERR(svn_checksum_mismatch_err(expected, actual, scratch_pool,
+ _("%s checksum mismatch in file %s"),
+ name, file_name));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify the MD5 checksums of the index data in the rev / pack file
+ * containing revision START in FS. If given, invoke CANCEL_FUNC with
+ * CANCEL_BATON at regular intervals. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static svn_error_t *
+verify_index_checksums(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__revision_file_t *rev_file;
+
+ /* Open the rev / pack file and read the footer */
+ SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, start,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_x__auto_read_footer(rev_file));
+
+ /* Verify the index contents against the checksum from the footer. */
+ SVN_ERR(verify_index_checksum(rev_file->file, "L2P index",
+ rev_file->l2p_offset, rev_file->p2l_offset,
+ rev_file->l2p_checksum,
+ cancel_func, cancel_baton, scratch_pool));
+ SVN_ERR(verify_index_checksum(rev_file->file, "P2L index",
+ rev_file->p2l_offset, rev_file->footer_offset,
+ rev_file->p2l_checksum,
+ cancel_func, cancel_baton, scratch_pool));
+
+ /* Done. */
+ SVN_ERR(svn_fs_x__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all log-to-phys index entries for revisions START to
+ * START + COUNT-1 in FS there is a consistent entry in the phys-to-log
+ * index. If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
+ * intervals. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+compare_l2p_to_p2l_index(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *max_ids;
+
+ /* common file access structure */
+ svn_fs_x__revision_file_t *rev_file;
+ SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, start, scratch_pool,
+ iterpool));
+
+ /* determine the range of items to check for each revision */
+ SVN_ERR(svn_fs_x__l2p_get_max_ids(&max_ids, fs, start, count, scratch_pool,
+ iterpool));
+
+ /* check all items in all revisions if the given range */
+ for (i = 0; i < max_ids->nelts; ++i)
+ {
+ apr_uint64_t k;
+ apr_uint64_t max_id = APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
+ svn_revnum_t revision = start + i;
+
+ for (k = 0; k < max_id; ++k)
+ {
+ apr_off_t offset;
+ apr_uint32_t sub_item;
+ svn_fs_x__id_t l2p_item;
+ svn_fs_x__id_t *p2l_item;
+
+ l2p_item.change_set = svn_fs_x__change_set_by_rev(revision);
+ l2p_item.number = k;
+
+ /* get L2P entry. Ignore unused entries. */
+ SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file,
+ &l2p_item, iterpool));
+ if (offset == -1)
+ continue;
+
+ /* find the corresponding P2L entry */
+ SVN_ERR(svn_fs_x__p2l_item_lookup(&p2l_item, fs, rev_file,
+ revision, offset, sub_item,
+ iterpool, iterpool));
+
+ if (p2l_item == NULL)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index entry not found for "
+ "PHYS o%s:s%ld returned by "
+ "l2p index for LOG r%ld:i%ld"),
+ apr_off_t_toa(scratch_pool, offset),
+ (long)sub_item, revision, (long)k);
+
+ if (!svn_fs_x__id_eq(&l2p_item, p2l_item))
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index info LOG r%ld:i%ld"
+ " does not match "
+ "l2p index for LOG r%ld:i%ld"),
+ svn_fs_x__get_revnum(p2l_item->change_set),
+ (long)p2l_item->number, revision,
+ (long)k);
+
+ svn_pool_clear(iterpool);
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_x__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all phys-to-log index entries for revisions START to
+ * START + COUNT-1 in FS there is a consistent entry in the log-to-phys
+ * index. If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
+ * intervals. Use SCRATCH_POOL for temporary allocations.
+ *
+ * Please note that we can only check on pack / rev file granularity and
+ * must only be called for a single rev / pack file.
+ */
+static svn_error_t *
+compare_p2l_to_l2p_index(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_pool_t *iterpool2 = svn_pool_create(scratch_pool);
+ apr_off_t max_offset;
+ apr_off_t offset = 0;
+
+ /* common file access structure */
+ svn_fs_x__revision_file_t *rev_file;
+ SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, start, scratch_pool,
+ iterpool));
+
+ /* get the size of the rev / pack file as covered by the P2L index */
+ SVN_ERR(svn_fs_x__p2l_get_max_offset(&max_offset, fs, rev_file, start,
+ scratch_pool));
+
+ /* for all offsets in the file, get the P2L index entries and check
+ them against the L2P index */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+ svn_fs_x__p2l_entry_t *last_entry;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* get all entries for the current block */
+ SVN_ERR(svn_fs_x__p2l_index_lookup(&entries, fs, rev_file, start,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+ if (entries->nelts == 0)
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION,
+ NULL,
+ _("p2l does not cover offset %s"
+ " for revision %ld"),
+ apr_off_t_toa(scratch_pool, offset), start);
+
+ /* process all entries (and later continue with the next block) */
+ last_entry
+ = &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_x__p2l_entry_t);
+ offset = last_entry->offset + last_entry->size;
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ apr_uint32_t k;
+ svn_fs_x__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t);
+
+ /* check all sub-items for consist entries in the L2P index */
+ for (k = 0; k < entry->item_count; ++k)
+ {
+ apr_off_t l2p_offset;
+ apr_uint32_t sub_item;
+ svn_fs_x__id_t *p2l_item = &entry->items[k];
+ svn_revnum_t revision
+ = svn_fs_x__get_revnum(p2l_item->change_set);
+
+ svn_pool_clear(iterpool2);
+ SVN_ERR(svn_fs_x__item_offset(&l2p_offset, &sub_item, fs,
+ rev_file, p2l_item, iterpool2));
+
+ if (sub_item != k || l2p_offset != entry->offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("l2p index entry PHYS o%s:s%ld "
+ "does not match p2l index value "
+ "LOG r%ld:i%ld for PHYS o%s:s%ld"),
+ apr_off_t_toa(scratch_pool,
+ l2p_offset),
+ (long)sub_item,
+ revision,
+ (long)p2l_item->number,
+ apr_off_t_toa(scratch_pool,
+ entry->offset),
+ (long)k);
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool2);
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_x__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Items smaller than this can be read at once into a buffer and directly
+ * be checksummed. Larger items require stream processing.
+ * Must be a multiple of 8. */
+#define STREAM_THRESHOLD 4096
+
+/* Verify that the next SIZE bytes read from FILE are NUL. SIZE must not
+ * exceed STREAM_THRESHOLD. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+expect_buffer_nul(apr_file_t *file,
+ apr_off_t size,
+ apr_pool_t *scratch_pool)
+{
+ union
+ {
+ unsigned char buffer[STREAM_THRESHOLD];
+ apr_uint64_t chunks[STREAM_THRESHOLD / sizeof(apr_uint64_t)];
+ } data;
+
+ apr_size_t i;
+ SVN_ERR_ASSERT(size <= STREAM_THRESHOLD);
+
+ /* read the whole data block; error out on failure */
+ data.chunks[(size - 1)/ sizeof(apr_uint64_t)] = 0;
+ SVN_ERR(svn_io_file_read_full2(file, data.buffer, size, NULL, NULL,
+ scratch_pool));
+
+ /* chunky check */
+ for (i = 0; i < size / sizeof(apr_uint64_t); ++i)
+ if (data.chunks[i] != 0)
+ break;
+
+ /* byte-wise check upon mismatch or at the end of the block */
+ for (i *= sizeof(apr_uint64_t); i < size; ++i)
+ if (data.buffer[i] != 0)
+ {
+ const char *file_name;
+ apr_off_t offset;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, scratch_pool));
+ SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
+ offset -= size - i;
+
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Empty section in file %s contains "
+ "non-NUL data at offset %s"),
+ file_name,
+ apr_off_t_toa(scratch_pool, offset));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the next SIZE bytes read from FILE are NUL.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+read_all_nul(apr_file_t *file,
+ apr_off_t size,
+ apr_pool_t *scratch_pool)
+{
+ for (; size >= STREAM_THRESHOLD; size -= STREAM_THRESHOLD)
+ SVN_ERR(expect_buffer_nul(file, STREAM_THRESHOLD, scratch_pool));
+
+ if (size)
+ SVN_ERR(expect_buffer_nul(file, size, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Compare the ACTUAL checksum with the one expected by ENTRY.
+ * Return an error in case of mismatch. Use the name of FILE
+ * in error message. Allocate temporary data in SCRATCH_POOL.
+ */
+static svn_error_t *
+expected_checksum(apr_file_t *file,
+ svn_fs_x__p2l_entry_t *entry,
+ apr_uint32_t actual,
+ apr_pool_t *scratch_pool)
+{
+ if (actual != entry->fnv1_checksum)
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, scratch_pool));
+ SVN_ERR(svn_io_file_name_get(&file_name, file, scratch_pool));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Checksum mismatch in item at offset %s of "
+ "length %s bytes in file %s"),
+ apr_off_t_toa(scratch_pool, entry->offset),
+ apr_off_t_toa(scratch_pool, entry->size),
+ file_name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the FNV checksum over the next ENTRY->SIZE bytes read
+ * from FILE will match ENTRY's expected checksum. SIZE must not
+ * exceed STREAM_THRESHOLD. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+expected_buffered_checksum(apr_file_t *file,
+ svn_fs_x__p2l_entry_t *entry,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char buffer[STREAM_THRESHOLD];
+ SVN_ERR_ASSERT(entry->size <= STREAM_THRESHOLD);
+
+ SVN_ERR(svn_io_file_read_full2(file, buffer, (apr_size_t)entry->size,
+ NULL, NULL, scratch_pool));
+ SVN_ERR(expected_checksum(file, entry,
+ svn__fnv1a_32x4(buffer, (apr_size_t)entry->size),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the FNV checksum over the next ENTRY->SIZE bytes read from
+ * FILE will match ENTRY's expected checksum.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+expected_streamed_checksum(apr_file_t *file,
+ svn_fs_x__p2l_entry_t *entry,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char buffer[STREAM_THRESHOLD];
+ svn_checksum_t *checksum;
+ svn_checksum_ctx_t *context
+ = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, scratch_pool);
+ apr_off_t size = entry->size;
+
+ while (size > 0)
+ {
+ apr_size_t to_read = size > sizeof(buffer)
+ ? sizeof(buffer)
+ : (apr_size_t)size;
+ SVN_ERR(svn_io_file_read_full2(file, buffer, to_read, NULL, NULL,
+ scratch_pool));
+ SVN_ERR(svn_checksum_update(context, buffer, to_read));
+ size -= to_read;
+ }
+
+ SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
+ SVN_ERR(expected_checksum(file, entry,
+ ntohl(*(const apr_uint32_t *)checksum->digest),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all phys-to-log index entries for revisions START to
+ * START + COUNT-1 in FS match the actual pack / rev file contents.
+ * If given, invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * Please note that we can only check on pack / rev file granularity and
+ * must only be called for a single rev / pack file.
+ */
+static svn_error_t *
+compare_p2l_to_rev(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_off_t max_offset;
+ apr_off_t offset = 0;
+ svn_fs_x__revision_file_t *rev_file;
+
+ /* open the pack / rev file that is covered by the p2l index */
+ SVN_ERR(svn_fs_x__open_pack_or_rev_file(&rev_file, fs, start, scratch_pool,
+ iterpool));
+
+ /* check file size vs. range covered by index */
+ SVN_ERR(svn_fs_x__auto_read_footer(rev_file));
+ SVN_ERR(svn_fs_x__p2l_get_max_offset(&max_offset, fs, rev_file, start,
+ scratch_pool));
+
+ if (rev_file->l2p_offset != max_offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT, NULL,
+ _("File size of %s for revision r%ld does "
+ "not match p2l index size of %s"),
+ apr_off_t_toa(scratch_pool,
+ rev_file->l2p_offset),
+ start,
+ apr_off_t_toa(scratch_pool,
+ max_offset));
+
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, ffd->block_size, NULL, 0,
+ scratch_pool));
+
+ /* for all offsets in the file, get the P2L index entries and check
+ them against the L2P index */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* get all entries for the current block */
+ SVN_ERR(svn_fs_x__p2l_index_lookup(&entries, fs, rev_file, start,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+
+ /* The above might have moved the file pointer.
+ * Ensure we actually start reading at OFFSET. */
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, ffd->block_size,
+ NULL, offset, iterpool));
+
+ /* process all entries (and later continue with the next block) */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_x__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_x__p2l_entry_t);
+
+ /* skip bits we previously checked */
+ if (i == 0 && entry->offset < offset)
+ continue;
+
+ /* skip zero-sized entries */
+ if (entry->size == 0)
+ continue;
+
+ /* p2l index must cover all rev / pack file offsets exactly once */
+ if (entry->offset != offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index entry for revision r%ld"
+ " is non-contiguous between offsets "
+ " %s and %s"),
+ start,
+ apr_off_t_toa(scratch_pool, offset),
+ apr_off_t_toa(scratch_pool,
+ entry->offset));
+
+ /* empty sections must contain NUL bytes only */
+ if (entry->type == SVN_FS_X__ITEM_TYPE_UNUSED)
+ {
+ /* skip filler entry at the end of the p2l index */
+ if (entry->offset != max_offset)
+ SVN_ERR(read_all_nul(rev_file->file, entry->size, iterpool));
+ }
+ else
+ {
+ if (entry->size < STREAM_THRESHOLD)
+ SVN_ERR(expected_buffered_checksum(rev_file->file, entry,
+ iterpool));
+ else
+ SVN_ERR(expected_streamed_checksum(rev_file->file, entry,
+ iterpool));
+ }
+
+ /* advance offset */
+ offset += entry->size;
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the revprops of the revisions START to END in FS can be
+ * accessed. Invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
+ *
+ * The values of START and END have already been auto-selected and
+ * verified.
+ */
+static svn_error_t *
+verify_revprops(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t revision;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (revision = start; revision < end; ++revision)
+ {
+ svn_string_t *date;
+ apr_time_t timetemp;
+
+ svn_pool_clear(iterpool);
+
+ /* Access the svn:date revprop.
+ * This implies parsing all revprops for that revision. */
+ SVN_ERR(svn_fs_x__revision_prop(&date, fs, revision,
+ SVN_PROP_REVISION_DATE,
+ iterpool, iterpool));
+
+ /* The time stamp is the only revprop that, if given, needs to
+ * have a valid content. */
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&timetemp, date->data, iterpool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that on-disk representation has not been tempered with (in a way
+ * that leaves the repository in a corrupted state). This compares log-to-
+ * phys with phys-to-log indexes, verifies the low-level checksums and
+ * checks that all revprops are available. The function signature is
+ * similar to svn_fs_x__verify.
+ *
+ * The values of START and END have already been auto-selected and
+ * verified.
+ */
+static svn_error_t *
+verify_metadata_consistency(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_revnum_t revision, next_revision;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (revision = start; revision <= end; revision = next_revision)
+ {
+ svn_revnum_t count = svn_fs_x__packed_base_rev(fs, revision);
+ svn_revnum_t pack_start = count;
+ svn_revnum_t pack_end = pack_start + svn_fs_x__pack_size(fs, revision);
+
+ svn_pool_clear(iterpool);
+
+ if (notify_func && (pack_start % ffd->max_files_per_dir == 0))
+ notify_func(pack_start, notify_baton, iterpool);
+
+ /* Check for external corruption to the indexes. */
+ err = verify_index_checksums(fs, pack_start, cancel_func,
+ cancel_baton, iterpool);
+
+ /* two-way index check */
+ if (!err)
+ err = compare_l2p_to_p2l_index(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+ if (!err)
+ err = compare_p2l_to_l2p_index(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+
+ /* verify in-index checksums and types vs. actual rev / pack files */
+ if (!err)
+ err = compare_p2l_to_rev(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+
+ /* ensure that revprops are available and accessible */
+ if (!err)
+ err = verify_revprops(fs, pack_start, pack_end,
+ cancel_func, cancel_baton, iterpool);
+
+ /* concurrent packing is one of the reasons why verification may fail.
+ Make sure, we operate on up-to-date information. */
+ if (err)
+ SVN_ERR(svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev,
+ fs, scratch_pool));
+
+ /* retry the whole shard if it got packed in the meantime */
+ if (err && count != svn_fs_x__pack_size(fs, revision))
+ {
+ svn_error_clear(err);
+
+ /* We could simply assign revision here but the code below is
+ more intuitive to maintainers. */
+ next_revision = svn_fs_x__packed_base_rev(fs, revision);
+ }
+ else
+ {
+ SVN_ERR(err);
+ next_revision = pack_end;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__verify(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
+
+ /* Input validation. */
+ if (! SVN_IS_VALID_REVNUM(start))
+ start = 0;
+ if (! SVN_IS_VALID_REVNUM(end))
+ end = youngest;
+ SVN_ERR(svn_fs_x__ensure_revision_exists(start, fs, scratch_pool));
+ SVN_ERR(svn_fs_x__ensure_revision_exists(end, fs, scratch_pool));
+
+ /* log/phys index consistency. We need to check them first to make
+ sure we can access the rev / pack files in format7. */
+ SVN_ERR(verify_metadata_consistency(fs, start, end,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* rep cache consistency */
+ SVN_ERR(verify_rep_cache(fs, start, end, notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}