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/10/15 10:52:18 UTC

svn commit: r1532250 [14/37] - in /subversion/branches/cache-server: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ contrib/client-side/emacs/ contrib/hook-scripts/ contrib/server-side/fsfsfixer/ contrib/se...

Modified: subversion/branches/cache-server/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/subversion/libsvn_fs_fs/fs_fs.c?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/cache-server/subversion/libsvn_fs_fs/fs_fs.c Tue Oct 15 08:52:06 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,366 +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,
@@ -601,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));
     }
 
@@ -653,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);
@@ -685,323 +211,60 @@ 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. */
-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);
-}
+/* Read the format number and maximum number of files per directory
+   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
+   respectively.
 
-/* 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;
-};
+   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
+   will be set to zero if a linear scheme should be used.
 
-/* Callback used in the implementation of get_writable_proto_rev(). */
+   Use POOL for temporary allocation. */
 static svn_error_t *
-get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+read_format(int *pformat, int *max_files_per_dir,
+            const char *path, 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);
+  svn_stream_t *stream;
+  svn_stringbuf_t *content;
+  svn_stringbuf_t *buf;
+  svn_boolean_t eos = FALSE;
 
-  /* 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);
-}
-
-/* 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)
-{
-  svn_error_t *err;
-  svn_stream_t *stream;
-  svn_stringbuf_t *content;
-  svn_stringbuf_t *buf;
-  svn_boolean_t eos = FALSE;
-
-  err = svn_stringbuf_from_file2(&content, path, pool);
-  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+  err = svn_stringbuf_from_file2(&content, path, pool);
+  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
     {
       /* Treat an absent format file as format 1.  Do not try to
          create the format file on the fly, because the repository
@@ -1073,21 +336,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");
     }
@@ -1102,15 +369,8 @@ write_format(const char *path, int forma
     }
   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));
-
-      /* rename the temp file as the real destination */
-      SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
+      SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
+                                  NULL /* copy_perms_path */, pool));
     }
 
   /* And set the perms to make it read only */
@@ -1359,37 +619,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)
 {
@@ -1422,7 +651,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));
@@ -1445,92 +674,27 @@ 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.
- * Use SCRATCH_POOL for temporary allocations.
- */
-static svn_error_t *
-upgrade_pack_revprops(svn_fs_t *fs,
-                      apr_pool_t *scratch_pool)
+/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying 
+ * parameters over between them. */
+struct upgrade_baton_t
 {
-  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,
-                                  NULL, NULL, iterpool));
-      svn_pool_clear(iterpool);
-    }
-
-  /* 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,
-                                    NULL, NULL, iterpool));
-      svn_pool_clear(iterpool);
-    }
-
-  svn_pool_destroy(iterpool);
-
-  return SVN_NO_ERROR;
-}
+  svn_fs_t *fs;
+  svn_fs_upgrade_notify_t notify_func;
+  void *notify_baton;
+  svn_cancel_func_t cancel_func;
+  void *cancel_baton;
+};
 
 static svn_error_t *
 upgrade_body(void *baton, apr_pool_t *pool)
 {
-  svn_fs_t *fs = baton;
+  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;
+  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
 
   /* Read the FS format number and max-files-per-dir setting. */
   SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
@@ -1562,10 +726,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'
@@ -1580,136 +746,66 @@ 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,
-     pack the revprops up to the point that revision data has been packed. */
+  /* 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
+     revision data has been packed.  However, keep the non-packed revprop
+     files around until after the format bump */
   if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
-      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
-    SVN_ERR(upgrade_pack_revprops(fs, pool));
+      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
+      && max_files_per_dir > 0)
+    {
+      needs_revprop_shard_cleanup = TRUE;
+      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. */
-  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;
+  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,
+                                       svn_fs_upgrade_format_bumped,
+                                       pool));
 
+  /* Now, it is safe to remove the redundant revprop files. */
+  if (needs_revprop_shard_cleanup)
+    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));
 
-svn_error_t *
-svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
-{
-  return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
+  /* Done */
+  return SVN_NO_ERROR;
 }
 
 
-/* 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)
+svn_error_t *
+svn_fs_fs__upgrade(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 *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;
+  struct upgrade_baton_t baton;
+  baton.fs = fs;
+  baton.notify_func = notify_func;
+  baton.notify_baton = notify_baton;
+  baton.cancel_func = cancel_func;
+  baton.cancel_baton = cancel_baton;
+  
+  return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)&baton, pool);
 }
 
 /* Find the youngest revision in a repository at path FS_PATH and
@@ -1721,8 +817,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);
 
@@ -1743,82 +841,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;
 
@@ -1843,9631 +869,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;
+  *checksum = NULL;
 
-  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"));
-
-  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;
 
-  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)
-{
-  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;
+          case svn_checksum_sha1:
+            if (! noderev->data_rep->has_sha1)
+              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;
+            temp.digest = noderev->data_rep->sha1_digest;
+            break;
 
-      svn_pool_clear(iterpool);
-      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
-      if (eof)
-        break;
+          default:
+            return SVN_NO_ERROR;
+        }
 
-      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
+      *checksum = svn_checksum_dup(&temp, pool);
     }
-  svn_pool_destroy(iterpool);
 
-  *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);
+  return SVN_NO_ERROR;
 }
 
-/* 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)
+representation_t *
+svn_fs_fs__rep_copy(representation_t *rep,
+                    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 (rep == NULL)
+    return NULL;
 
-  if (is_packed_rev(fs, rev))
-    {
-      apr_off_t rev_offset;
+  return apr_pmemdup(pool, rep, sizeof(*rep));
+}
 
-      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));
+/* 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;
 
-  *file = rev_file;
+  /* 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));
 
-  return SVN_NO_ERROR;
+  /* 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 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)
+svn_error_t *
+svn_fs_fs__create(svn_fs_t *fs,
+                  const char *path,
+                  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));
+  int format = SVN_FS_FS__FORMAT_NUMBER;
+  fs_fs_data_t *ffd = fs->fsap_data;
 
-  offset = rep->offset;
-  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+  fs->path = apr_pstrdup(pool, path);
+  /* See if compatibility with older versions was explicitly requested. */
+  if (fs->config)
+    {
+      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;
 
-  *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));
 
-/* 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);
+  /* 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
-    return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
-}
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
+                                                        PATH_REVPROPS_DIR,
+                                                        pool),
+                                        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"));
+  /* Create the transaction directory. */
+  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
+                                                      pool),
+                                      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));
 
-  rep->revision = SVN_STR_TO_REV(str);
-  if (rep->revision == SVN_INVALID_REVNUM)
-    {
-      rep->txn_id = txn_id;
-      if (mutable_rep_truncated)
-        return SVN_NO_ERROR;
-    }
+  /* 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));
 
-  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"));
+  SVN_ERR(write_revision_zero(fs));
 
-  SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
-                                 pool));
+  SVN_ERR(write_config(fs, pool));
 
-  /* 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_ERR(read_config(ffd, fs->path, 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"));
+  /* 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));
 
-  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
+  /* Create the txn-current file if the repository supports
+     the transaction sequence file. */
+  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+    {
+      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));
+    }
 
-  /* 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"));
-
-  rep->uniquifier = apr_pstrdup(pool, str);
+  /* This filesystem is ready.  Stamp it with a format number. */
+  SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
 
+  ffd->youngest_rev_cache = 0;
   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 *
+svn_fs_fs__set_uuid(svn_fs_t *fs,
+                    const char *uuid,
+                    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;
-
-  err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
-                              pool);
-  if (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)");
+  char *my_uuid;
+  apr_size_t my_uuid_len;
+  const char *uuid_path = path_uuid(fs, pool);
 
-      return svn_error_quick_wrap(err, where);
-    }
-  else
-    return SVN_NO_ERROR;
-}
+  if (! uuid)
+    uuid = svn_uuid_generate(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);
-}
+  /* Make sure we have a copy in FS->POOL, and append a newline. */
+  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
+  my_uuid_len = strlen(my_uuid);
 
-/* 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 = { 0 };
+  /* 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));
 
-      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));
-    }
+  /* Remove the newline we added, and stash the UUID. */
+  my_uuid[my_uuid_len - 1] = '\0';
+  fs->uuid = my_uuid;
 
   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;
+/** Node origin lazy cache. */
 
-  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
+/* 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))
     {
-      pair_cache_key_t key = { 0 };
-
-      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);
+      svn_error_clear(err);
+      return SVN_NO_ERROR;
     }
+  SVN_ERR(err);
 
-  return SVN_NO_ERROR;
+  /* We successfully created a new directory.  Dup the permissions
+     from FS->path. */
+  return svn_io_copy_perms(fs_path, path, pool);
 }
 
-/* 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. */
+/* 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_node_revision_body(node_revision_t **noderev_p,
-                       svn_fs_t *fs,
-                       const svn_fs_id_t *id,
-                       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)
 {
-  apr_file_t *revision_file;
+  apr_file_t *fd;
   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);
-    }
+  svn_stream_t *stream;
 
-  if (err)
+  *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))
     {
-      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_error_clear(err);
+      return SVN_NO_ERROR;
     }
+  SVN_ERR(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);
+  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);
 }
 
 svn_error_t *
-svn_fs_fs__read_noderev(node_revision_t **noderev_p,
-                        svn_stream_t *stream,
-                        apr_pool_t *pool)
+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)
 {
-  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));
-
-  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
-  noderev_id = value; /* for error messages later */
+  apr_hash_t *node_origins;
 
-  /* Read the type. */
-  value = svn_hash_gets(headers, HEADER_TYPE);
+  *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)
+    {
+      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);
 
-  if ((value == NULL) ||
-      (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
-    /* ### s/kind/type/ */
-    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                             _("Missing kind field in node-rev '%s'"),
-                             noderev_id);
+      if (origin_id_str)
+        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
+                                         origin_id_str->len, pool);
+    }
+  return SVN_NO_ERROR;
+}
 
-  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));
-    }
+/* 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 *
+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)
+{
+  const char *path_tmp;
+  svn_stream_t *stream;
+  apr_hash_t *origins_hash;
+  svn_string_t *old_node_rev_id;
 
-  /* 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));
-    }
+  /* 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);
 
-  /* 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);
-    }
+  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
+                                                       PATH_NODE_ORIGINS_DIR,
+                                                       pool),
+                                       fs->path, pool));
 
-  /* 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;
+  /* 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);
 
-      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);
-    }
+  old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
 
-  /* 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);
-    }
+  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
+    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                             _("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,
-                        rep->expanded_size,
-                        DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
-
-  return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
-                      " %" SVN_FILESIZE_T_FMT " %s %s %s",
-                      rep->revision, rep->offset, rep->size,
-                      rep->expanded_size,
-                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
-                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
-                      rep->uniquifier);
-
-#undef DISPLAY_MAYBE_NULL_CHECKSUM
-
-}
-
+
+/*** Revisions ***/
 
 svn_error_t *
-svn_fs_fs__write_noderev(svn_stream_t *outfile,
-                         node_revision_t *noderev,
-                         int format,
-                         svn_boolean_t include_mergeinfo,
+svn_fs_fs__revision_prop(svn_string_t **value_p,
+                         svn_fs_t *fs,
+                         svn_revnum_t rev,
+                         const char *propname,
                          apr_pool_t *pool)
 {
-  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
-                            svn_fs_fs__id_unparse(noderev->id,
-                                                  pool)->data));
-
-  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
-                            (noderev->kind == svn_node_file) ?
-                            KIND_FILE : KIND_DIR));
-
-  if (noderev->predecessor_id)
-    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
-                              svn_fs_fs__id_unparse(noderev->predecessor_id,
-                                                    pool)->data));
-
-  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
-                            noderev->predecessor_count));
-
-  if (noderev->data_rep)
-    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
-                              representation_string(noderev->data_rep,
-                                                    format,
-                                                    (noderev->kind
-                                                     == svn_node_dir),
-                                                    FALSE,
-                                                    pool)));
-
-  if (noderev->prop_rep)
-    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
-                              representation_string(noderev->prop_rep, format,
-                                                    TRUE, FALSE, pool)));
-
-  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
-                            noderev->created_path));
-
-  if (noderev->copyfrom_path)
-    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
-                              " %s\n",
-                              noderev->copyfrom_rev,

[... 8925 lines stripped ...]