/* 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; }