/* 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 "revprops.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(svn_fs_x__revision_file_t *file,
                      const char *name,
                      svn_fs_x__index_info_t *index_info,
                      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 = index_info->end - index_info->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_fs_x__rev_file_seek(file, NULL, index_info->start));
  while (size > 0)
    {
      apr_size_t to_read = size > sizeof(buffer)
                         ? sizeof(buffer)
                         : (apr_size_t)size;
      SVN_ERR(svn_fs_x__rev_file_read(file, buffer, to_read));
      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(index_info->checksum, actual))
    {
      const char *file_name;

      SVN_ERR(svn_fs_x__rev_file_name(&file_name, file, scratch_pool));
      SVN_ERR(svn_checksum_mismatch_err(index_info->checksum, 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;
  svn_fs_x__index_info_t l2p_index_info;
  svn_fs_x__index_info_t p2l_index_info;

  /* Open the rev / pack file and read the footer */
  SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));
  SVN_ERR(svn_fs_x__rev_file_l2p_info(&l2p_index_info, rev_file));
  SVN_ERR(svn_fs_x__rev_file_p2l_info(&p2l_index_info, rev_file));

  /* Verify the index contents against the checksum from the footer. */
  SVN_ERR(verify_index_checksum(rev_file, "L2P index", &l2p_index_info,
                                cancel_func, cancel_baton, scratch_pool));
  SVN_ERR(verify_index_checksum(rev_file, "P2L index", &p2l_index_info,
                                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__rev_file_init(&rev_file, fs, start, scratch_pool));

  /* 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__rev_file_init(&rev_file, fs, start, scratch_pool));

  /* 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(svn_fs_x__revision_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_fs_x__rev_file_read(file, data.buffer, size));

  /* 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_fs_x__rev_file_name(&file_name, file, scratch_pool));
        SVN_ERR(svn_fs_x__rev_file_offset(&offset, file));
        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(svn_fs_x__revision_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(svn_fs_x__revision_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_fs_x__rev_file_name(&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(svn_fs_x__revision_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_fs_x__rev_file_read(file, buffer, (apr_size_t)entry->size));
  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(svn_fs_x__revision_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_fs_x__rev_file_read(file, buffer, to_read));
      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;
  svn_fs_x__index_info_t l2p_index_info;

  /* open the pack / rev file that is covered by the p2l index */
  SVN_ERR(svn_fs_x__rev_file_init(&rev_file, fs, start, scratch_pool));

  /* check file size vs. range covered by index */
  SVN_ERR(svn_fs_x__rev_file_l2p_info(&l2p_index_info, rev_file));
  SVN_ERR(svn_fs_x__p2l_get_max_offset(&max_offset, fs, rev_file, start,
                                       scratch_pool));

  if (l2p_index_info.start != 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,
                                           l2p_index_info.start),
                             start,
                             apr_off_t_toa(scratch_pool,
                                           max_offset));

  SVN_ERR(svn_fs_x__rev_file_seek(rev_file, NULL, 0));

  /* 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_fs_x__rev_file_seek(rev_file, NULL, offset));

      /* 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, entry->size, iterpool));
            }
          else
            {
              if (entry->size < STREAM_THRESHOLD)
                SVN_ERR(expected_buffered_checksum(rev_file, entry,
                                                   iterpool));
              else
                SVN_ERR(expected_streamed_checksum(rev_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);

  /* Invalidate the revprop generation once.
   * Use the cache inside the loop to speed up packed revprop access. */
  svn_fs_x__invalidate_revprop_generation(fs);

  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, FALSE,
                                      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_error_t *err2
            = svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev,
                                              fs, scratch_pool);

          /* Be careful to not leak ERR. */
          if (err2)
            return svn_error_trace(svn_error_compose_create(err, err2));
        }

      /* 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)
{
  /* Input validation. */
  if (! SVN_IS_VALID_REVNUM(start))
    start = 0;
  if (! SVN_IS_VALID_REVNUM(end))
    {
      SVN_ERR(svn_fs_x__youngest_rev(&end, fs, scratch_pool));
    }

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