subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From stef...@apache.org
Subject svn commit: r1425472 [2/6] - /subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/
Date Sun, 23 Dec 2012 15:17:47 GMT
Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.c?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.c Sun Dec 23 15:17:47 2012
@@ -20,52 +20,23 @@
  * ====================================================================
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <apr_general.h>
-#include <apr_pools.h>
-#include <apr_file_io.h>
+#include "fs_fs.h"
+
 #include <apr_uuid.h>
-#include <apr_lib.h>
-#include <apr_md5.h>
-#include <apr_sha1.h>
-#include <apr_strings.h>
-#include <apr_thread_mutex.h>
-
-#include "svn_pools.h"
-#include "svn_fs.h"
-#include "svn_dirent_uri.h"
-#include "svn_path.h"
+
 #include "svn_hash.h"
 #include "svn_props.h"
-#include "svn_sorts.h"
-#include "svn_string.h"
 #include "svn_time.h"
-#include "svn_mergeinfo.h"
-#include "svn_config.h"
-#include "svn_ctype.h"
 
-#include "fs.h"
-#include "tree.h"
-#include "lock.h"
-#include "key-gen.h"
-#include "fs_fs.h"
+#include "cached_data.h"
 #include "id.h"
 #include "rep-cache.h"
-#include "temp_serializer.h"
-#include "low_level.h"
 #include "revprops.h"
+#include "transaction.h"
+#include "tree.h"
 #include "util.h"
 
-#include "private/svn_string_private.h"
 #include "private/svn_fs_util.h"
-#include "private/svn_subr_private.h"
-#include "private/svn_delta_private.h"
 #include "../libsvn_fs/fs-loader.h"
 
 #include "svn_private_config.h"
@@ -93,513 +64,9 @@
    Values < 1 disable deltification. */
 #define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
 
-/* Give writing processes 10 seconds to replace an existing revprop
-   file with a new one. After that time, we assume that the writing
-   process got aborted and that we have re-read revprops. */
-#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
-
-/* The following are names of atomics that will be used to communicate
- * revprop updates across all processes on this machine. */
-#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
-#define ATOMIC_REVPROP_TIMEOUT    "rev-prop-timeout"
-#define ATOMIC_REVPROP_NAMESPACE  "rev-prop-atomics"
-
-/* Following are defines that specify the textual elements of the
-   native filesystem directories and revision files. */
-
-/* Notes:
-
-To avoid opening and closing the rev-files all the time, it would
-probably be advantageous to keep each rev-file open for the
-lifetime of the transaction object.  I'll leave that as a later
-optimization for now.
-
-I didn't keep track of pool lifetimes at all in this code.  There
-are likely some errors because of that.
-
-*/
-
-/* The vtable associated with an open transaction object. */
-static txn_vtable_t txn_vtable = {
-  svn_fs_fs__commit_txn,
-  svn_fs_fs__abort_txn,
-  svn_fs_fs__txn_prop,
-  svn_fs_fs__txn_proplist,
-  svn_fs_fs__change_txn_prop,
-  svn_fs_fs__txn_root,
-  svn_fs_fs__change_txn_props
-};
-
-/* Declarations. */
-
-static svn_error_t *
-get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
-
-/* Functions for working with shared transaction data. */
-
-/* Return the transaction object for transaction TXN_ID from the
-   transaction list of filesystem FS (which must already be locked via the
-   txn_list_lock mutex).  If the transaction does not exist in the list,
-   then create a new transaction object and return it (if CREATE_NEW is
-   true) or return NULL (otherwise). */
-static fs_fs_shared_txn_data_t *
-get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  fs_fs_shared_data_t *ffsd = ffd->shared;
-  fs_fs_shared_txn_data_t *txn;
-
-  for (txn = ffsd->txns; txn; txn = txn->next)
-    if (strcmp(txn->txn_id, txn_id) == 0)
-      break;
-
-  if (txn || !create_new)
-    return txn;
-
-  /* Use the transaction object from the (single-object) freelist,
-     if one is available, or otherwise create a new object. */
-  if (ffsd->free_txn)
-    {
-      txn = ffsd->free_txn;
-      ffsd->free_txn = NULL;
-    }
-  else
-    {
-      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
-      txn = apr_palloc(subpool, sizeof(*txn));
-      txn->pool = subpool;
-    }
-
-  assert(strlen(txn_id) < sizeof(txn->txn_id));
-  apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
-  txn->being_written = FALSE;
-
-  /* Link this transaction into the head of the list.  We will typically
-     be dealing with only one active transaction at a time, so it makes
-     sense for searches through the transaction list to look at the
-     newest transactions first.  */
-  txn->next = ffsd->txns;
-  ffsd->txns = txn;
-
-  return txn;
-}
-
-/* Free the transaction object for transaction TXN_ID, and remove it
-   from the transaction list of filesystem FS (which must already be
-   locked via the txn_list_lock mutex).  Do nothing if the transaction
-   does not exist. */
-static void
-free_shared_txn(svn_fs_t *fs, const char *txn_id)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  fs_fs_shared_data_t *ffsd = ffd->shared;
-  fs_fs_shared_txn_data_t *txn, *prev = NULL;
-
-  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
-    if (strcmp(txn->txn_id, txn_id) == 0)
-      break;
-
-  if (!txn)
-    return;
-
-  if (prev)
-    prev->next = txn->next;
-  else
-    ffsd->txns = txn->next;
-
-  /* As we typically will be dealing with one transaction after another,
-     we will maintain a single-object free list so that we can hopefully
-     keep reusing the same transaction object. */
-  if (!ffsd->free_txn)
-    ffsd->free_txn = txn;
-  else
-    svn_pool_destroy(txn->pool);
-}
-
-
-/* Obtain a lock on the transaction list of filesystem FS, call BODY
-   with FS, BATON, and POOL, and then unlock the transaction list.
-   Return what BODY returned. */
-static svn_error_t *
-with_txnlist_lock(svn_fs_t *fs,
-                  svn_error_t *(*body)(svn_fs_t *fs,
-                                       const void *baton,
-                                       apr_pool_t *pool),
-                  const void *baton,
-                  apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  fs_fs_shared_data_t *ffsd = ffd->shared;
-
-  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
-                       body(fs, baton, pool));
-
-  return SVN_NO_ERROR;
-}
-
-
-/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
-static svn_error_t *
-get_lock_on_filesystem(const char *lock_filename,
-                       apr_pool_t *pool)
-{
-  svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
-
-  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
-    {
-      /* No lock file?  No big deal; these are just empty files
-         anyway.  Create it and try again. */
-      svn_error_clear(err);
-      err = NULL;
-
-      SVN_ERR(svn_io_file_create(lock_filename, "", pool));
-      SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
-    }
-
-  return svn_error_trace(err);
-}
-
-/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
-   When registered with the pool holding the lock on the lock file,
-   this makes sure the flag gets reset just before we release the lock. */
-static apr_status_t
-reset_lock_flag(void *baton_void)
-{
-  fs_fs_data_t *ffd = baton_void;
-  ffd->has_write_lock = FALSE;
-  return APR_SUCCESS;
-}
-
-/* Obtain a write lock on the file LOCK_FILENAME (protecting with
-   LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
-   BATON and that subpool, destroy the subpool (releasing the write
-   lock) and return what BODY returned.  If IS_GLOBAL_LOCK is set,
-   set the HAS_WRITE_LOCK flag while we keep the write lock. */
-static svn_error_t *
-with_some_lock_file(svn_fs_t *fs,
-                    svn_error_t *(*body)(void *baton,
-                                         apr_pool_t *pool),
-                    void *baton,
-                    const char *lock_filename,
-                    svn_boolean_t is_global_lock,
-                    apr_pool_t *pool)
-{
-  apr_pool_t *subpool = svn_pool_create(pool);
-  svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
-
-  if (!err)
-    {
-      fs_fs_data_t *ffd = fs->fsap_data;
-
-      if (is_global_lock)
-        {
-          /* set the "got the lock" flag and register reset function */
-          apr_pool_cleanup_register(subpool,
-                                    ffd,
-                                    reset_lock_flag,
-                                    apr_pool_cleanup_null);
-          ffd->has_write_lock = TRUE;
-        }
-
-      /* nobody else will modify the repo state
-         => read HEAD & pack info once */
-      if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
-        SVN_ERR(update_min_unpacked_rev(fs, pool));
-      SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
-                           pool));
-      err = body(baton, subpool);
-    }
-
-  svn_pool_destroy(subpool);
-
-  return svn_error_trace(err);
-}
-
-svn_error_t *
-svn_fs_fs__with_write_lock(svn_fs_t *fs,
-                           svn_error_t *(*body)(void *baton,
-                                                apr_pool_t *pool),
-                           void *baton,
-                           apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  fs_fs_shared_data_t *ffsd = ffd->shared;
-
-  SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
-                       with_some_lock_file(fs, body, baton,
-                                           path_lock(fs, pool),
-                                           TRUE,
-                                           pool));
-
-  return SVN_NO_ERROR;
-}
-
-/* Run BODY (with BATON and POOL) while the txn-current file
-   of FS is locked. */
-static svn_error_t *
-with_txn_current_lock(svn_fs_t *fs,
-                      svn_error_t *(*body)(void *baton,
-                                           apr_pool_t *pool),
-                      void *baton,
-                      apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  fs_fs_shared_data_t *ffsd = ffd->shared;
-
-  SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
-                       with_some_lock_file(fs, body, baton,
-                                           path_txn_current_lock(fs, pool),
-                                           FALSE,
-                                           pool));
-
-  return SVN_NO_ERROR;
-}
-
-/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
-   which see. */
-struct unlock_proto_rev_baton
-{
-  const char *txn_id;
-  void *lockcookie;
-};
-
-/* Callback used in the implementation of unlock_proto_rev(). */
-static svn_error_t *
-unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
-  const struct unlock_proto_rev_baton *b = baton;
-  const char *txn_id = b->txn_id;
-  apr_file_t *lockfile = b->lockcookie;
-  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
-  apr_status_t apr_err;
-
-  if (!txn)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Can't unlock unknown transaction '%s'"),
-                             txn_id);
-  if (!txn->being_written)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Can't unlock nonlocked transaction '%s'"),
-                             txn_id);
-
-  apr_err = apr_file_unlock(lockfile);
-  if (apr_err)
-    return svn_error_wrap_apr
-      (apr_err,
-       _("Can't unlock prototype revision lockfile for transaction '%s'"),
-       txn_id);
-  apr_err = apr_file_close(lockfile);
-  if (apr_err)
-    return svn_error_wrap_apr
-      (apr_err,
-       _("Can't close prototype revision lockfile for transaction '%s'"),
-       txn_id);
-
-  txn->being_written = FALSE;
-
-  return SVN_NO_ERROR;
-}
-
-/* Unlock the prototype revision file for transaction TXN_ID in filesystem
-   FS using cookie LOCKCOOKIE.  The original prototype revision file must
-   have been closed _before_ calling this function.
-
-   Perform temporary allocations in POOL. */
-static svn_error_t *
-unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
-                 apr_pool_t *pool)
-{
-  struct unlock_proto_rev_baton b;
-
-  b.txn_id = txn_id;
-  b.lockcookie = lockcookie;
-  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
-}
-
-/* Same as unlock_proto_rev(), but requires that the transaction list
-   lock is already held. */
-static svn_error_t *
-unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
-                             void *lockcookie,
-                             apr_pool_t *pool)
-{
-  struct unlock_proto_rev_baton b;
-
-  b.txn_id = txn_id;
-  b.lockcookie = lockcookie;
-  return unlock_proto_rev_body(fs, &b, pool);
-}
-
-/* A structure used by get_writable_proto_rev() and
-   get_writable_proto_rev_body(), which see. */
-struct get_writable_proto_rev_baton
-{
-  apr_file_t **file;
-  void **lockcookie;
-  const char *txn_id;
-};
-
-/* Callback used in the implementation of get_writable_proto_rev(). */
-static svn_error_t *
-get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
-  const struct get_writable_proto_rev_baton *b = baton;
-  apr_file_t **file = b->file;
-  void **lockcookie = b->lockcookie;
-  const char *txn_id = b->txn_id;
-  svn_error_t *err;
-  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
-
-  /* First, ensure that no thread in this process (including this one)
-     is currently writing to this transaction's proto-rev file. */
-  if (txn->being_written)
-    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
-                             _("Cannot write to the prototype revision file "
-                               "of transaction '%s' because a previous "
-                               "representation is currently being written by "
-                               "this process"),
-                             txn_id);
-
-
-  /* We know that no thread in this process is writing to the proto-rev
-     file, and by extension, that no thread in this process is holding a
-     lock on the prototype revision lock file.  It is therefore safe
-     for us to attempt to lock this file, to see if any other process
-     is holding a lock. */
-
-  {
-    apr_file_t *lockfile;
-    apr_status_t apr_err;
-    const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
-
-    /* Open the proto-rev lockfile, creating it if necessary, as it may
-       not exist if the transaction dates from before the lockfiles were
-       introduced.
-
-       ### We'd also like to use something like svn_io_file_lock2(), but
-           that forces us to create a subpool just to be able to unlock
-           the file, which seems a waste. */
-    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
-                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
-
-    apr_err = apr_file_lock(lockfile,
-                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
-    if (apr_err)
-      {
-        svn_error_clear(svn_io_file_close(lockfile, pool));
-
-        if (APR_STATUS_IS_EAGAIN(apr_err))
-          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
-                                   _("Cannot write to the prototype revision "
-                                     "file of transaction '%s' because a "
-                                     "previous representation is currently "
-                                     "being written by another process"),
-                                   txn_id);
-
-        return svn_error_wrap_apr(apr_err,
-                                  _("Can't get exclusive lock on file '%s'"),
-                                  svn_dirent_local_style(lockfile_path, pool));
-      }
-
-    *lockcookie = lockfile;
-  }
-
-  /* We've successfully locked the transaction; mark it as such. */
-  txn->being_written = TRUE;
-
-
-  /* Now open the prototype revision file and seek to the end. */
-  err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
-                         APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
-
-  /* You might expect that we could dispense with the following seek
-     and achieve the same thing by opening the file using APR_APPEND.
-     Unfortunately, APR's buffered file implementation unconditionally
-     places its initial file pointer at the start of the file (even for
-     files opened with APR_APPEND), so we need this seek to reconcile
-     the APR file pointer to the OS file pointer (since we need to be
-     able to read the current file position later). */
-  if (!err)
-    {
-      apr_off_t offset = 0;
-      err = svn_io_file_seek(*file, APR_END, &offset, pool);
-    }
-
-  if (err)
-    {
-      err = svn_error_compose_create(
-              err,
-              unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
-
-      *lockcookie = NULL;
-    }
-
-  return svn_error_trace(err);
-}
-
-/* Get a handle to the prototype revision file for transaction TXN_ID in
-   filesystem FS, and lock it for writing.  Return FILE, a file handle
-   positioned at the end of the file, and LOCKCOOKIE, a cookie that
-   should be passed to unlock_proto_rev() to unlock the file once FILE
-   has been closed.
-
-   If the prototype revision file is already locked, return error
-   SVN_ERR_FS_REP_BEING_WRITTEN.
-
-   Perform all allocations in POOL. */
-static svn_error_t *
-get_writable_proto_rev(apr_file_t **file,
-                       void **lockcookie,
-                       svn_fs_t *fs, const char *txn_id,
-                       apr_pool_t *pool)
-{
-  struct get_writable_proto_rev_baton b;
-
-  b.file = file;
-  b.lockcookie = lockcookie;
-  b.txn_id = txn_id;
-
-  return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
-}
-
-/* Callback used in the implementation of purge_shared_txn(). */
-static svn_error_t *
-purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
-  const char *txn_id = baton;
-
-  free_shared_txn(fs, txn_id);
-  svn_fs_fs__reset_txn_caches(fs);
-
-  return SVN_NO_ERROR;
-}
-
-/* Purge the shared data for transaction TXN_ID in filesystem FS.
-   Perform all allocations in POOL. */
-static svn_error_t *
-purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
-}
 
 
 
-/* Fetch the current offset of FILE into *OFFSET_P. */
-static svn_error_t *
-get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
-{
-  apr_off_t offset;
-
-  /* Note that, for buffered files, one (possibly surprising) side-effect
-     of this call is to flush any unwritten data to disk. */
-  offset = 0;
-  SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
-  *offset_p = offset;
-
-  return SVN_NO_ERROR;
-}
-
-
 /* Check that BUF, a nul-terminated buffer of text from format file PATH,
    contains only digits at OFFSET and beyond, raising an error if not.
 
@@ -611,14 +78,6 @@ check_format_file_buffer_numeric(const c
   return check_file_buffer_numeric(buf, offset, path, "Format", pool);
 }
 
-/* Read the format number and maximum number of files per directory
-   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
-   respectively.
-
-   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
-   will be set to zero if a linear scheme should be used.
-
-   Use POOL for temporary allocation. */
 static svn_error_t *
 read_format(int *pformat, int *max_files_per_dir,
             const char *path, apr_pool_t *pool)
@@ -702,45 +161,34 @@ read_format(int *pformat, int *max_files
    previously existing file.
 
    Use POOL for temporary allocation. */
-static svn_error_t *
-write_format(const char *path, int format, int max_files_per_dir,
-             svn_boolean_t overwrite, apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__write_format(svn_fs_t *fs, apr_pool_t *pool)
 {
   svn_stringbuf_t *sb;
+  const char *path = path_format(fs, pool);
+  fs_fs_data_t *ffd = fs->fsap_data;
+  const char *path_tmp;
 
-  SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
+  SVN_ERR_ASSERT(1 <= ffd->format && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
 
-  sb = svn_stringbuf_createf(pool, "%d\n", format);
+  sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
 
-  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+  if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
     {
-      if (max_files_per_dir)
+      if (ffd->max_files_per_dir)
         svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
-                                                  max_files_per_dir));
+                                                  ffd->max_files_per_dir));
       else
         svn_stringbuf_appendcstr(sb, "layout linear\n");
     }
 
-  /* svn_io_write_version_file() does a load of magic to allow it to
-     replace version files that already exist.  We only need to do
-     that when we're allowed to overwrite an existing file. */
-  if (! overwrite)
-    {
-      /* Create the file */
-      SVN_ERR(svn_io_file_create(path, sb->data, pool));
-    }
-  else
-    {
-      const char *path_tmp;
-
-      SVN_ERR(svn_io_write_unique(&path_tmp,
-                                  svn_dirent_dirname(path, pool),
-                                  sb->data, sb->len,
-                                  svn_io_file_del_none, pool));
+  SVN_ERR(svn_io_write_unique(&path_tmp,
+                              svn_dirent_dirname(path, pool),
+                              sb->data, sb->len,
+                              svn_io_file_del_none, pool));
 
-      /* rename the temp file as the real destination */
-      SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
-    }
+  /* rename the temp file as the real destination */
+  SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
 
   /* And set the perms to make it read only */
   return svn_io_set_file_read_only(path, FALSE, pool);
@@ -778,6 +226,24 @@ svn_fs_fs__fs_supports_mergeinfo(svn_fs_
   return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
 }
 
+/* Find the youngest revision in a repository at path FS_PATH and
+   return it in *YOUNGEST_P.  Perform temporary allocations in
+   POOL. */
+static svn_error_t *
+get_youngest(svn_revnum_t *youngest_p,
+             const char *fs_path,
+             apr_pool_t *pool)
+{
+  svn_stringbuf_t *buf;
+  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
+                       pool));
+
+  *youngest_p = SVN_STR_TO_REV(buf->data);
+
+  return SVN_NO_ERROR;
+}
+
+
 /* Read the configuration information of the file system at FS_PATH
  * and set the respective values in FFD.  Use POOL for allocations.
  */
@@ -1047,6 +513,7 @@ static svn_error_t *
 upgrade_body(void *baton, apr_pool_t *pool)
 {
   svn_fs_t *fs = baton;
+  fs_fs_data_t *ffd = fs->fsap_data;
   int format, max_files_per_dir;
   const char *format_path = path_format(fs, pool);
   svn_node_kind_t kind;
@@ -1108,8 +575,10 @@ upgrade_body(void *baton, apr_pool_t *po
     SVN_ERR(upgrade_pack_revprops(fs, pool));
 
   /* Bump the format file. */
-  return write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, max_files_per_dir,
-                      TRUE, pool);
+
+  ffd->format = SVN_FS_FS__FORMAT_NUMBER;
+  ffd->max_files_per_dir = max_files_per_dir;
+  return svn_fs_fs__write_format(fs, pool);
 }
 
 
@@ -1120,24 +589,6 @@ svn_fs_fs__upgrade(svn_fs_t *fs, apr_poo
 }
 
 
-/* Find the youngest revision in a repository at path FS_PATH and
-   return it in *YOUNGEST_P.  Perform temporary allocations in
-   POOL. */
-static svn_error_t *
-get_youngest(svn_revnum_t *youngest_p,
-             const char *fs_path,
-             apr_pool_t *pool)
-{
-  svn_stringbuf_t *buf;
-  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
-                       pool));
-
-  *youngest_p = SVN_STR_TO_REV(buf->data);
-
-  return SVN_NO_ERROR;
-}
-
-
 svn_error_t *
 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
                         svn_fs_t *fs,
@@ -1211,11 +662,11 @@ svn_fs_fs__ensure_revision_exists(svn_re
          since the rev file was opened.
 
    Use POOL for allocations. */
-static svn_error_t *
-open_pack_or_rev_file(apr_file_t **file,
-                      svn_fs_t *fs,
-                      svn_revnum_t rev,
-                      apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file(apr_file_t **file,
+                                 svn_fs_t *fs,
+                                 svn_revnum_t rev,
+                                 apr_pool_t *pool)
 {
   fs_fs_data_t *ffd = fs->fsap_data;
   svn_error_t *err;
@@ -1266,4517 +717,105 @@ open_pack_or_rev_file(apr_file_t **file,
   return svn_error_trace(err);
 }
 
-/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
-   Use POOL for temporary allocations. */
-static svn_error_t *
-get_packed_offset(apr_off_t *rev_offset,
-                  svn_fs_t *fs,
-                  svn_revnum_t rev,
-                  apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  svn_stream_t *manifest_stream;
-  svn_boolean_t is_cached;
-  svn_revnum_t shard;
-  apr_int64_t shard_pos;
-  apr_array_header_t *manifest;
-  apr_pool_t *iterpool;
-
-  shard = rev / ffd->max_files_per_dir;
-
-  /* position of the shard within the manifest */
-  shard_pos = rev % ffd->max_files_per_dir;
-
-  /* fetch exactly that element into *rev_offset, if the manifest is found
-     in the cache */
-  SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
-                                 ffd->packed_offset_cache, &shard,
-                                 svn_fs_fs__get_sharded_offset, &shard_pos,
-                                 pool));
-
-  if (is_cached)
-      return SVN_NO_ERROR;
-
-  /* Open the manifest file. */
-  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
-                                   path_rev_packed(fs, rev, PATH_MANIFEST,
-                                                   pool),
-                                   pool, pool));
-
-  /* While we're here, let's just read the entire manifest file into an array,
-     so we can cache the entire thing. */
-  iterpool = svn_pool_create(pool);
-  manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
-  while (1)
-    {
-      svn_boolean_t eof;
-      apr_int64_t val;
 
-      svn_pool_clear(iterpool);
-      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
-      if (eof)
-        break;
+svn_error_t *
+svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
+                             svn_fs_t *fs,
+                             svn_revnum_t rev,
+                             apr_pool_t *pool)
+{
+  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
 
-      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
-    }
-  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
 
-  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
-                              apr_off_t);
 
-  /* Close up shop and cache the array. */
-  SVN_ERR(svn_stream_close(manifest_stream));
-  return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
-}
-
-/* Open the revision file for revision REV in filesystem FS and store
-   the newly opened file in FILE.  Seek to location OFFSET before
-   returning.  Perform temporary allocations in POOL. */
-static svn_error_t *
-open_and_seek_revision(apr_file_t **file,
-                       svn_fs_t *fs,
-                       svn_revnum_t rev,
-                       apr_off_t offset,
-                       apr_pool_t *pool)
-{
-  apr_file_t *rev_file;
-
-  SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
-  SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
-
-  if (is_packed_rev(fs, rev))
-    {
-      apr_off_t rev_offset;
-
-      SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
-      offset += rev_offset;
-    }
-
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
-  *file = rev_file;
-
-  return SVN_NO_ERROR;
-}
-
-/* Open the representation for a node-revision in transaction TXN_ID
-   in filesystem FS and store the newly opened file in FILE.  Seek to
-   location OFFSET before returning.  Perform temporary allocations in
-   POOL.  Only appropriate for file contents, nor props or directory
-   contents. */
-static svn_error_t *
-open_and_seek_transaction(apr_file_t **file,
-                          svn_fs_t *fs,
-                          const char *txn_id,
-                          representation_t *rep,
-                          apr_pool_t *pool)
-{
-  apr_file_t *rev_file;
-  apr_off_t offset;
-
-  SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
-                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
-  offset = rep->offset;
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
-  *file = rev_file;
-
-  return SVN_NO_ERROR;
-}
-
-/* Given a node-id ID, and a representation REP in filesystem FS, open
-   the correct file and seek to the correction location.  Store this
-   file in *FILE_P.  Perform any allocations in POOL. */
-static svn_error_t *
-open_and_seek_representation(apr_file_t **file_p,
-                             svn_fs_t *fs,
-                             representation_t *rep,
-                             apr_pool_t *pool)
-{
-  if (! rep->txn_id)
-    return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
-                                  pool);
-  else
-    return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
-}
-
-static svn_error_t *
-err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
-{
-  svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
-  return svn_error_createf
-    (SVN_ERR_FS_ID_NOT_FOUND, 0,
-     _("Reference to non-existent node '%s' in filesystem '%s'"),
-     id_str->data, fs->path);
-}
-
-/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
- * caching has been enabled and the data can be found, IS_CACHED will
- * be set to TRUE. The noderev will be allocated from POOL.
- *
- * Non-permanent ids (e.g. ids within a TXN) will not be cached.
- */
-static svn_error_t *
-get_cached_node_revision_body(node_revision_t **noderev_p,
-                              svn_fs_t *fs,
-                              const svn_fs_id_t *id,
-                              svn_boolean_t *is_cached,
-                              apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
-    {
-      *is_cached = FALSE;
-    }
-  else
-    {
-      pair_cache_key_t key;
-
-      key.revision = svn_fs_fs__id_rev(id);
-      key.second = svn_fs_fs__id_offset(id);
-      SVN_ERR(svn_cache__get((void **) noderev_p,
-                            is_cached,
-                            ffd->node_revision_cache,
-                            &key,
-                            pool));
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* If noderev caching has been enabled, store the NODEREV_P for the given ID
- * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
- *
- * Non-permanent ids (e.g. ids within a TXN) will not be cached.
- */
-static svn_error_t *
-set_cached_node_revision_body(node_revision_t *noderev_p,
-                              svn_fs_t *fs,
-                              const svn_fs_id_t *id,
-                              apr_pool_t *scratch_pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
-    {
-      pair_cache_key_t key;
-
-      key.revision = svn_fs_fs__id_rev(id);
-      key.second = svn_fs_fs__id_offset(id);
-      return svn_cache__set(ffd->node_revision_cache,
-                            &key,
-                            noderev_p,
-                            scratch_pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Get the node-revision for the node ID in FS.
-   Set *NODEREV_P to the new node-revision structure, allocated in POOL.
-   See svn_fs_fs__get_node_revision, which wraps this and adds another
-   error. */
-static svn_error_t *
-get_node_revision_body(node_revision_t **noderev_p,
-                       svn_fs_t *fs,
-                       const svn_fs_id_t *id,
-                       apr_pool_t *pool)
-{
-  apr_file_t *revision_file;
-  svn_error_t *err;
-  svn_boolean_t is_cached = FALSE;
-
-  /* First, try a cache lookup. If that succeeds, we are done here. */
-  SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
-  if (is_cached)
-    return SVN_NO_ERROR;
-
-  if (svn_fs_fs__id_txn_id(id))
-    {
-      /* This is a transaction node-rev. */
-      err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
-                             APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
-    }
-  else
-    {
-      /* This is a revision node-rev. */
-      err = open_and_seek_revision(&revision_file, fs,
-                                   svn_fs_fs__id_rev(id),
-                                   svn_fs_fs__id_offset(id),
-                                   pool);
-    }
-
-  if (err)
-    {
-      if (APR_STATUS_IS_ENOENT(err->apr_err))
-        {
-          svn_error_clear(err);
-          return svn_error_trace(err_dangling_id(fs, id));
-        }
-
-      return svn_error_trace(err);
-    }
-
-  SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
-                                  svn_stream_from_aprfile2(revision_file, FALSE,
-                                                           pool),
-                                  pool));
-  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
-  if (svn_fs_fs__id_txn_id(id) == NULL)
-    (*noderev_p)->is_fresh_txn_root = FALSE;
-
-
-  /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
-  return set_cached_node_revision_body(*noderev_p, fs, id, pool);
-}
-
-svn_error_t *
-svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
-                             svn_fs_t *fs,
-                             const svn_fs_id_t *id,
-                             apr_pool_t *pool)
-{
-  svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
-  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
-    {
-      svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
-                               "Corrupt node-revision '%s'",
-                               id_string->data);
-    }
-  return svn_error_trace(err);
-}
-
-
-svn_error_t *
-svn_fs_fs__put_node_revision(svn_fs_t *fs,
-                             const svn_fs_id_t *id,
-                             node_revision_t *noderev,
-                             svn_boolean_t fresh_txn_root,
-                             apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  apr_file_t *noderev_file;
-  const char *txn_id = svn_fs_fs__id_txn_id(id);
-
-  noderev->is_fresh_txn_root = fresh_txn_root;
-
-  if (! txn_id)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Attempted to write to non-transaction '%s'"),
-                             svn_fs_fs__id_unparse(id, pool)->data);
-
-  SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
-                           APR_WRITE | APR_CREATE | APR_TRUNCATE
-                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
-  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
-                                                            pool),
-                                   noderev, ffd->format,
-                                   svn_fs_fs__fs_supports_mergeinfo(fs),
-                                   pool));
-
-  SVN_ERR(svn_io_file_close(noderev_file, pool));
-
-  return SVN_NO_ERROR;
-}
-
-/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
- * file in the respective transaction, if rep sharing has been enabled etc.
- * Use POOL for temporary allocations.
- */
-static svn_error_t *
-store_sha1_rep_mapping(svn_fs_t *fs,
-                       node_revision_t *noderev,
-                       apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  /* if rep sharing has been enabled and the noderev has a data rep and
-   * its SHA-1 is known, store the rep struct under its SHA1. */
-  if (   ffd->rep_sharing_allowed
-      && noderev->data_rep
-      && noderev->data_rep->sha1_checksum)
-    {
-      apr_file_t *rep_file;
-      const char *file_name = path_txn_sha1(fs,
-                                            svn_fs_fs__id_txn_id(noderev->id),
-                                            noderev->data_rep->sha1_checksum,
-                                            pool);
-      const char *rep_string = representation_string(noderev->data_rep,
-                                                     ffd->format,
-                                                     (noderev->kind
-                                                      == svn_node_dir),
-                                                     FALSE,
-                                                     pool);
-      SVN_ERR(svn_io_file_open(&rep_file, file_name,
-                               APR_WRITE | APR_CREATE | APR_TRUNCATE
-                               | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
-      SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
-                                     strlen(rep_string), NULL, pool));
-
-      SVN_ERR(svn_io_file_close(rep_file, pool));
-    }
-
-  return SVN_NO_ERROR;
-}
-
-
-/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
-   of the header located at OFFSET and store it in *ID_P.  Allocate
-   temporary variables from POOL. */
-static svn_error_t *
-get_fs_id_at_offset(svn_fs_id_t **id_p,
-                    apr_file_t *rev_file,
-                    svn_fs_t *fs,
-                    svn_revnum_t rev,
-                    apr_off_t offset,
-                    apr_pool_t *pool)
-{
-  svn_fs_id_t *id;
-  apr_hash_t *headers;
-  const char *node_id_str;
-
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
-  SVN_ERR(read_header_block(&headers,
-                            svn_stream_from_aprfile2(rev_file, TRUE, pool),
-                            pool));
-
-  /* In error messages, the offset is relative to the pack file,
-     not to the rev file. */
-
-  node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
-
-  if (node_id_str == NULL)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Missing node-id in node-rev at r%ld "
-                             "(offset %s)"),
-                             rev,
-                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
-
-  id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
-
-  if (id == NULL)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Corrupt node-id '%s' in node-rev at r%ld "
-                               "(offset %s)"),
-                             node_id_str, rev,
-                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
-
-  *id_p = id;
-
-  /* ### assert that the txn_id is REV/OFFSET ? */
-
-  return SVN_NO_ERROR;
-}
-
-
-/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
-   specifies the offset to the root node-id and to the changed path
-   information.  Store the root node offset in *ROOT_OFFSET and the
-   changed path offset in *CHANGES_OFFSET.  If either of these
-   pointers is NULL, do nothing with it.
-
-   If PACKED is true, REV_FILE should be a packed shard file.
-   ### There is currently no such parameter.  This function assumes that
-       is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
-       file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
-       refreshed since REV_FILE was opened if there is a possibility that
-       revision REV may have become packed since then.
-       TODO: Take an IS_PACKED parameter instead, in order to remove this
-       requirement.
-
-   Allocate temporary variables from POOL. */
-static svn_error_t *
-get_root_changes_offset(apr_off_t *root_offset,
-                        apr_off_t *changes_offset,
-                        apr_file_t *rev_file,
-                        svn_fs_t *fs,
-                        svn_revnum_t rev,
-                        apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  apr_off_t offset;
-  apr_off_t rev_offset;
-  char buf[64];
-  int i, num_bytes;
-  const char *str;
-  apr_size_t len;
-  apr_seek_where_t seek_relative;
-
-  /* Determine where to seek to in the file.
-
-     If we've got a pack file, we want to seek to the end of the desired
-     revision.  But we don't track that, so we seek to the beginning of the
-     next revision.
-
-     Unless the next revision is in a different file, in which case, we can
-     just seek to the end of the pack file -- just like we do in the
-     non-packed case. */
-  if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
-    {
-      SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
-      seek_relative = APR_SET;
-    }
-  else
-    {
-      seek_relative = APR_END;
-      offset = 0;
-    }
-
-  /* Offset of the revision from the start of the pack file, if applicable. */
-  if (is_packed_rev(fs, rev))
-    SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
-  else
-    rev_offset = 0;
-
-  /* We will assume that the last line containing the two offsets
-     will never be longer than 64 characters. */
-  SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
-
-  offset -= sizeof(buf);
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
-  /* Read in this last block, from which we will identify the last line. */
-  len = sizeof(buf);
-  SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
-
-  /* This cast should be safe since the maximum amount read, 64, will
-     never be bigger than the size of an int. */
-  num_bytes = (int) len;
-
-  /* The last byte should be a newline. */
-  if (buf[num_bytes - 1] != '\n')
-    {
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                               _("Revision file (r%ld) lacks trailing newline"),
-                               rev);
-    }
-
-  /* Look for the next previous newline. */
-  for (i = num_bytes - 2; i >= 0; i--)
-    {
-      if (buf[i] == '\n')
-        break;
-    }
-
-  if (i < 0)
-    {
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                               _("Final line in revision file (r%ld) longer "
-                                 "than 64 characters"),
-                               rev);
-    }
-
-  i++;
-  str = &buf[i];
-
-  /* find the next space */
-  for ( ; i < (num_bytes - 2) ; i++)
-    if (buf[i] == ' ')
-      break;
-
-  if (i == (num_bytes - 2))
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Final line in revision file r%ld missing space"),
-                             rev);
-
-  if (root_offset)
-    {
-      apr_int64_t val;
-
-      buf[i] = '\0';
-      SVN_ERR(svn_cstring_atoi64(&val, str));
-      *root_offset = rev_offset + (apr_off_t)val;
-    }
-
-  i++;
-  str = &buf[i];
-
-  /* find the next newline */
-  for ( ; i < num_bytes; i++)
-    if (buf[i] == '\n')
-      break;
-
-  if (changes_offset)
-    {
-      apr_int64_t val;
-
-      buf[i] = '\0';
-      SVN_ERR(svn_cstring_atoi64(&val, str));
-      *changes_offset = rev_offset + (apr_off_t)val;
-    }
-
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
-                        svn_fs_t *fs,
-                        svn_revnum_t rev,
-                        apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  apr_file_t *revision_file;
-  apr_off_t root_offset;
-  svn_fs_id_t *root_id = NULL;
-  svn_boolean_t is_cached;
-
-  SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
-  SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
-                         ffd->rev_root_id_cache, &rev, pool));
-  if (is_cached)
-    return SVN_NO_ERROR;
-
-  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
-  SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
-                                  pool));
-
-  SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
-                              root_offset, pool));
-
-  SVN_ERR(svn_io_file_close(revision_file, pool));
-
-  SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
-
-  *root_id_p = root_id;
-
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
-                             svn_fs_t *fs,
-                             svn_revnum_t rev,
-                             apr_pool_t *pool)
-{
-  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
-
-  return SVN_NO_ERROR;
-}
-
-/* Represents where in the current svndiff data block each
-   representation is. */
-typedef struct rep_state_t
-{
-  apr_file_t *file;
-                    /* The txdelta window cache to use or NULL. */
-  svn_cache__t *window_cache;
-                    /* Caches un-deltified windows. May be NULL. */
-  svn_cache__t *combined_cache;
-  apr_off_t start;  /* The starting offset for the raw
-                       svndiff/plaintext data minus header. */
-  apr_off_t off;    /* The current offset into the file. */
-  apr_off_t end;    /* The end offset of the raw data. */
-  int ver;          /* If a delta, what svndiff version? */
-  int chunk_index;
-} rep_state_t;
-
-/* See create_rep_state, which wraps this and adds another error. */
-static svn_error_t *
-create_rep_state_body(rep_state_t **rep_state,
-                      rep_args_t **rep_args,
-                      apr_file_t **file_hint,
-                      svn_revnum_t *rev_hint,
-                      representation_t *rep,
-                      svn_fs_t *fs,
-                      apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  rep_state_t *rs = apr_pcalloc(pool, sizeof(*rs));
-  rep_args_t *ra;
-  unsigned char buf[4];
-
-  /* If the hint is
-   * - given,
-   * - refers to a packed revision,
-   * - as does the rep we want to read, and
-   * - refers to the same pack file as the rep
-   * ...
-   */
-  if (   file_hint && rev_hint && *file_hint
-      && *rev_hint < ffd->min_unpacked_rev
-      && rep->revision < ffd->min_unpacked_rev
-      && (   (*rev_hint / ffd->max_files_per_dir)
-          == (rep->revision / ffd->max_files_per_dir)))
-    {
-      /* ... we can re-use the same, already open file object
-       */
-      apr_off_t offset;
-      SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
-
-      offset += rep->offset;
-      SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
-
-      rs->file = *file_hint;
-    }
-  else
-    {
-      /* otherwise, create a new file object
-       */
-      SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
-    }
-
-  /* remember the current file, if suggested by the caller */
-  if (file_hint)
-    *file_hint = rs->file;
-  if (rev_hint)
-    *rev_hint = rep->revision;
-
-  /* continue constructing RS and RA */
-  rs->window_cache = ffd->txdelta_window_cache;
-  rs->combined_cache = ffd->combined_window_cache;
-
-  SVN_ERR(read_rep_line(&ra, svn_stream_from_aprfile2(rs->file, TRUE, pool),
-                        pool));
-  SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
-  rs->off = rs->start;
-  rs->end = rs->start + rep->size;
-  *rep_state = rs;
-  *rep_args = ra;
-
-  if (ra->is_delta == FALSE)
-    /* This is a plaintext, so just return the current rep_state. */
-    return SVN_NO_ERROR;
-
-  /* We are dealing with a delta, find out what version. */
-  SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
-                                 NULL, NULL, pool));
-  /* ### Layering violation */
-  if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
-    return svn_error_create
-      (SVN_ERR_FS_CORRUPT, NULL,
-       _("Malformed svndiff data in representation"));
-  rs->ver = buf[3];
-  rs->chunk_index = 0;
-  rs->off += 4;
-
-  return SVN_NO_ERROR;
-}
-
-/* Read the rep args for REP in filesystem FS and create a rep_state
-   for reading the representation.  Return the rep_state in *REP_STATE
-   and the rep args in *REP_ARGS, both allocated in POOL.
-
-   When reading multiple reps, i.e. a skip delta chain, you may provide
-   non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
-   call it should be a pointer to NULL.)  The function will use these variables
-   to store the previous call results and tries to re-use them.  This may
-   result in significant savings in I/O for packed files.
- */
-static svn_error_t *
-create_rep_state(rep_state_t **rep_state,
-                 rep_args_t **rep_args,
-                 apr_file_t **file_hint,
-                 svn_revnum_t *rev_hint,
-                 representation_t *rep,
-                 svn_fs_t *fs,
-                 apr_pool_t *pool)
-{
-  svn_error_t *err = create_rep_state_body(rep_state, rep_args,
-                                           file_hint, rev_hint,
-                                           rep, fs, pool);
-  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
-    {
-      fs_fs_data_t *ffd = fs->fsap_data;
-
-      /* ### This always returns "-1" for transaction reps, because
-         ### this particular bit of code doesn't know if the rep is
-         ### stored in the protorev or in the mutable area (for props
-         ### or dir contents).  It is pretty rare for FSFS to *read*
-         ### from the protorev file, though, so this is probably OK.
-         ### And anyone going to debug corruption errors is probably
-         ### going to jump straight to this comment anyway! */
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
-                               "Corrupt representation '%s'",
-                               rep
-                               ? representation_string(rep, ffd->format, TRUE,
-                                                       TRUE, pool)
-                               : "(null)");
-    }
-  /* ### Call representation_string() ? */
-  return svn_error_trace(err);
-}
-
-struct rep_read_baton
-{
-  /* The FS from which we're reading. */
-  svn_fs_t *fs;
-
-  /* If not NULL, this is the base for the first delta window in rs_list */
-  svn_stringbuf_t *base_window;
-
-  /* The state of all prior delta representations. */
-  apr_array_header_t *rs_list;
-
-  /* The plaintext state, if there is a plaintext. */
-  rep_state_t *src_state;
-
-  /* The index of the current delta chunk, if we are reading a delta. */
-  int chunk_index;
-
-  /* The buffer where we store undeltified data. */
-  char *buf;
-  apr_size_t buf_pos;
-  apr_size_t buf_len;
-
-  /* A checksum context for summing the data read in order to verify it.
-     Note: we don't need to use the sha1 checksum because we're only doing
-     data verification, for which md5 is perfectly safe.  */
-  svn_checksum_ctx_t *md5_checksum_ctx;
-
-  svn_boolean_t checksum_finalized;
-
-  /* The stored checksum of the representation we are reading, its
-     length, and the amount we've read so far.  Some of this
-     information is redundant with rs_list and src_state, but it's
-     convenient for the checksumming code to have it here. */
-  svn_checksum_t *md5_checksum;
-
-  svn_filesize_t len;
-  svn_filesize_t off;
-
-  /* The key for the fulltext cache for this rep, if there is a
-     fulltext cache. */
-  pair_cache_key_t fulltext_cache_key;
-  /* The text we've been reading, if we're going to cache it. */
-  svn_stringbuf_t *current_fulltext;
-
-  /* Used for temporary allocations during the read. */
-  apr_pool_t *pool;
-
-  /* Pool used to store file handles and other data that is persistant
-     for the entire stream read. */
-  apr_pool_t *filehandle_pool;
-};
-
-/* Combine the name of the rev file in RS with the given OFFSET to form
- * a cache lookup key.  Allocations will be made from POOL.  May return
- * NULL if the key cannot be constructed. */
-static const char*
-get_window_key(rep_state_t *rs, apr_off_t offset, apr_pool_t *pool)
-{
-  const char *name;
-  const char *last_part;
-  const char *name_last;
-
-  /* the rev file name containing the txdelta window.
-   * If this fails we are in serious trouble anyways.
-   * And if nobody else detects the problems, the file content checksum
-   * comparison _will_ find them.
-   */
-  if (apr_file_name_get(&name, rs->file))
-    return NULL;
-
-  /* Handle packed files as well by scanning backwards until we find the
-   * revision or pack number. */
-  name_last = name + strlen(name) - 1;
-  while (! svn_ctype_isdigit(*name_last))
-    --name_last;
-
-  last_part = name_last;
-  while (svn_ctype_isdigit(*last_part))
-    --last_part;
-
-  /* We must differentiate between packed files (as of today, the number
-   * is being followed by a dot) and non-packed files (followed by \0).
-   * Otherwise, there might be overlaps in the numbering range if the
-   * repo gets packed after caching the txdeltas of non-packed revs.
-   * => add the first non-digit char to the packed number. */
-  if (name_last[1] != '\0')
-    ++name_last;
-
-  /* copy one char MORE than the actual number to mark packed files,
-   * i.e. packed revision file content uses different key space then
-   * non-packed ones: keys for packed rev file content ends with a dot
-   * for non-packed rev files they end with a digit. */
-  name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
-  return svn_fs_fs__combine_number_and_string(offset, name, pool);
-}
-
-/* Read the WINDOW_P for the rep state RS from the current FSFS session's
- * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
- * cache has been given. If a cache is available IS_CACHED will inform
- * the caller about the success of the lookup. Allocations (of the window
- * in particualar) will be made from POOL.
- *
- * If the information could be found, put RS and the position within the
- * rev file into the same state as if the data had just been read from it.
- */
-static svn_error_t *
-get_cached_window(svn_txdelta_window_t **window_p,
-                  rep_state_t *rs,
-                  svn_boolean_t *is_cached,
-                  apr_pool_t *pool)
-{
-  if (! rs->window_cache)
-    {
-      /* txdelta window has not been enabled */
-      *is_cached = FALSE;
-    }
-  else
-    {
-      /* ask the cache for the desired txdelta window */
-      svn_fs_fs__txdelta_cached_window_t *cached_window;
-      SVN_ERR(svn_cache__get((void **) &cached_window,
-                             is_cached,
-                             rs->window_cache,
-                             get_window_key(rs, rs->off, pool),
-                             pool));
-
-      if (*is_cached)
-        {
-          /* found it. Pass it back to the caller. */
-          *window_p = cached_window->window;
-
-          /* manipulate the RS as if we just read the data */
-          rs->chunk_index++;
-          rs->off = cached_window->end_offset;
-
-          /* manipulate the rev file as if we just read from it */
-          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-        }
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Store the WINDOW read at OFFSET for the rep state RS in the current
- * FSFS session's cache. This will be a no-op if no cache has been given.
- * Temporary allocations will be made from SCRATCH_POOL. */
-static svn_error_t *
-set_cached_window(svn_txdelta_window_t *window,
-                  rep_state_t *rs,
-                  apr_off_t offset,
-                  apr_pool_t *scratch_pool)
-{
-  if (rs->window_cache)
-    {
-      /* store the window and the first offset _past_ it */
-      svn_fs_fs__txdelta_cached_window_t cached_window;
-
-      cached_window.window = window;
-      cached_window.end_offset = rs->off;
-
-      /* but key it with the start offset because that is the known state
-       * when we will look it up */
-      return svn_cache__set(rs->window_cache,
-                            get_window_key(rs, offset, scratch_pool),
-                            &cached_window,
-                            scratch_pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Read the WINDOW_P for the rep state RS from the current FSFS session's
- * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
- * cache has been given. If a cache is available IS_CACHED will inform
- * the caller about the success of the lookup. Allocations (of the window
- * in particualar) will be made from POOL.
- */
-static svn_error_t *
-get_cached_combined_window(svn_stringbuf_t **window_p,
-                           rep_state_t *rs,
-                           svn_boolean_t *is_cached,
-                           apr_pool_t *pool)
-{
-  if (! rs->combined_cache)
-    {
-      /* txdelta window has not been enabled */
-      *is_cached = FALSE;
-    }
-  else
-    {
-      /* ask the cache for the desired txdelta window */
-      return svn_cache__get((void **)window_p,
-                            is_cached,
-                            rs->combined_cache,
-                            get_window_key(rs, rs->start, pool),
-                            pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Store the WINDOW read at OFFSET for the rep state RS in the current
- * FSFS session's cache. This will be a no-op if no cache has been given.
- * Temporary allocations will be made from SCRATCH_POOL. */
-static svn_error_t *
-set_cached_combined_window(svn_stringbuf_t *window,
-                           rep_state_t *rs,
-                           apr_off_t offset,
-                           apr_pool_t *scratch_pool)
-{
-  if (rs->combined_cache)
-    {
-      /* but key it with the start offset because that is the known state
-       * when we will look it up */
-      return svn_cache__set(rs->combined_cache,
-                            get_window_key(rs, offset, scratch_pool),
-                            window,
-                            scratch_pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Build an array of rep_state structures in *LIST giving the delta
-   reps from first_rep to a plain-text or self-compressed rep.  Set
-   *SRC_STATE to the plain-text rep we find at the end of the chain,
-   or to NULL if the final delta representation is self-compressed.
-   The representation to start from is designated by filesystem FS, id
-   ID, and representation REP.
-   Also, set *WINDOW_P to the base window content for *LIST, if it
-   could be found in cache. Otherwise, *LIST will contain the base
-   representation for the whole delta chain.
-   Finally, return the expanded size of the representation in 
-   *EXPANDED_SIZE. It will take care of cases where only the on-disk
-   size is known.  */
-static svn_error_t *
-build_rep_list(apr_array_header_t **list,
-               svn_stringbuf_t **window_p,
-               rep_state_t **src_state,
-               svn_filesize_t *expanded_size,
-               svn_fs_t *fs,
-               representation_t *first_rep,
-               apr_pool_t *pool)
-{
-  representation_t rep;
-  rep_state_t *rs = NULL;
-  rep_args_t *rep_args;
-  svn_boolean_t is_cached = FALSE;
-  apr_file_t *last_file = NULL;
-  svn_revnum_t last_revision;
-
-  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
-  rep = *first_rep;
-
-  /* The value as stored in the data struct.
-     0 is either for unknown length or actually zero length. */
-  *expanded_size = first_rep->expanded_size;
-
-  /* for the top-level rep, we need the rep_args */
-  SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
-                           &last_revision, &rep, fs, pool));
-
-  /* Unknown size or empty representation?
-     That implies the this being the first iteration.
-     Usually size equals on-disk size, except for empty,
-     compressed representations (delta, size = 4).
-     Please note that for all non-empty deltas have
-     a 4-byte header _plus_ some data. */
-  if (*expanded_size == 0)
-    if (! rep_args->is_delta || first_rep->size != 4)
-      *expanded_size = first_rep->size;
-
-  while (1)
-    {
-      /* fetch state, if that has not been done already */
-      if (!rs)
-        SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
-                                &last_revision, &rep, fs, pool));
-
-      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
-      if (is_cached)
-        {
-          /* We already have a reconstructed window in our cache.
-             Write a pseudo rep_state with the full length. */
-          rs->off = rs->start;
-          rs->end = rs->start + (*window_p)->len;
-          *src_state = rs;
-          return SVN_NO_ERROR;
-        }
-
-      if (rep_args->is_delta == FALSE)
-        {
-          /* This is a plaintext, so just return the current rep_state. */
-          *src_state = rs;
-          return SVN_NO_ERROR;
-        }
-
-      /* Push this rep onto the list.  If it's self-compressed, we're done. */
-      APR_ARRAY_PUSH(*list, rep_state_t *) = rs;
-      if (rep_args->is_delta_vs_empty)
-        {
-          *src_state = NULL;
-          return SVN_NO_ERROR;
-        }
-
-      rep.revision = rep_args->base_revision;
-      rep.offset = rep_args->base_offset;
-      rep.size = rep_args->base_length;
-      rep.txn_id = NULL;
-
-      rs = NULL;
-    }
-}
-
-
-/* Create a rep_read_baton structure for node revision NODEREV in
-   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
-   NULL, it is the rep's key in the fulltext cache, and a stringbuf
-   must be allocated to store the text.  Perform all allocations in
-   POOL.  If rep is mutable, it must be for file contents. */
-static svn_error_t *
-rep_read_get_baton(struct rep_read_baton **rb_p,
-                   svn_fs_t *fs,
-                   representation_t *rep,
-                   pair_cache_key_t fulltext_cache_key,
-                   apr_pool_t *pool)
-{
-  struct rep_read_baton *b;
-
-  b = apr_pcalloc(pool, sizeof(*b));
-  b->fs = fs;
-  b->base_window = NULL;
-  b->chunk_index = 0;
-  b->buf = NULL;
-  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
-  b->checksum_finalized = FALSE;
-  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
-  b->len = rep->expanded_size;
-  b->off = 0;
-  b->fulltext_cache_key = fulltext_cache_key;
-  b->pool = svn_pool_create(pool);
-  b->filehandle_pool = svn_pool_create(pool);
-
-  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
-                         &b->src_state, &b->len, fs, rep,
-                         b->filehandle_pool));
-
-  if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
-    b->current_fulltext = svn_stringbuf_create_ensure
-                            ((apr_size_t)b->len,
-                             b->filehandle_pool);
-  else
-    b->current_fulltext = NULL;
-
-  /* Save our output baton. */
-  *rb_p = b;
-
-  return SVN_NO_ERROR;
-}
-
-/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
-   window into *NWIN. */
-static svn_error_t *
-read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
-                  rep_state_t *rs, apr_pool_t *pool)
-{
-  svn_stream_t *stream;
-  svn_boolean_t is_cached;
-  apr_off_t old_offset;
-
-  SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
-
-  /* RS->FILE may be shared between RS instances -> make sure we point
-   * to the right data. */
-  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-
-  /* Skip windows to reach the current chunk if we aren't there yet. */
-  while (rs->chunk_index < this_chunk)
-    {
-      SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
-      rs->chunk_index++;
-      SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
-      if (rs->off >= rs->end)
-        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                _("Reading one svndiff window read "
-                                  "beyond the end of the "
-                                  "representation"));
-    }
-
-  /* Read the next window. But first, try to find it in the cache. */
-  SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
-  if (is_cached)
-    return SVN_NO_ERROR;
-
-  /* Actually read the next window. */
-  old_offset = rs->off;
-  stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
-  SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
-  rs->chunk_index++;
-  SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
-
-  if (rs->off > rs->end)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Reading one svndiff window read beyond "
-                              "the end of the representation"));
-
-  /* the window has not been cached before, thus cache it now
-   * (if caching is used for them at all) */
-  return set_cached_window(*nwin, rs, old_offset, pool);
-}
-
-/* Read SIZE bytes from the representation RS and return it in *NWIN. */
-static svn_error_t *
-read_plain_window(svn_stringbuf_t **nwin, rep_state_t *rs,
-                  apr_size_t size, apr_pool_t *pool)
-{
-  /* RS->FILE may be shared between RS instances -> make sure we point
-   * to the right data. */
-  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-
-  /* Read the plain data. */
-  *nwin = svn_stringbuf_create_ensure(size, pool);
-  SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
-                                 pool));
-  (*nwin)->data[size] = 0;
-
-  /* Update RS. */
-  rs->off += (apr_off_t)size;
-
-  return SVN_NO_ERROR;
-}
-
-/* Get the undeltified window that is a result of combining all deltas
-   from the current desired representation identified in *RB with its
-   base representation.  Store the window in *RESULT. */
-static svn_error_t *
-get_combined_window(svn_stringbuf_t **result,
-                    struct rep_read_baton *rb)
-{
-  apr_pool_t *pool, *new_pool, *window_pool;
-  int i;
-  svn_txdelta_window_t *window;
-  apr_array_header_t *windows;
-  svn_stringbuf_t *source, *buf = rb->base_window;
-  rep_state_t *rs;
-
-  /* Read all windows that we need to combine. This is fine because
-     the size of each window is relatively small (100kB) and skip-
-     delta limits the number of deltas in a chain to well under 100.
-     Stop early if one of them does not depend on its predecessors. */
-  window_pool = svn_pool_create(rb->pool);
-  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
-  for (i = 0; i < rb->rs_list->nelts; ++i)
-    {
-      rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
-      SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
-
-      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
-      if (window->src_ops == 0)
-        {
-          ++i;
-          break;
-        }
-    }
-
-  /* Combine in the windows from the other delta reps. */
-  pool = svn_pool_create(rb->pool);
-  for (--i; i >= 0; --i)
-    {
-
-      rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
-      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
-
-      /* Maybe, we've got a PLAIN start representation.  If we do, read
-         as much data from it as the needed for the txdelta window's source
-         view.
-         Note that BUF / SOURCE may only be NULL in the first iteration. */
-      source = buf;
-      if (source == NULL && rb->src_state != NULL)
-        SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
-                                  pool));
-
-      /* Combine this window with the current one. */
-      new_pool = svn_pool_create(rb->pool);
-      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
-      buf->len = window->tview_len;
-
-      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
-                                     buf->data, &buf->len);
-      if (buf->len != window->tview_len)
-        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                _("svndiff window length is "
-                                  "corrupt"));
-
-      /* Cache windows only if the whole rep content could be read as a
-         single chunk.  Only then will no other chunk need a deeper RS
-         list than the cached chunk. */
-      if ((rb->chunk_index == 0) && (rs->off == rs->end))
-        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
-
-      /* Cycle pools so that we only need to hold three windows at a time. */
-      svn_pool_destroy(pool);
-      pool = new_pool;
-    }
-
-  svn_pool_destroy(window_pool);
-
-  *result = buf;
-  return SVN_NO_ERROR;
-}
-
-/* Returns whether or not the expanded fulltext of the file is cachable
- * based on its size SIZE.  The decision depends on the cache used by RB.
- */
-static svn_boolean_t
-fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
-{
-  return (size < APR_SIZE_MAX)
-      && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
-}
-
-/* Close method used on streams returned by read_representation().
- */
-static svn_error_t *
-rep_read_contents_close(void *baton)
-{
-  struct rep_read_baton *rb = baton;
-
-  svn_pool_destroy(rb->pool);
-  svn_pool_destroy(rb->filehandle_pool);
-
-  return SVN_NO_ERROR;
-}
-
-/* Return the next *LEN bytes of the rep and store them in *BUF. */
-static svn_error_t *
-get_contents(struct rep_read_baton *rb,
-             char *buf,
-             apr_size_t *len)
-{
-  apr_size_t copy_len, remaining = *len;
-  char *cur = buf;
-  rep_state_t *rs;
-
-  /* Special case for when there are no delta reps, only a plain
-     text. */
-  if (rb->rs_list->nelts == 0)
-    {
-      copy_len = remaining;
-      rs = rb->src_state;
-
-      if (rb->base_window != NULL)
-        {
-          /* We got the desired rep directly from the cache.
-             This is where we need the pseudo rep_state created
-             by build_rep_list(). */
-          apr_size_t offset = (apr_size_t)(rs->off - rs->start);
-          if (copy_len + offset > rb->base_window->len)
-            copy_len = offset < rb->base_window->len
-                     ? rb->base_window->len - offset
-                     : 0ul;
-
-          memcpy (cur, rb->base_window->data + offset, copy_len);
-        }
-      else
-        {
-          if (((apr_off_t) copy_len) > rs->end - rs->off)
-            copy_len = (apr_size_t) (rs->end - rs->off);
-          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
-                                         NULL, rb->pool));
-        }
-
-      rs->off += copy_len;
-      *len = copy_len;
-      return SVN_NO_ERROR;
-    }
-
-  while (remaining > 0)
-    {
-      /* If we have buffered data from a previous chunk, use that. */
-      if (rb->buf)
-        {
-          /* Determine how much to copy from the buffer. */
-          copy_len = rb->buf_len - rb->buf_pos;
-          if (copy_len > remaining)
-            copy_len = remaining;
-
-          /* Actually copy the data. */
-          memcpy(cur, rb->buf + rb->buf_pos, copy_len);
-          rb->buf_pos += copy_len;
-          cur += copy_len;
-          remaining -= copy_len;
-
-          /* If the buffer is all used up, clear it and empty the
-             local pool. */
-          if (rb->buf_pos == rb->buf_len)
-            {
-              svn_pool_clear(rb->pool);
-              rb->buf = NULL;
-            }
-        }
-      else
-        {
-          svn_stringbuf_t *sbuf = NULL;
-
-          rs = APR_ARRAY_IDX(rb->rs_list, 0, rep_state_t *);
-          if (rs->off == rs->end)
-            break;
-
-          /* Get more buffered data by evaluating a chunk. */
-          SVN_ERR(get_combined_window(&sbuf, rb));
-
-          rb->chunk_index++;
-          rb->buf_len = sbuf->len;
-          rb->buf = sbuf->data;
-          rb->buf_pos = 0;
-        }
-    }
-
-  *len = cur - buf;
-
-  return SVN_NO_ERROR;
-}
-
-/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
-   representation and store them in *BUF.  Sum as we read and verify
-   the MD5 sum at the end. */
-static svn_error_t *
-rep_read_contents(void *baton,
-                  char *buf,
-                  apr_size_t *len)
-{
-  struct rep_read_baton *rb = baton;
-
-  /* Get the next block of data. */
-  SVN_ERR(get_contents(rb, buf, len));
-
-  if (rb->current_fulltext)
-    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
-
-  /* Perform checksumming.  We want to check the checksum as soon as
-     the last byte of data is read, in case the caller never performs
-     a short read, but we don't want to finalize the MD5 context
-     twice. */
-  if (!rb->checksum_finalized)
-    {
-      SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
-      rb->off += *len;
-      if (rb->off == rb->len)
-        {
-          svn_checksum_t *md5_checksum;
-
-          rb->checksum_finalized = TRUE;
-          SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
-                                     rb->pool));
-          if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
-            return svn_error_create(SVN_ERR_FS_CORRUPT,
-                    svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
-                        rb->pool,
-                        _("Checksum mismatch while reading representation")),
-                    NULL);
-        }
-    }
-
-  if (rb->off == rb->len && rb->current_fulltext)
-    {
-      fs_fs_data_t *ffd = rb->fs->fsap_data;
-      SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
-                             rb->current_fulltext, rb->pool));
-      rb->current_fulltext = NULL;
-    }
-
-  return SVN_NO_ERROR;
-}
-
-
-/* Return a stream in *CONTENTS_P that will read the contents of a
-   representation stored at the location given by REP.  Appropriate
-   for any kind of immutable representation, but only for file
-   contents (not props or directory contents) in mutable
-   representations.
-
-   If REP is NULL, the representation is assumed to be empty, and the
-   empty stream is returned.
-*/
-static svn_error_t *
-read_representation(svn_stream_t **contents_p,
-                    svn_fs_t *fs,
-                    representation_t *rep,
-                    apr_pool_t *pool)
-{
-  if (! rep)
-    {
-      *contents_p = svn_stream_empty(pool);
-    }
-  else
-    {
-      fs_fs_data_t *ffd = fs->fsap_data;
-      pair_cache_key_t fulltext_cache_key;
-      svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
-      struct rep_read_baton *rb;
-
-      fulltext_cache_key.revision = rep->revision;
-      fulltext_cache_key.second = rep->offset;
-      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
-          && fulltext_size_is_cachable(ffd, len))
-        {
-          svn_stringbuf_t *fulltext;
-          svn_boolean_t is_cached;
-          SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
-                                 ffd->fulltext_cache, &fulltext_cache_key,
-                                 pool));
-          if (is_cached)
-            {
-              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
-              return SVN_NO_ERROR;
-            }
-        }
-      else
-        fulltext_cache_key.revision = SVN_INVALID_REVNUM;
-
-      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
-
-      *contents_p = svn_stream_create(rb, pool);
-      svn_stream_set_read(*contents_p, rep_read_contents);
-      svn_stream_set_close(*contents_p, rep_read_contents_close);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__get_contents(svn_stream_t **contents_p,
-                        svn_fs_t *fs,
-                        node_revision_t *noderev,
-                        apr_pool_t *pool)
-{
-  return read_representation(contents_p, fs, noderev->data_rep, pool);
-}
-
-/* Baton used when reading delta windows. */
-struct delta_read_baton
-{
-  rep_state_t *rs;
-  svn_checksum_t *checksum;
-};
-
-/* This implements the svn_txdelta_next_window_fn_t interface. */
-static svn_error_t *
-delta_read_next_window(svn_txdelta_window_t **window, void *baton,
-                       apr_pool_t *pool)
-{
-  struct delta_read_baton *drb = baton;
-
-  if (drb->rs->off == drb->rs->end)
-    {
-      *window = NULL;
-      return SVN_NO_ERROR;
-    }
-
-  return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
-}
-
-/* This implements the svn_txdelta_md5_digest_fn_t interface. */
-static const unsigned char *
-delta_read_md5_digest(void *baton)
-{
-  struct delta_read_baton *drb = baton;
-
-  if (drb->checksum->kind == svn_checksum_md5)
-    return drb->checksum->digest;
-  else
-    return NULL;
-}
-
-svn_error_t *
-svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
-                                 svn_fs_t *fs,
-                                 node_revision_t *source,
-                                 node_revision_t *target,
-                                 apr_pool_t *pool)
-{
-  svn_stream_t *source_stream, *target_stream;
-
-  /* Try a shortcut: if the target is stored as a delta against the source,
-     then just use that delta. */
-  if (source && source->data_rep && target->data_rep)
-    {
-      rep_state_t *rep_state;
-      rep_args_t *rep_args;
-
-      /* Read target's base rep if any. */
-      SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
-                               target->data_rep, fs, pool));
-      /* If that matches source, then use this delta as is. */
-      if (rep_args->is_delta
-          && (rep_args->is_delta_vs_empty
-              || (rep_args->base_revision == source->data_rep->revision
-                  && rep_args->base_offset == source->data_rep->offset)))
-        {
-          /* Create the delta read baton. */
-          struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
-          drb->rs = rep_state;
-          drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
-                                           pool);
-          *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
-                                                delta_read_md5_digest, pool);
-          return SVN_NO_ERROR;
-        }
-      else
-        SVN_ERR(svn_io_file_close(rep_state->file, pool));
-    }
-
-  /* Read both fulltexts and construct a delta. */
-  if (source)
-    SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
-  else
-    source_stream = svn_stream_empty(pool);
-  SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
-
-  /* Because source and target stream will already verify their content,
-   * there is no need to do this once more.  In particular if the stream
-   * content is being fetched from cache. */
-  svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
-
-  return SVN_NO_ERROR;
-}
-
-/* Baton for cache_access_wrapper. Wraps the original parameters of
- * svn_fs_fs__try_process_file_content().
- */
-typedef struct cache_access_wrapper_baton_t
-{
-  svn_fs_process_contents_func_t func;
-  void* baton;
-} cache_access_wrapper_baton_t;
-
-/* Wrapper to translate between svn_fs_process_contents_func_t and
- * svn_cache__partial_getter_func_t.
- */
-static svn_error_t *
-cache_access_wrapper(void **out,
-                     const void *data,
-                     apr_size_t data_len,
-                     void *baton,
-                     apr_pool_t *pool)
-{
-  cache_access_wrapper_baton_t *wrapper_baton = baton;
-
-  SVN_ERR(wrapper_baton->func((const unsigned char *)data,
-                              data_len - 1, /* cache adds terminating 0 */
-                              wrapper_baton->baton,
-                              pool));
-  
-  /* non-NULL value to signal the calling cache that all went well */
-  *out = baton;
-
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
-                                     svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     svn_fs_process_contents_func_t processor,
-                                     void* baton,
-                                     apr_pool_t *pool)
-{
-  representation_t *rep = noderev->data_rep;
-  if (rep)
-    {
-      fs_fs_data_t *ffd = fs->fsap_data;
-      pair_cache_key_t fulltext_cache_key;
-
-      fulltext_cache_key.revision = rep->revision;
-      fulltext_cache_key.second = rep->offset;
-      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
-          && fulltext_size_is_cachable(ffd, rep->expanded_size))
-        {
-          cache_access_wrapper_baton_t wrapper_baton;
-          void *dummy = NULL;
-
-          wrapper_baton.func = processor;
-          wrapper_baton.baton = baton;
-          return svn_cache__get_partial(&dummy, success,
-                                        ffd->fulltext_cache,
-                                        &fulltext_cache_key,
-                                        cache_access_wrapper,
-                                        &wrapper_baton,
-                                        pool);
-        }
-    }
-
-  *success = FALSE;
-  return SVN_NO_ERROR;
-}
-
-/* Fetch the contents of a directory into ENTRIES.  Values are stored
-   as filename to string mappings; further conversion is necessary to
-   convert them into svn_fs_dirent_t values. */
-static svn_error_t *
-get_dir_contents(apr_hash_t *entries,
-                 svn_fs_t *fs,
-                 node_revision_t *noderev,
-                 apr_pool_t *pool)
-{
-  svn_stream_t *contents;
-
-  if (noderev->data_rep && noderev->data_rep->txn_id)
-    {
-      const char *filename = path_txn_node_children(fs, noderev->id, pool);
-
-      /* The representation is mutable.  Read the old directory
-         contents from the mutable children file, followed by the
-         changes we've made in this transaction. */
-      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
-      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
-      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
-      SVN_ERR(svn_stream_close(contents));
-    }
-  else if (noderev->data_rep)
-    {
-      /* use a temporary pool for temp objects.
-       * Also undeltify content before parsing it. Otherwise, we could only
-       * parse it byte-by-byte.
-       */
-      apr_pool_t *text_pool = svn_pool_create(pool);
-      apr_size_t len = noderev->data_rep->expanded_size
-                     ? (apr_size_t)noderev->data_rep->expanded_size
-                     : (apr_size_t)noderev->data_rep->size;
-      svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
-      text->len = len;
-
-      /* The representation is immutable.  Read it normally. */
-      SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
-      SVN_ERR(svn_stream_read(contents, text->data, &text->len));
-      SVN_ERR(svn_stream_close(contents));
-
-      /* de-serialize hash */
-      contents = svn_stream_from_stringbuf(text, text_pool);
-      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
-
-      svn_pool_destroy(text_pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-
-static const char *
-unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
-                  apr_pool_t *pool)
-{
-  return apr_psprintf(pool, "%s %s",
-                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
-                      svn_fs_fs__id_unparse(id, pool)->data);
-}
-
-/* Given a hash ENTRIES of dirent structions, return a hash in
-   *STR_ENTRIES_P, that has svn_string_t as the values in the format
-   specified by the fs_fs directory contents file.  Perform
-   allocations in POOL. */
-static svn_error_t *
-unparse_dir_entries(apr_hash_t **str_entries_p,
-                    apr_hash_t *entries,
-                    apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  /* For now, we use a our own hash function to ensure that we get a
-   * (largely) stable order when serializing the data.  It also gives
-   * us some performance improvement.
-   *
-   * ### TODO ###
-   * Use some sorted or other fixed order data container.
-   */
-  *str_entries_p = svn_hash__make(pool);
-
-  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
-    {
-      const void *key;
-      apr_ssize_t klen;
-      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
-      const char *new_val;
-
-      apr_hash_this(hi, &key, &klen, NULL);
-      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
-      apr_hash_set(*str_entries_p, key, klen,
-                   svn_string_create(new_val, pool));
-    }
-
-  return SVN_NO_ERROR;
-}
-
-
-/* Given a hash STR_ENTRIES with values as svn_string_t as specified
-   in an FSFS directory contents listing, return a hash of dirents in
-   *ENTRIES_P.  Perform allocations in POOL. */
-static svn_error_t *
-parse_dir_entries(apr_hash_t **entries_p,
-                  apr_hash_t *str_entries,
-                  const char *unparsed_id,
-                  apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  *entries_p = apr_hash_make(pool);
-
-  /* Translate the string dir entries into real entries. */
-  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
-    {
-      const char *name = svn__apr_hash_index_key(hi);
-      svn_string_t *str_val = svn__apr_hash_index_val(hi);
-      char *str, *last_str;
-      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
-
-      last_str = apr_pstrdup(pool, str_val->data);
-      dirent->name = apr_pstrdup(pool, name);
-
-      str = svn_cstring_tokenize(" ", &last_str);
-      if (str == NULL)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Directory entry corrupt in '%s'"),
-                                 unparsed_id);
-
-      if (strcmp(str, KIND_FILE) == 0)
-        {
-          dirent->kind = svn_node_file;
-        }
-      else if (strcmp(str, KIND_DIR) == 0)
-        {
-          dirent->kind = svn_node_dir;
-        }
-      else
-        {
-          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                   _("Directory entry corrupt in '%s'"),
-                                   unparsed_id);
-        }
-
-      str = svn_cstring_tokenize(" ", &last_str);
-      if (str == NULL)
-          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                   _("Directory entry corrupt in '%s'"),
-                                   unparsed_id);
-
-      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
-
-      apr_hash_set(*entries_p, dirent->name, APR_HASH_KEY_STRING, dirent);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Return the cache object in FS responsible to storing the directory
- * the NODEREV. If none exists, return NULL. */
-static svn_cache__t *
-locate_dir_cache(svn_fs_t *fs,
-                 node_revision_t *noderev)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  return svn_fs_fs__id_txn_id(noderev->id)
-      ? ffd->txn_dir_cache
-      : ffd->dir_cache;
-}
-
-svn_error_t *
-svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
-                            svn_fs_t *fs,
-                            node_revision_t *noderev,
-                            apr_pool_t *pool)
-{
-  const char *unparsed_id = NULL;
-  apr_hash_t *unparsed_entries, *parsed_entries;
-
-  /* find the cache we may use */
-  svn_cache__t *cache = locate_dir_cache(fs, noderev);
-  if (cache)
-    {
-      svn_boolean_t found;
-
-      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
-      SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
-                             unparsed_id, pool));
-      if (found)
-        return SVN_NO_ERROR;
-    }
-
-  /* Read in the directory hash. */
-  unparsed_entries = apr_hash_make(pool);
-  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
-  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
-                            unparsed_id, pool));
-
-  /* Update the cache, if we are to use one. */
-  if (cache)
-    SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
-
-  *entries_p = parsed_entries;
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
-                                  svn_fs_t *fs,
-                                  node_revision_t *noderev,
-                                  const char *name,
-                                  apr_pool_t *result_pool,
-                                  apr_pool_t *scratch_pool)
-{
-  svn_boolean_t found = FALSE;
-
-  /* find the cache we may use */
-  svn_cache__t *cache = locate_dir_cache(fs, noderev);
-  if (cache)
-    {
-      const char *unparsed_id =
-        svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
-
-      /* Cache lookup. */
-      SVN_ERR(svn_cache__get_partial((void **)dirent,

[... 4839 lines stripped ...]


Mime
View raw message