You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/11/27 12:52:46 UTC

svn commit: r1546002 [13/39] - in /subversion/branches/verify-keep-going: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ build/win32/ contrib/client-side/emacs/ contrib/server-side/ contrib/server-side/svnc...

Modified: subversion/branches/verify-keep-going/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_fs_fs/fs_fs.c?rev=1546002&r1=1546001&r2=1546002&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_fs_fs/fs_fs.c Wed Nov 27 11:52:35 2013
@@ -20,59 +20,31 @@
  * ====================================================================
  */
 
-#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_private_config.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 "svn_dirent_uri.h"
 #include "svn_version.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 "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_string_private.h"
 #include "private/svn_subr_private.h"
-#include "private/svn_delta_private.h"
 #include "../libsvn_fs/fs-loader.h"
 
-#include "svn_private_config.h"
-#include "temp_serializer.h"
-
-/* An arbitrary maximum path length, so clients can't run us out of memory
- * by giving us arbitrarily large paths. */
-#define FSFS_MAX_PATH_LEN 4096
-
 /* The default maximum number of files per directory to store in the
    rev and revprops directory.  The number below is somewhat arbitrary,
    and can be overridden by defining the macro while compiling; the
@@ -96,53 +68,6 @@
    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. */
-
-/* Headers used to describe node-revision in the revision file. */
-#define HEADER_ID          "id"
-#define HEADER_TYPE        "type"
-#define HEADER_COUNT       "count"
-#define HEADER_PROPS       "props"
-#define HEADER_TEXT        "text"
-#define HEADER_CPATH       "cpath"
-#define HEADER_PRED        "pred"
-#define HEADER_COPYFROM    "copyfrom"
-#define HEADER_COPYROOT    "copyroot"
-#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
-#define HEADER_MINFO_HERE  "minfo-here"
-#define HEADER_MINFO_CNT   "minfo-cnt"
-
-/* Kinds that a change can be. */
-#define ACTION_MODIFY      "modify"
-#define ACTION_ADD         "add"
-#define ACTION_DELETE      "delete"
-#define ACTION_REPLACE     "replace"
-#define ACTION_RESET       "reset"
-
-/* True and False flags. */
-#define FLAG_TRUE          "true"
-#define FLAG_FALSE         "false"
-
-/* Kinds that a node-rev can be. */
-#define KIND_FILE          "file"
-#define KIND_DIR           "dir"
-
-/* Kinds of representation. */
-#define REP_PLAIN          "PLAIN"
-#define REP_DELTA          "DELTA"
-
 /* Notes:
 
 To avoid opening and closing the rev-files all the time, it would
@@ -155,59 +80,13 @@ 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 *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
-                      const char *path,
-                      apr_pool_t *pool);
-
-static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
-
-static svn_error_t *
 get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
 
-static svn_error_t *
-verify_walker(representation_t *rep,
-              void *baton,
-              svn_fs_t *fs,
-              apr_pool_t *scratch_pool);
-
 /* Pathname helper functions */
 
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  return (rev < ffd->min_unpacked_rev);
-}
-
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  /* rev 0 will not be packed */
-  return (rev < ffd->min_unpacked_rev)
-      && (rev != 0)
-      && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
-}
-
 static const char *
 path_format(svn_fs_t *fs, apr_pool_t *pool)
 {
@@ -227,367 +106,13 @@ svn_fs_fs__path_current(svn_fs_t *fs, ap
 }
 
 static APR_INLINE const char *
-path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
-{
-  return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
-}
-
-static APR_INLINE const char *
-path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
-{
-  return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
-}
-
-static APR_INLINE const char *
 path_lock(svn_fs_t *fs, apr_pool_t *pool)
 {
   return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
 }
 
-static const char *
-path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
-{
-  return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
-}
-
-static const char *
-path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
-                apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  assert(ffd->max_files_per_dir);
-  assert(is_packed_rev(fs, rev));
-
-  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
-                              apr_psprintf(pool,
-                                           "%ld" PATH_EXT_PACKED_SHARD,
-                                           rev / ffd->max_files_per_dir),
-                              kind, NULL);
-}
-
-static const char *
-path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  assert(ffd->max_files_per_dir);
-  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
-                              apr_psprintf(pool, "%ld",
-                                                 rev / ffd->max_files_per_dir),
-                              NULL);
-}
-
-static const char *
-path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  assert(! is_packed_rev(fs, rev));
-
-  if (ffd->max_files_per_dir)
-    {
-      return svn_dirent_join(path_rev_shard(fs, rev, pool),
-                             apr_psprintf(pool, "%ld", rev),
-                             pool);
-    }
-
-  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
-                              apr_psprintf(pool, "%ld", rev), NULL);
-}
-
-svn_error_t *
-svn_fs_fs__path_rev_absolute(const char **path,
-                             svn_fs_t *fs,
-                             svn_revnum_t rev,
-                             apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
-      || ! is_packed_rev(fs, rev))
-    {
-      *path = path_rev(fs, rev, pool);
-    }
-  else
-    {
-      *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-static const char *
-path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  assert(ffd->max_files_per_dir);
-  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
-                              apr_psprintf(pool, "%ld",
-                                           rev / ffd->max_files_per_dir),
-                              NULL);
-}
-
-static const char *
-path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  assert(ffd->max_files_per_dir);
-  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
-                              apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
-                                           rev / ffd->max_files_per_dir),
-                              NULL);
-}
-
-static const char *
-path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  if (ffd->max_files_per_dir)
-    {
-      return svn_dirent_join(path_revprops_shard(fs, rev, pool),
-                             apr_psprintf(pool, "%ld", rev),
-                             pool);
-    }
-
-  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
-                              apr_psprintf(pool, "%ld", rev), NULL);
-}
-
-static APR_INLINE const char *
-path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
-  return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
-                              apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
-                                          (char *)NULL),
-                              NULL);
-}
-
-/* Return the name of the sha1->rep mapping file in transaction TXN_ID
- * within FS for the given SHA1 checksum.  Use POOL for allocations.
- */
-static APR_INLINE const char *
-path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
-              apr_pool_t *pool)
-{
-  return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
-                         svn_checksum_to_cstring(sha1, pool),
-                         pool);
-}
-
-static APR_INLINE const char *
-path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
-}
-
-static APR_INLINE const char *
-path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
-}
-
-static APR_INLINE const char *
-path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
-}
-
-static APR_INLINE const char *
-path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
-{
-  return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
-}
-
-
-static APR_INLINE const char *
-path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
-    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
-                                apr_pstrcat(pool, txn_id, PATH_EXT_REV,
-                                            (char *)NULL),
-                                NULL);
-  else
-    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
-}
-
-static APR_INLINE const char *
-path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
-    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
-                                apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
-                                            (char *)NULL),
-                                NULL);
-  else
-    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
-                           pool);
-}
-
-static const char *
-path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
-  const char *txn_id = svn_fs_fs__id_txn_id(id);
-  const char *node_id = svn_fs_fs__id_node_id(id);
-  const char *copy_id = svn_fs_fs__id_copy_id(id);
-  const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
-                                  node_id, copy_id);
-
-  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
-}
-
-static APR_INLINE const char *
-path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
-  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
-                     (char *)NULL);
-}
-
-static APR_INLINE const char *
-path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
-  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
-                     PATH_EXT_CHILDREN, (char *)NULL);
-}
-
-static APR_INLINE const char *
-path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
-{
-  size_t len = strlen(node_id);
-  const char *node_id_minus_last_char =
-    (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
-  return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
-                              node_id_minus_last_char, NULL);
-}
-
-static APR_INLINE const char *
-path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
-{
-  const char *path;
-  apr_off_t offset = 0;
-
-  if (apr_file_name_get(&path, file) != APR_SUCCESS)
-    path = "(unknown)";
-
-  if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
-    offset = -1;
-
-  return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
-}
-
-
 
 
-/* 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,
@@ -602,7 +127,7 @@ get_lock_on_filesystem(const char *lock_
       svn_error_clear(err);
       err = NULL;
 
-      SVN_ERR(svn_io_file_create(lock_filename, "", pool));
+      SVN_ERR(svn_io_file_create_empty(lock_filename, pool));
       SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
     }
 
@@ -654,7 +179,7 @@ with_some_lock_file(svn_fs_t *fs,
       /* 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(svn_fs_fs__update_min_unpacked_rev(fs, pool));
       SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
                            pool));
       err = body(baton, subpool);
@@ -686,301 +211,63 @@ svn_fs_fs__with_write_lock(svn_fs_t *fs,
 
 /* 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)
+svn_error_t *
+svn_fs_fs__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));
+                               svn_fs_fs__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.
+/* 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.
 
-   Perform temporary allocations in POOL. */
+   Uses POOL for temporary allocation. */
 static svn_error_t *
-unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
-                 apr_pool_t *pool)
+check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
+                                 const char *path, 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);
+  return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
+                                              pool);
 }
 
-/* Same as unlock_proto_rev(), but requires that the transaction list
-   lock is already held. */
+/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
+   number is not the same as a format number supported by this
+   Subversion. */
 static svn_error_t *
-unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
-                             void *lockcookie,
-                             apr_pool_t *pool)
+check_format(int format)
 {
-  struct unlock_proto_rev_baton b;
+  /* Blacklist.  These formats may be either younger or older than
+     SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
+  if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
+    return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+                             _("Found format '%d', only created by "
+                               "unreleased dev builds; see "
+                               "http://subversion.apache.org"
+                               "/docs/release-notes/1.7#revprop-packing"),
+                             format);
 
-  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 file PATH,
-   contains only digits at OFFSET and beyond, raising an error if not.
-   TITLE contains a user-visible description of the file, usually the
-   short file name.
-
-   Uses POOL for temporary allocation. */
-static svn_error_t *
-check_file_buffer_numeric(const char *buf, apr_off_t offset,
-                          const char *path, const char *title,
-                          apr_pool_t *pool)
-{
-  const char *p;
-
-  for (p = buf + offset; *p; p++)
-    if (!svn_ctype_isdigit(*p))
-      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
-        _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
-        title, svn_dirent_local_style(path, pool), *p, buf);
-
-  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.
-
-   Uses POOL for temporary allocation. */
-static svn_error_t *
-check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
-                                 const char *path, apr_pool_t *pool)
-{
-  return check_file_buffer_numeric(buf, offset, path, "Format", pool);
+  /* We support all formats from 1-current simultaneously */
+  if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
+    return SVN_NO_ERROR;
+
+  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+     _("Expected FS format between '1' and '%d'; found format '%d'"),
+     SVN_FS_FS__FORMAT_NUMBER, format);
 }
 
 /* Read the format number and maximum number of files per directory
@@ -1033,6 +320,9 @@ read_format(int *pformat, int *max_files
   SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
   SVN_ERR(svn_cstring_atoi(pformat, buf->data));
 
+  /* Check that we support this format at all */
+  SVN_ERR(check_format(*pformat));
+
   /* Set the default values for anything that can be set via an option. */
   *max_files_per_dir = 0;
 
@@ -1074,21 +364,25 @@ 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,
+                        svn_boolean_t overwrite,
+                        apr_pool_t *pool)
 {
   svn_stringbuf_t *sb;
+  fs_fs_data_t *ffd = fs->fsap_data;
+  const char *path = path_format(fs, pool);
 
-  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");
     }
@@ -1111,31 +405,6 @@ write_format(const char *path, int forma
   return svn_io_set_file_read_only(path, FALSE, pool);
 }
 
-/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
-   number is not the same as a format number supported by this
-   Subversion. */
-static svn_error_t *
-check_format(int format)
-{
-  /* Blacklist.  These formats may be either younger or older than
-     SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
-  if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
-    return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
-                             _("Found format '%d', only created by "
-                               "unreleased dev builds; see "
-                               "http://subversion.apache.org"
-                               "/docs/release-notes/1.7#revprop-packing"),
-                             format);
-
-  /* We support all formats from 1-current simultaneously */
-  if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
-    return SVN_NO_ERROR;
-
-  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
-     _("Expected FS format between '1' and '%d'; found format '%d'"),
-     SVN_FS_FS__FORMAT_NUMBER, format);
-}
-
 svn_boolean_t
 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
 {
@@ -1353,37 +622,6 @@ write_config(svn_fs_t *fs,
                             fsfs_conf_contents, pool);
 }
 
-static svn_error_t *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
-                      const char *path,
-                      apr_pool_t *pool)
-{
-  char buf[80];
-  apr_file_t *file;
-  apr_size_t len;
-
-  SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
-                           APR_OS_DEFAULT, pool));
-  len = sizeof(buf);
-  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
-  SVN_ERR(svn_io_file_close(file, pool));
-
-  *min_unpacked_rev = SVN_STR_TO_REV(buf);
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-
-  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
-
-  return read_min_unpacked_rev(&ffd->min_unpacked_rev,
-                               path_min_unpacked_rev(fs, pool),
-                               pool);
-}
-
 svn_error_t *
 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
 {
@@ -1398,7 +636,6 @@ svn_fs_fs__open(svn_fs_t *fs, const char
   /* Read the FS format number. */
   SVN_ERR(read_format(&format, &max_files_per_dir,
                       path_format(fs, pool), pool));
-  SVN_ERR(check_format(format));
 
   /* Now we've got a format number no matter what. */
   ffd->format = format;
@@ -1416,7 +653,7 @@ svn_fs_fs__open(svn_fs_t *fs, const char
 
   /* Read the min unpacked revision. */
   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
-    SVN_ERR(update_min_unpacked_rev(fs, pool));
+    SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
 
   /* Read the configuration file. */
   SVN_ERR(read_config(ffd, fs->path, pool));
@@ -1439,137 +676,6 @@ create_file_ignore_eexist(const char *fi
   return svn_error_trace(err);
 }
 
-/* forward declarations */
-
-static svn_error_t *
-pack_revprops_shard(const char *pack_file_dir,
-                    const char *shard_path,
-                    apr_int64_t shard,
-                    int max_files_per_dir,
-                    apr_off_t max_pack_size,
-                    int compression_level,
-                    svn_cancel_func_t cancel_func,
-                    void *cancel_baton,
-                    apr_pool_t *scratch_pool);
-
-static svn_error_t *
-delete_revprops_shard(const char *shard_path,
-                      apr_int64_t shard,
-                      int max_files_per_dir,
-                      svn_cancel_func_t cancel_func,
-                      void *cancel_baton,
-                      apr_pool_t *scratch_pool);
-
-/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
- * 
- * NOTE: Keep the old non-packed shards around until after the format bump.
- * Otherwise, re-running upgrade will drop the packed revprop shard but
- * have no unpacked data anymore.  Call upgrade_cleanup_pack_revprops after
- * the bump.
- * 
- * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
- * used in the usual way.  Temporary allocations are done in SCRATCH_POOL.
- */
-static svn_error_t *
-upgrade_pack_revprops(svn_fs_t *fs,
-                      svn_fs_upgrade_notify_t notify_func,
-                      void *notify_baton,
-                      svn_cancel_func_t cancel_func,
-                      void *cancel_baton,
-                      apr_pool_t *scratch_pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  const char *revprops_shard_path;
-  const char *revprops_pack_file_dir;
-  apr_int64_t shard;
-  apr_int64_t first_unpacked_shard
-    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
-
-  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
-                                              scratch_pool);
-  int compression_level = ffd->compress_packed_revprops
-                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
-                           : SVN_DELTA_COMPRESSION_LEVEL_NONE;
-
-  /* first, pack all revprops shards to match the packed revision shards */
-  for (shard = 0; shard < first_unpacked_shard; ++shard)
-    {
-      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
-                   apr_psprintf(iterpool,
-                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
-                                shard),
-                   iterpool);
-      revprops_shard_path = svn_dirent_join(revsprops_dir,
-                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
-                       iterpool);
-
-      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
-                                  shard, ffd->max_files_per_dir,
-                                  (int)(0.9 * ffd->revprop_pack_size),
-                                  compression_level,
-                                  cancel_func, cancel_baton, iterpool));
-      if (notify_func)
-        SVN_ERR(notify_func(notify_baton, shard,
-                            svn_fs_upgrade_pack_revprops, iterpool));
-
-      svn_pool_clear(iterpool);
-    }
-
-  svn_pool_destroy(iterpool);
-
-  return SVN_NO_ERROR;
-}
-
-/* In the filesystem FS, remove all non-packed revprop shards up to
- * min_unpacked_rev.  Temporary allocations are done in SCRATCH_POOL.
- * 
- * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
- * used in the usual way.  Cancellation is supported in the sense that we
- * will cleanly abort the operation.  However, there will be remnant shards
- * that must be removed manually.
- *
- * See upgrade_pack_revprops for more info.
- */
-static svn_error_t *
-upgrade_cleanup_pack_revprops(svn_fs_t *fs,
-                              svn_fs_upgrade_notify_t notify_func,
-                              void *notify_baton,
-                              svn_cancel_func_t cancel_func,
-                              void *cancel_baton,
-                              apr_pool_t *scratch_pool)
-{
-  fs_fs_data_t *ffd = fs->fsap_data;
-  const char *revprops_shard_path;
-  apr_int64_t shard;
-  apr_int64_t first_unpacked_shard
-    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
-
-  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
-                                              scratch_pool);
-  
-  /* delete the non-packed revprops shards afterwards */
-  for (shard = 0; shard < first_unpacked_shard; ++shard)
-    {
-      revprops_shard_path = svn_dirent_join(revsprops_dir,
-                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
-                       iterpool);
-      SVN_ERR(delete_revprops_shard(revprops_shard_path,
-                                    shard, ffd->max_files_per_dir,
-                                    cancel_func, cancel_baton, iterpool));
-      if (notify_func)
-        SVN_ERR(notify_func(notify_baton, shard,
-                            svn_fs_upgrade_cleanup_revprops, iterpool));
-
-      svn_pool_clear(iterpool);
-    }
-
-  svn_pool_destroy(iterpool);
-
-  return SVN_NO_ERROR;
-}
-
 /* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying 
  * parameters over between them. */
 struct upgrade_baton_t
@@ -1586,6 +692,7 @@ upgrade_body(void *baton, apr_pool_t *po
 {
   struct upgrade_baton_t *upgrade_baton = baton;
   svn_fs_t *fs = upgrade_baton->fs;
+  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;
@@ -1593,7 +700,6 @@ upgrade_body(void *baton, apr_pool_t *po
 
   /* Read the FS format number and max-files-per-dir setting. */
   SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
-  SVN_ERR(check_format(format));
 
   /* If the config file does not exist, create one. */
   SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
@@ -1621,10 +727,12 @@ upgrade_body(void *baton, apr_pool_t *po
      file', make that file and its corresponding lock file. */
   if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
     {
-      SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
-                                        pool));
-      SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
-                                        pool));
+      SVN_ERR(create_file_ignore_eexist(
+                           svn_fs_fs__path_txn_current(fs, pool), "0\n",
+                           pool));
+      SVN_ERR(create_file_ignore_eexist(
+                           svn_fs_fs__path_txn_current_lock(fs, pool), "",
+                           pool));
     }
 
   /* If our filesystem predates the existance of the 'txn-protorevs'
@@ -1639,7 +747,8 @@ upgrade_body(void *baton, apr_pool_t *po
 
   /* If our filesystem is new enough, write the min unpacked rev file. */
   if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
-    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
+    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
+                               "0\n", pool));
 
   /* If the file system supports revision packing but not revprop packing
      *and* the FS has been sharded, pack the revprops up to the point that
@@ -1650,17 +759,18 @@ upgrade_body(void *baton, apr_pool_t *po
       && max_files_per_dir > 0)
     {
       needs_revprop_shard_cleanup = TRUE;
-      SVN_ERR(upgrade_pack_revprops(fs,
-                                    upgrade_baton->notify_func,
-                                    upgrade_baton->notify_baton,
-                                    upgrade_baton->cancel_func,
-                                    upgrade_baton->cancel_baton,
-                                    pool));
+      SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
+                                               upgrade_baton->notify_func,
+                                               upgrade_baton->notify_baton,
+                                               upgrade_baton->cancel_func,
+                                               upgrade_baton->cancel_baton,
+                                               pool));
     }
 
   /* Bump the format file. */
-  SVN_ERR(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;
+  SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
   if (upgrade_baton->notify_func)
     SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
                                        SVN_FS_FS__FORMAT_NUMBER,
@@ -1669,12 +779,12 @@ upgrade_body(void *baton, apr_pool_t *po
 
   /* Now, it is safe to remove the redundant revprop files. */
   if (needs_revprop_shard_cleanup)
-    SVN_ERR(upgrade_cleanup_pack_revprops(fs,
-                                          upgrade_baton->notify_func,
-                                          upgrade_baton->notify_baton,
-                                          upgrade_baton->cancel_func,
-                                          upgrade_baton->cancel_baton,
-                                          pool));
+    SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
+                                               upgrade_baton->notify_func,
+                                               upgrade_baton->notify_baton,
+                                               upgrade_baton->cancel_func,
+                                               upgrade_baton->cancel_baton,
+                                               pool));
 
   /* Done */
   return SVN_NO_ERROR;
@@ -1699,118 +809,6 @@ svn_fs_fs__upgrade(svn_fs_t *fs,
   return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)&baton, pool);
 }
 
-
-/* Functions for dealing with recoverable errors on mutable files
- *
- * Revprops, current, and txn-current files are mutable; that is, they
- * change as part of normal fsfs operation, in constrat to revs files, or
- * the format file, which are written once at create (or upgrade) time.
- * When more than one host writes to the same repository, we will
- * sometimes see these recoverable errors when accesssing these files.
- *
- * These errors all relate to NFS, and thus we only use this retry code if
- * ESTALE is defined.
- *
- ** ESTALE
- *
- * In NFS v3 and under, the server doesn't track opened files.  If you
- * unlink(2) or rename(2) a file held open by another process *on the
- * same host*, that host's kernel typically renames the file to
- * .nfsXXXX and automatically deletes that when it's no longer open,
- * but this behavior is not required.
- *
- * For obvious reasons, this does not work *across hosts*.  No one
- * knows about the opened file; not the server, and not the deleting
- * client.  So the file vanishes, and the reader gets stale NFS file
- * handle.
- *
- ** EIO, ENOENT
- *
- * Some client implementations (at least the 2.6.18.5 kernel that ships
- * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
- * even EIO errors when trying to read these files that have been renamed
- * over on some other host.
- *
- ** Solution
- *
- * Try open and read of such files in try_stringbuf_from_file().  Call
- * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
- * (though, realistically, the second try will succeed).
- */
-
-#define RECOVERABLE_RETRY_COUNT 10
-
-/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
- * not be modified unless the whole file was read successfully.
- *
- * ESTALE, EIO and ENOENT will not cause this function to return an error
- * unless LAST_ATTEMPT has been set.  If MISSING is not NULL, indicate
- * missing files (ENOENT) there.
- *
- * Use POOL for allocations.
- */
-static svn_error_t *
-try_stringbuf_from_file(svn_stringbuf_t **content,
-                        svn_boolean_t *missing,
-                        const char *path,
-                        svn_boolean_t last_attempt,
-                        apr_pool_t *pool)
-{
-  svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
-  if (missing)
-    *missing = FALSE;
-
-  if (err)
-    {
-      *content = NULL;
-
-      if (APR_STATUS_IS_ENOENT(err->apr_err))
-        {
-          if (!last_attempt)
-            {
-              svn_error_clear(err);
-              if (missing)
-                *missing = TRUE;
-              return SVN_NO_ERROR;
-            }
-        }
-#ifdef ESTALE
-      else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
-                || APR_TO_OS_ERROR(err->apr_err) == EIO)
-        {
-          if (!last_attempt)
-            {
-              svn_error_clear(err);
-              return SVN_NO_ERROR;
-            }
-        }
-#endif
-    }
-
-  return svn_error_trace(err);
-}
-
-/* Read the 'current' file FNAME and store the contents in *BUF.
-   Allocations are performed in POOL. */
-static svn_error_t *
-read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
-{
-  int i;
-  *content = NULL;
-
-  for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
-    SVN_ERR(try_stringbuf_from_file(content, NULL,
-                                    fname, i + 1 < RECOVERABLE_RETRY_COUNT,
-                                    pool));
-
-  if (!*content)
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Can't read '%s'"),
-                             svn_dirent_local_style(fname, pool));
-
-  return SVN_NO_ERROR;
-}
-
 /* Find the youngest revision in a repository at path FS_PATH and
    return it in *YOUNGEST_P.  Perform temporary allocations in
    POOL. */
@@ -1820,8 +818,10 @@ get_youngest(svn_revnum_t *youngest_p,
              apr_pool_t *pool)
 {
   svn_stringbuf_t *buf;
-  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
-                       pool));
+  SVN_ERR(svn_fs_fs__read_content(&buf,
+                                  svn_dirent_join(fs_path, PATH_CURRENT,
+                                                  pool),
+                                  pool));
 
   *youngest_p = SVN_STR_TO_REV(buf->data);
 
@@ -1842,82 +842,10 @@ svn_fs_fs__youngest_rev(svn_revnum_t *yo
   return SVN_NO_ERROR;
 }
 
-/* Given a revision file FILE that has been pre-positioned at the
-   beginning of a Node-Rev header block, read in that header block and
-   store it in the apr_hash_t HEADERS.  All allocations will be from
-   POOL. */
-static svn_error_t * read_header_block(apr_hash_t **headers,
-                                       svn_stream_t *stream,
-                                       apr_pool_t *pool)
-{
-  *headers = apr_hash_make(pool);
-
-  while (1)
-    {
-      svn_stringbuf_t *header_str;
-      const char *name, *value;
-      apr_size_t i = 0;
-      svn_boolean_t eof;
-
-      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
-
-      if (eof || header_str->len == 0)
-        break; /* end of header block */
-
-      while (header_str->data[i] != ':')
-        {
-          if (header_str->data[i] == '\0')
-            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                     _("Found malformed header '%s' in "
-                                       "revision file"),
-                                     header_str->data);
-          i++;
-        }
-
-      /* Create a 'name' string and point to it. */
-      header_str->data[i] = '\0';
-      name = header_str->data;
-
-      /* Skip over the NULL byte and the space following it. */
-      i += 2;
-
-      if (i > header_str->len)
-        {
-          /* Restore the original line for the error. */
-          i -= 2;
-          header_str->data[i] = ':';
-          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                   _("Found malformed header '%s' in "
-                                     "revision file"),
-                                   header_str->data);
-        }
-
-      value = header_str->data + i;
-
-      /* header_str is safely in our pool, so we can use bits of it as
-         key and value. */
-      svn_hash_sets(*headers, name, value);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
-   than the current youngest revision or is simply not a valid
-   revision number, else return success.
-
-   FSFS is based around the concept that commits only take effect when
-   the number in "current" is bumped.  Thus if there happens to be a rev
-   or revprops file installed for a revision higher than the one recorded
-   in "current" (because a commit failed between installing the rev file
-   and bumping "current", or because an administrator rolled back the
-   repository by resetting "current" without deleting rev files, etc), it
-   ought to be completely ignored.  This function provides the check
-   by which callers can make that decision. */
-static svn_error_t *
-ensure_revision_exists(svn_fs_t *fs,
-                       svn_revnum_t rev,
-                       apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
+                                  svn_fs_t *fs,
+                                  apr_pool_t *pool)
 {
   fs_fs_data_t *ffd = fs->fsap_data;
 
@@ -1942,9627 +870,483 @@ ensure_revision_exists(svn_fs_t *fs,
 }
 
 svn_error_t *
-svn_fs_fs__revision_exists(svn_revnum_t rev,
-                           svn_fs_t *fs,
-                           apr_pool_t *pool)
+svn_fs_fs__file_length(svn_filesize_t *length,
+                       node_revision_t *noderev,
+                       apr_pool_t *pool)
 {
-  /* Different order of parameters. */
-  SVN_ERR(ensure_revision_exists(fs, rev, pool));
+  if (noderev->data_rep)
+    *length = noderev->data_rep->expanded_size;
+  else
+    *length = 0;
+
   return SVN_NO_ERROR;
 }
 
-/* Open the correct revision file for REV.  If the filesystem FS has
-   been packed, *FILE will be set to the packed file; otherwise, set *FILE
-   to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
-   file doesn't exist.
-
-   TODO: Consider returning an indication of whether this is a packed rev
-         file, so the caller need not rely on is_packed_rev() which in turn
-         relies on the cached FFD->min_unpacked_rev value not having changed
-         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_boolean_t
+svn_fs_fs__noderev_same_rep_key(representation_t *a,
+                                representation_t *b)
 {
-  fs_fs_data_t *ffd = fs->fsap_data;
-  svn_error_t *err;
-  const char *path;
-  svn_boolean_t retry = FALSE;
-
-  do
-    {
-      err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
-
-      /* open the revision file in buffered r/o mode */
-      if (! err)
-        err = svn_io_file_open(file, path,
-                              APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
+  if (a == b)
+    return TRUE;
 
-      if (err && APR_STATUS_IS_ENOENT(err->apr_err))
-        {
-          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
-            {
-              /* Could not open the file. This may happen if the
-               * file once existed but got packed later. */
-              svn_error_clear(err);
-
-              /* if that was our 2nd attempt, leave it at that. */
-              if (retry)
-                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
-                                         _("No such revision %ld"), rev);
+  if (a == NULL || b == NULL)
+    return FALSE;
 
-              /* We failed for the first time. Refresh cache & retry. */
-              SVN_ERR(update_min_unpacked_rev(fs, pool));
+  if (a->offset != b->offset)
+    return FALSE;
 
-              retry = TRUE;
-            }
-          else
-            {
-              svn_error_clear(err);
-              return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
-                                       _("No such revision %ld"), rev);
-            }
-        }
-      else
-        {
-          retry = FALSE;
-        }
-    }
-  while (retry);
+  if (a->revision != b->revision)
+    return FALSE;
 
-  return svn_error_trace(err);
+  return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
 }
 
-/* Reads a line from STREAM and converts it to a 64 bit integer to be
- * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
- * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
- * error return.
- * SCRATCH_POOL is used for temporary allocations.
- */
-static svn_error_t *
-read_number_from_stream(apr_int64_t *result,
-                        svn_boolean_t *hit_eof,
-                        svn_stream_t *stream,
-                        apr_pool_t *scratch_pool)
+svn_error_t *
+svn_fs_fs__file_checksum(svn_checksum_t **checksum,
+                         node_revision_t *noderev,
+                         svn_checksum_kind_t kind,
+                         apr_pool_t *pool)
 {
-  svn_stringbuf_t *sb;
-  svn_boolean_t eof;
-  svn_error_t *err;
-
-  SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
-  if (hit_eof)
-    *hit_eof = eof;
-  else
-    if (eof)
-      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
+  *checksum = NULL;
 
-  if (!eof)
+  if (noderev->data_rep)
     {
-      err = svn_cstring_atoi64(result, sb->data);
-      if (err)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
-                                 _("Number '%s' invalid or too large"),
-                                 sb->data);
+      svn_checksum_t temp;
+      temp.kind = kind;
+      
+      switch(kind)
+        {
+          case svn_checksum_md5:
+            temp.digest = noderev->data_rep->md5_digest;
+            break;
+
+          case svn_checksum_sha1:
+            if (! noderev->data_rep->has_sha1)
+              return SVN_NO_ERROR;
+
+            temp.digest = noderev->data_rep->sha1_digest;
+            break;
+
+          default:
+            return SVN_NO_ERROR;
+        }
+
+      *checksum = svn_checksum_dup(&temp, pool);
     }
 
   return SVN_NO_ERROR;
 }
 
-/* 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)
+representation_t *
+svn_fs_fs__rep_copy(representation_t *rep,
+                    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;
+  if (rep == NULL)
+    return NULL;
 
-  /* 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;
+  return apr_pmemdup(pool, rep, sizeof(*rep));
+}
 
-      svn_pool_clear(iterpool);
-      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
-      if (eof)
-        break;
 
-      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
-    }
-  svn_pool_destroy(iterpool);
+/* Write out the zeroth revision for filesystem FS. */
+static svn_error_t *
+write_revision_zero(svn_fs_t *fs)
+{
+  const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, fs->pool);
+  apr_hash_t *proplist;
+  svn_string_t date;
 
-  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
-                              apr_off_t);
+  /* Write out a rev file for revision 0. */
+  SVN_ERR(svn_io_file_create(path_revision_zero,
+                             "PLAIN\nEND\nENDREP\n"
+                             "id: 0.0.r0/17\n"
+                             "type: dir\n"
+                             "count: 0\n"
+                             "text: 0 0 4 4 "
+                             "2d2977d1c96f487abe4a1e202dd03b4e\n"
+                             "cpath: /\n"
+                             "\n\n17 107\n", fs->pool));
+  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
 
-  /* 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);
+  /* Set a date on revision 0. */
+  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
+  date.len = strlen(date.data);
+  proplist = apr_hash_make(fs->pool);
+  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
+  return svn_fs_fs__set_revision_proplist(fs, 0, proplist, fs->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)
+svn_error_t *
+svn_fs_fs__create(svn_fs_t *fs,
+                  const char *path,
+                  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));
+  int format = SVN_FS_FS__FORMAT_NUMBER;
+  fs_fs_data_t *ffd = fs->fsap_data;
 
-  if (is_packed_rev(fs, rev))
+  fs->path = apr_pstrdup(pool, path);
+  /* See if compatibility with older versions was explicitly requested. */
+  if (fs->config)
     {
-      apr_off_t rev_offset;
-
-      SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
-      offset += rev_offset;
+      if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
+        format = 1;
+      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
+        format = 2;
+      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
+        format = 3;
+      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
+        format = 4;
     }
+  ffd->format = format;
 
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
-  *file = rev_file;
+  /* Override the default linear layout if this is a new-enough format. */
+  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+    ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
 
-  return SVN_NO_ERROR;
-}
+  /* Create the revision data directories. */
+  if (ffd->max_files_per_dir)
+    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
+                                                                  pool),
+                                        pool));
+  else
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
+                                                        pool),
+                                        pool));
 
-/* 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;
+  /* Create the revprops directory. */
+  if (ffd->max_files_per_dir)
+    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
+                                                                       pool),
+                                        pool));
+  else
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
+                                                        PATH_REVPROPS_DIR,
+                                                        pool),
+                                        pool));
 
-  SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
-                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+  /* Create the transaction directory. */
+  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
+                                                      pool),
+                                      pool));
 
-  offset = rep->offset;
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+  /* Create the protorevs directory. */
+  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
+                                                      pool),
+                                        pool));
 
-  *file = rev_file;
+  /* Create the 'current' file. */
+  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
+                             (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
+                              ? "0\n" : "0 1 1\n"),
+                             pool));
+  SVN_ERR(svn_io_file_create_empty(path_lock(fs, pool), pool));
+  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
 
-  return SVN_NO_ERROR;
-}
+  SVN_ERR(write_revision_zero(fs));
 
-/* 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);
-}
+  SVN_ERR(write_config(fs, pool));
 
-/* Parse the description of a representation from STRING and store it
-   into *REP_P.  If the representation is mutable (the revision is
-   given as -1), then use TXN_ID for the representation's txn_id
-   field.  If MUTABLE_REP_TRUNCATED is true, then this representation
-   is for property or directory contents, and no information will be
-   expected except the "-1" revision number for a mutable
-   representation.  Allocate *REP_P in POOL. */
-static svn_error_t *
-read_rep_offsets_body(representation_t **rep_p,
-                      char *string,
-                      const char *txn_id,
-                      svn_boolean_t mutable_rep_truncated,
-                      apr_pool_t *pool)
-{
-  representation_t *rep;
-  char *str;
-  apr_int64_t val;
-
-  rep = apr_pcalloc(pool, sizeof(*rep));
-  *rep_p = rep;
-
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
+  SVN_ERR(read_config(ffd, fs->path, pool));
 
+  /* Create the min unpacked rev file. */
+  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
+                               "0\n", pool));
 
-  rep->revision = SVN_STR_TO_REV(str);
-  if (rep->revision == SVN_INVALID_REVNUM)
+  /* Create the txn-current file if the repository supports
+     the transaction sequence file. */
+  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
     {
-      rep->txn_id = txn_id;
-      if (mutable_rep_truncated)
-        return SVN_NO_ERROR;
+      SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
+                                 "0\n", pool));
+      SVN_ERR(svn_io_file_create_empty(
+                                 svn_fs_fs__path_txn_current_lock(fs, pool),
+                                 pool));
     }
 
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
-
-  SVN_ERR(svn_cstring_atoi64(&val, str));
-  rep->offset = (apr_off_t)val;
-
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
-
-  SVN_ERR(svn_cstring_atoi64(&val, str));
-  rep->size = (svn_filesize_t)val;
-
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
-
-  SVN_ERR(svn_cstring_atoi64(&val, str));
-  rep->expanded_size = (svn_filesize_t)val;
-
-  /* Read in the MD5 hash. */
-  str = svn_cstring_tokenize(" ", &string);
-  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
+  /* This filesystem is ready.  Stamp it with a format number. */
+  SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
 
-  SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
-                                 pool));
+  ffd->youngest_rev_cache = 0;
+  return SVN_NO_ERROR;
+}
 
-  /* The remaining fields are only used for formats >= 4, so check that. */
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return SVN_NO_ERROR;
+svn_error_t *
+svn_fs_fs__set_uuid(svn_fs_t *fs,
+                    const char *uuid,
+                    apr_pool_t *pool)
+{
+  char *my_uuid;
+  apr_size_t my_uuid_len;
+  const char *uuid_path = path_uuid(fs, pool);
 
-  /* Read the SHA1 hash. */
-  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
+  if (! uuid)
+    uuid = svn_uuid_generate(pool);
 
-  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
-                                 pool));
+  /* Make sure we have a copy in FS->POOL, and append a newline. */
+  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", SVN_VA_NULL);
+  my_uuid_len = strlen(my_uuid);
 
-  /* Read the uniquifier. */
-  str = svn_cstring_tokenize(" ", &string);
-  if (str == NULL)
-    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                            _("Malformed text representation offset line in node-rev"));
+  /* We use the permissions of the 'current' file, because the 'uuid'
+     file does not exist during repository creation. */
+  SVN_ERR(svn_io_write_atomic(uuid_path, my_uuid, my_uuid_len,
+                              svn_fs_fs__path_current(fs, pool) /* perms */,
+                              pool));
 
-  rep->uniquifier = apr_pstrdup(pool, str);
+  /* Remove the newline we added, and stash the UUID. */
+  my_uuid[my_uuid_len - 1] = '\0';
+  fs->uuid = my_uuid;
 
   return SVN_NO_ERROR;
 }
 
-/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
-   and adding an error message. */
-static svn_error_t *
-read_rep_offsets(representation_t **rep_p,
-                 char *string,
-                 const svn_fs_id_t *noderev_id,
-                 svn_boolean_t mutable_rep_truncated,
-                 apr_pool_t *pool)
-{
-  svn_error_t *err;
-  const char *txn_id;
-
-  if (noderev_id)
-    txn_id = svn_fs_fs__id_txn_id(noderev_id);
-  else
-    txn_id = NULL;
+/** Node origin lazy cache. */
 
-  err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
-                              pool);
-  if (err)
+/* If directory PATH does not exist, create it and give it the same
+   permissions as FS_path.*/
+svn_error_t *
+svn_fs_fs__ensure_dir_exists(const char *path,
+                             const char *fs_path,
+                             apr_pool_t *pool)
+{
+  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
+  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
     {
-      const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
-      const char *where;
-      where = apr_psprintf(pool,
-                           _("While reading representation offsets "
-                             "for node-revision '%s':"),
-                           noderev_id ? id_unparsed->data : "(null)");
-
-      return svn_error_quick_wrap(err, where);
+      svn_error_clear(err);
+      return SVN_NO_ERROR;
     }
-  else
-    return SVN_NO_ERROR;
-}
+  SVN_ERR(err);
 
-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);
+  /* We successfully created a new directory.  Dup the permissions
+     from FS->path. */
+  return svn_io_copy_perms(fs_path, path, pool);
 }
 
-/* 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.
- */
+/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
+   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
 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)
+get_node_origins_from_file(svn_fs_t *fs,
+                           apr_hash_t **node_origins,
+                           const char *node_origins_file,
+                           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 = { 0 };
+  apr_file_t *fd;
+  svn_error_t *err;
+  svn_stream_t *stream;
 
-      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));
+  *node_origins = NULL;
+  err = svn_io_file_open(&fd, node_origins_file,
+                         APR_READ, APR_OS_DEFAULT, pool);
+  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+    {
+      svn_error_clear(err);
+      return SVN_NO_ERROR;
     }
+  SVN_ERR(err);
 
-  return SVN_NO_ERROR;
+  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
+  *node_origins = apr_hash_make(pool);
+  SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
+  return svn_stream_close(stream);
 }
 
-/* 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)
+svn_error_t *
+svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
+                           svn_fs_t *fs,
+                           const svn_fs_fs__id_part_t *node_id,
+                           apr_pool_t *pool)
 {
-  fs_fs_data_t *ffd = fs->fsap_data;
+  apr_hash_t *node_origins;
 
-  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
+  *origin_id = NULL;
+  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
+                                     svn_fs_fs__path_node_origin(fs, node_id,
+                                                                 pool),
+                                     pool));
+  if (node_origins)
     {
-      pair_cache_key_t key = { 0 };
+      char node_id_ptr[SVN_INT64_BUFFER_SIZE];
+      apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
+      svn_string_t *origin_id_str
+        = apr_hash_get(node_origins, node_id_ptr, len);
 
-      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);
+      if (origin_id_str)
+        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
+                                         origin_id_str->len, 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. */
+
+/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
+   pair and adds it to the NODE_ORIGINS_PATH file.  */
 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)
+set_node_origins_for_file(svn_fs_t *fs,
+                          const char *node_origins_path,
+                          const svn_fs_fs__id_part_t *node_id,
+                          svn_string_t *node_rev_id,
+                          apr_pool_t *pool)
 {
-  apr_file_t *revision_file;
-  svn_error_t *err;
-  svn_boolean_t is_cached = FALSE;
+  const char *path_tmp;
+  svn_stream_t *stream;
+  apr_hash_t *origins_hash;
+  svn_string_t *old_node_rev_id;
 
-  /* 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;
+  /* the hash serialization functions require strings as keys */
+  char node_id_ptr[SVN_INT64_BUFFER_SIZE];
+  apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
 
-  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));
-
-  /* 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__read_noderev(node_revision_t **noderev_p,
-                        svn_stream_t *stream,
-                        apr_pool_t *pool)
-{
-  apr_hash_t *headers;
-  node_revision_t *noderev;
-  char *value;
-  const char *noderev_id;
-
-  SVN_ERR(read_header_block(&headers, stream, pool));
-
-  noderev = apr_pcalloc(pool, sizeof(*noderev));
-
-  /* Read the node-rev id. */
-  value = svn_hash_gets(headers, HEADER_ID);
-  if (value == NULL)
-      /* ### More information: filename/offset coordinates */
-      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                              _("Missing id field in node-rev"));
-
-  SVN_ERR(svn_stream_close(stream));
+  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
+                                                       PATH_NODE_ORIGINS_DIR,
+                                                       pool),
+                                       fs->path, pool));
 
-  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
-  noderev_id = value; /* for error messages later */
+  /* Read the previously existing origins (if any), and merge our
+     update with it. */
+  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
+                                     node_origins_path, pool));
+  if (! origins_hash)
+    origins_hash = apr_hash_make(pool);
 
-  /* Read the type. */
-  value = svn_hash_gets(headers, HEADER_TYPE);
+  old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
 
-  if ((value == NULL) ||
-      (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
-    /* ### s/kind/type/ */
+  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Missing kind field in node-rev '%s'"),
-                             noderev_id);
-
-  noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
-    : svn_node_dir;
-
-  /* Read the 'count' field. */
-  value = svn_hash_gets(headers, HEADER_COUNT);
-  if (value)
-    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
-  else
-    noderev->predecessor_count = 0;
-
-  /* Get the properties location. */
-  value = svn_hash_gets(headers, HEADER_PROPS);
-  if (value)
-    {
-      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
-                               noderev->id, TRUE, pool));
-    }
-
-  /* Get the data location. */
-  value = svn_hash_gets(headers, HEADER_TEXT);
-  if (value)
-    {
-      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
-                               noderev->id,
-                               (noderev->kind == svn_node_dir), pool));
-    }
-
-  /* Get the created path. */
-  value = svn_hash_gets(headers, HEADER_CPATH);
-  if (value == NULL)
-    {
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                               _("Missing cpath field in node-rev '%s'"),
-                               noderev_id);
-    }
-  else
-    {
-      noderev->created_path = apr_pstrdup(pool, value);
-    }
-
-  /* Get the predecessor ID. */
-  value = svn_hash_gets(headers, HEADER_PRED);
-  if (value)
-    noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
-                                                  pool);
-
-  /* Get the copyroot. */
-  value = svn_hash_gets(headers, HEADER_COPYROOT);
-  if (value == NULL)
-    {
-      noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
-      noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
-    }
-  else
-    {
-      char *str;
-
-      str = svn_cstring_tokenize(" ", &value);
-      if (str == NULL)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Malformed copyroot line in node-rev '%s'"),
-                                 noderev_id);
-
-      noderev->copyroot_rev = SVN_STR_TO_REV(str);
-
-      if (*value == '\0')
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Malformed copyroot line in node-rev '%s'"),
-                                 noderev_id);
-      noderev->copyroot_path = apr_pstrdup(pool, value);
-    }
-
-  /* Get the copyfrom. */
-  value = svn_hash_gets(headers, HEADER_COPYFROM);
-  if (value == NULL)
-    {
-      noderev->copyfrom_path = NULL;
-      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
-    }
-  else
-    {
-      char *str = svn_cstring_tokenize(" ", &value);
-      if (str == NULL)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Malformed copyfrom line in node-rev '%s'"),
-                                 noderev_id);
-
-      noderev->copyfrom_rev = SVN_STR_TO_REV(str);
-
-      if (*value == 0)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Malformed copyfrom line in node-rev '%s'"),
-                                 noderev_id);
-      noderev->copyfrom_path = apr_pstrdup(pool, value);
-    }
+                             _("Node origin for '%s' exists with a different "
+                               "value (%s) than what we were about to store "
+                               "(%s)"),
+                             node_id_ptr, old_node_rev_id->data,
+                             node_rev_id->data);
 
-  /* Get whether this is a fresh txn root. */
-  value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
-  noderev->is_fresh_txn_root = (value != NULL);
-
-  /* Get the mergeinfo count. */
-  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
-  if (value)
-    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
-  else
-    noderev->mergeinfo_count = 0;
+  apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
 
-  /* Get whether *this* node has mergeinfo. */
-  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
-  noderev->has_mergeinfo = (value != NULL);
+  /* Sure, there's a race condition here.  Two processes could be
+     trying to add different cache elements to the same file at the
+     same time, and the entries added by the first one to write will
+     be lost.  But this is just a cache of reconstructible data, so
+     we'll accept this problem in return for not having to deal with
+     locking overhead. */
 
-  *noderev_p = noderev;
+  /* Create a temporary file, write out our hash, and close the file. */
+  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
+                                 svn_dirent_dirname(node_origins_path, pool),
+                                 svn_io_file_del_none, pool, pool));
+  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
+  SVN_ERR(svn_stream_close(stream));
 
-  return SVN_NO_ERROR;
+  /* Rename the temp file as the real destination */
+  return svn_io_file_rename(path_tmp, node_origins_path, 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_fs_fs__set_node_origin(svn_fs_t *fs,
+                           const svn_fs_fs__id_part_t *node_id,
+                           const svn_fs_id_t *node_rev_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_error_t *err;
+  const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
+
+  err = set_node_origins_for_file(fs, filename,
+                                  node_id,
+                                  svn_fs_fs__id_unparse(node_rev_id, pool),
+                                  pool);
+  if (err && APR_STATUS_IS_EACCES(err->apr_err))
     {
-      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);
+      /* It's just a cache; stop trying if I can't write. */
+      svn_error_clear(err);
+      err = NULL;
     }
   return svn_error_trace(err);
 }
 
 
-/* Return a formatted string, compatible with filesystem format FORMAT,
-   that represents the location of representation REP.  If
-   MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
-   and only a "-1" revision number will be given for a mutable rep.
-   If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
-   Perform the allocation from POOL.  */
-static const char *
-representation_string(representation_t *rep,
-                      int format,
-                      svn_boolean_t mutable_rep_truncated,
-                      svn_boolean_t may_be_corrupt,
-                      apr_pool_t *pool)
-{
-  if (rep->txn_id && mutable_rep_truncated)
-    return "-1";
-
-#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum)          \
-  ((!may_be_corrupt || (checksum) != NULL)     \
-   ? svn_checksum_to_cstring_display((checksum), pool) \
-   : "(null)")
-
-  if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
-    return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
-                        " %" SVN_FILESIZE_T_FMT " %s",
-                        rep->revision, rep->offset, rep->size,

[... 8987 lines stripped ...]