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/07/12 13:35:48 UTC

svn commit: r1502517 [2/2] - in /subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs: fs_fs.c fs_fs.h low_level.c low_level.h

Added: subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.c?rev=1502517&view=auto
==============================================================================
--- subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.c (added)
+++ subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.c Fri Jul 12 11:35:48 2013
@@ -0,0 +1,976 @@
+/* low_level.c --- low level r/w access to fs_fs file structures
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include <apr_md5.h>
+#include <apr_sha1.h>
+
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "private/svn_string_private.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "low_level.h"
+
+/* 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 of representation. */
+#define REP_PLAIN          "PLAIN"
+#define REP_DELTA          "DELTA"
+
+/* 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 256 is an arbitrary size large enough to hold the node id and the
+ * various flags. */
+#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
+
+svn_error_t *
+svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
+                                  apr_off_t *changes_offset,
+                                  svn_stringbuf_t *trailer,
+                                  svn_revnum_t rev)
+{
+  int i, num_bytes;
+  const char *str;
+
+  /* This cast should be safe since the maximum amount read, 64, will
+     never be bigger than the size of an int. */
+  num_bytes = (int) trailer->len;
+
+  /* The last byte should be a newline. */
+  if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
+    {
+      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                               _("Revision file (r%ld) lacks trailing newline"),
+                               rev);
+    }
+
+  /* Look for the next previous newline. */
+  for (i = num_bytes - 2; i >= 0; i--)
+    {
+      if (trailer->data[i] == '\n')
+        break;
+    }
+
+  if (i < 0)
+    {
+      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                               _("Final line in revision file (r%ld) longer "
+                                 "than 64 characters"),
+                               rev);
+    }
+
+  i++;
+  str = &trailer->data[i];
+
+  /* find the next space */
+  for ( ; i < (num_bytes - 2) ; i++)
+    if (trailer->data[i] == ' ')
+      break;
+
+  if (i == (num_bytes - 2))
+    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                             _("Final line in revision file r%ld missing space"),
+                             rev);
+
+  if (root_offset)
+    {
+      apr_int64_t val;
+
+      trailer->data[i] = '\0';
+      SVN_ERR(svn_cstring_atoi64(&val, str));
+      *root_offset = (apr_off_t)val;
+    }
+
+  i++;
+  str = &trailer->data[i];
+
+  /* find the next newline */
+  for ( ; i < num_bytes; i++)
+    if (trailer->data[i] == '\n')
+      break;
+
+  if (changes_offset)
+    {
+      apr_int64_t val;
+
+      trailer->data[i] = '\0';
+      SVN_ERR(svn_cstring_atoi64(&val, str));
+      *changes_offset = (apr_off_t)val;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_stringbuf_t *
+svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
+                                    apr_off_t changes_offset,
+                                    apr_pool_t *pool)
+{
+  return svn_stringbuf_createf(pool,
+                               "%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
+                               root_offset,
+                               changes_offset);
+}
+
+
+/* Read the next entry in the changes record from file FILE and store
+   the resulting change in *CHANGE_P.  If there is no next record,
+   store NULL there.  Perform all allocations from POOL. */
+static svn_error_t *
+read_change(change_t **change_p,
+            svn_stream_t *stream,
+            apr_pool_t *pool)
+{
+  svn_stringbuf_t *line;
+  svn_boolean_t eof = TRUE;
+  change_t *change;
+  char *str, *last_str, *kind_str;
+
+  /* Default return value. */
+  *change_p = NULL;
+
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, pool));
+
+  /* Check for a blank line. */
+  if (eof || (line->len == 0))
+    return SVN_NO_ERROR;
+
+  change = apr_pcalloc(pool, sizeof(*change));
+  last_str = line->data;
+
+  /* Get the node-id of the change. */
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (str == NULL)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Invalid changes line in rev-file"));
+
+  change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
+  if (change->noderev_id == NULL)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Invalid changes line in rev-file"));
+
+  /* Get the change type. */
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (str == NULL)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Invalid changes line in rev-file"));
+
+  /* Don't bother to check the format number before looking for
+   * node-kinds: just read them if you find them. */
+  change->node_kind = svn_node_unknown;
+  kind_str = strchr(str, '-');
+  if (kind_str)
+    {
+      /* Cap off the end of "str" (the action). */
+      *kind_str = '\0';
+      kind_str++;
+      if (strcmp(kind_str, SVN_FS_FS__KIND_FILE) == 0)
+        change->node_kind = svn_node_file;
+      else if (strcmp(kind_str, SVN_FS_FS__KIND_DIR) == 0)
+        change->node_kind = svn_node_dir;
+      else
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("Invalid changes line in rev-file"));
+    }
+
+  if (strcmp(str, ACTION_MODIFY) == 0)
+    {
+      change->kind = svn_fs_path_change_modify;
+    }
+  else if (strcmp(str, ACTION_ADD) == 0)
+    {
+      change->kind = svn_fs_path_change_add;
+    }
+  else if (strcmp(str, ACTION_DELETE) == 0)
+    {
+      change->kind = svn_fs_path_change_delete;
+    }
+  else if (strcmp(str, ACTION_REPLACE) == 0)
+    {
+      change->kind = svn_fs_path_change_replace;
+    }
+  else if (strcmp(str, ACTION_RESET) == 0)
+    {
+      change->kind = svn_fs_path_change_reset;
+    }
+  else
+    {
+      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                              _("Invalid change kind in rev file"));
+    }
+
+  /* Get the text-mod flag. */
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (str == NULL)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Invalid changes line in rev-file"));
+
+  if (strcmp(str, FLAG_TRUE) == 0)
+    {
+      change->text_mod = TRUE;
+    }
+  else if (strcmp(str, FLAG_FALSE) == 0)
+    {
+      change->text_mod = FALSE;
+    }
+  else
+    {
+      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                              _("Invalid text-mod flag in rev-file"));
+    }
+
+  /* Get the prop-mod flag. */
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (str == NULL)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Invalid changes line in rev-file"));
+
+  if (strcmp(str, FLAG_TRUE) == 0)
+    {
+      change->prop_mod = TRUE;
+    }
+  else if (strcmp(str, FLAG_FALSE) == 0)
+    {
+      change->prop_mod = FALSE;
+    }
+  else
+    {
+      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                              _("Invalid prop-mod flag in rev-file"));
+    }
+
+  /* Get the changed path. */
+  change->path = apr_pstrdup(pool, last_str);
+
+
+  /* Read the next line, the copyfrom line. */
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, pool));
+  if (eof || line->len == 0)
+    {
+      change->copyfrom_rev = SVN_INVALID_REVNUM;
+      change->copyfrom_path = NULL;
+    }
+  else
+    {
+      last_str = line->data;
+      str = svn_cstring_tokenize(" ", &last_str);
+      if (! str)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("Invalid changes line in rev-file"));
+      change->copyfrom_rev = SVN_STR_TO_REV(str);
+
+      if (! last_str)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("Invalid changes line in rev-file"));
+
+      change->copyfrom_path = apr_pstrdup(pool, last_str);
+    }
+
+  *change_p = change;
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_changes(apr_array_header_t **changes,
+                        svn_stream_t *stream,
+                        apr_pool_t *pool)
+{
+  change_t *change;
+
+  /* pre-allocate enough room for most change lists
+     (will be auto-expanded as necessary) */
+  *changes = apr_array_make(pool, 30, sizeof(change_t *));
+
+  SVN_ERR(read_change(&change, stream, pool));
+  while (change)
+    {
+      APR_ARRAY_PUSH(*changes, change_t*) = change;
+      SVN_ERR(read_change(&change, stream, pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Write a single change entry, path PATH, change CHANGE, and copyfrom
+   string COPYFROM, into the file specified by FILE.  Only include the
+   node kind field if INCLUDE_NODE_KIND is true.  All temporary
+   allocations are in POOL. */
+static svn_error_t *
+write_change_entry(svn_stream_t *stream,
+                   const char *path,
+                   svn_fs_path_change2_t *change,
+                   svn_boolean_t include_node_kind,
+                   apr_pool_t *pool)
+{
+  const char *idstr, *buf;
+  const char *change_string = NULL;
+  const char *kind_string = "";
+
+  switch (change->change_kind)
+    {
+    case svn_fs_path_change_modify:
+      change_string = ACTION_MODIFY;
+      break;
+    case svn_fs_path_change_add:
+      change_string = ACTION_ADD;
+      break;
+    case svn_fs_path_change_delete:
+      change_string = ACTION_DELETE;
+      break;
+    case svn_fs_path_change_replace:
+      change_string = ACTION_REPLACE;
+      break;
+    case svn_fs_path_change_reset:
+      change_string = ACTION_RESET;
+      break;
+    default:
+      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                               _("Invalid change type %d"),
+                               change->change_kind);
+    }
+
+  if (change->node_rev_id)
+    idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
+  else
+    idstr = ACTION_RESET;
+
+  if (include_node_kind)
+    {
+      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
+                     || change->node_kind == svn_node_file);
+      kind_string = apr_psprintf(pool, "-%s",
+                                 change->node_kind == svn_node_dir
+                                 ? SVN_FS_FS__KIND_DIR
+                                  : SVN_FS_FS__KIND_FILE);
+    }
+  buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
+                     idstr, change_string, kind_string,
+                     change->text_mod ? FLAG_TRUE : FLAG_FALSE,
+                     change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
+                     path);
+
+  SVN_ERR(svn_stream_puts(stream, buf));
+
+  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
+    {
+      buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
+                         change->copyfrom_path);
+      SVN_ERR(svn_stream_puts(stream, buf));
+    }
+
+  return svn_error_trace(svn_stream_puts(stream, "\n"));
+}
+
+svn_error_t *
+svn_fs_fs__write_changes(svn_stream_t *stream,
+                         svn_fs_t *fs,
+                         apr_hash_t *changes,
+                         svn_boolean_t terminate_list,
+                         apr_pool_t *pool)
+{
+  apr_pool_t *iterpool = svn_pool_create(pool);
+  fs_fs_data_t *ffd = fs->fsap_data;
+  svn_boolean_t include_node_kinds =
+      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
+  apr_array_header_t *sorted_changed_paths;
+  int i;
+
+  /* For the sake of the repository administrator sort the changes so
+     that the final file is deterministic and repeatable, however the
+     rest of the FSFS code doesn't require any particular order here. */
+  sorted_changed_paths = svn_sort__hash(changes,
+                                        svn_sort_compare_items_lexically, pool);
+
+  /* Write all items to disk in the new order. */
+  for (i = 0; i < sorted_changed_paths->nelts; ++i)
+    {
+      svn_fs_path_change2_t *change;
+      const char *path;
+
+      svn_pool_clear(iterpool);
+
+      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
+      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
+
+      /* Write out the new entry into the final rev-file. */
+      SVN_ERR(write_change_entry(stream, path, change, include_node_kinds,
+                                 iterpool));
+    }
+
+  if (terminate_list)
+    svn_stream_puts(stream, "\n");
+
+  svn_pool_destroy(iterpool);
+
+  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;
+}
+
+svn_error_t *
+svn_fs_fs__parse_representation(representation_t **rep_p,
+                                svn_stringbuf_t *text,
+                                apr_pool_t *pool)
+{
+  representation_t *rep;
+  char *str;
+  apr_int64_t val;
+  char *string = text->data;
+
+  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"));
+
+  rep->revision = SVN_STR_TO_REV(str);
+
+  /* while in transactions, it is legal to simply write "-1" */
+  str = svn_cstring_tokenize(" ", &string);
+  if (str == NULL)
+    {
+      if (rep->revision == SVN_INVALID_REVNUM)
+        return SVN_NO_ERROR;
+
+      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(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
+                                 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;
+
+  /* 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"));
+
+  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
+                                 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);
+
+  return SVN_NO_ERROR;
+}
+
+/* Wrap svn_fs_fs__parse_representation(), 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,
+                 apr_pool_t *pool)
+{
+  svn_error_t *err
+    = svn_fs_fs__parse_representation(rep_p,
+                                      svn_stringbuf_create_wrap(string, pool),
+                                      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)");
+
+      return svn_error_quick_wrap(err, where);
+    }
+
+  if ((*rep_p)->revision == SVN_INVALID_REVNUM)
+    if (noderev_id)
+      (*rep_p)->txn_id = svn_fs_fs__id_txn_id(noderev_id);
+
+  return SVN_NO_ERROR;
+}
+
+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));
+
+  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
+  noderev_id = value; /* for error messages later */
+
+  /* Read the type. */
+  value = svn_hash_gets(headers, HEADER_TYPE);
+
+  if ((value == NULL) ||
+      (   strcmp(value, SVN_FS_FS__KIND_FILE)
+       && strcmp(value, SVN_FS_FS__KIND_DIR)))
+    /* ### s/kind/type/ */
+    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                             _("Missing kind field in node-rev '%s'"),
+                             noderev_id);
+
+  noderev->kind = (strcmp(value, SVN_FS_FS__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, 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, 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);
+    }
+
+  /* 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;
+
+  /* Get whether *this* node has mergeinfo. */
+  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
+  noderev->has_mergeinfo = (value != NULL);
+
+  *noderev_p = noderev;
+
+  return SVN_NO_ERROR;
+}
+
+svn_stringbuf_t *
+svn_fs_fs__unparse_representation(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 svn_stringbuf_ncreate("-1", 2, pool);
+
+#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 svn_stringbuf_createf
+               (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 svn_stringbuf_createf
+               (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
+
+}
+
+
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+                         node_revision_t *noderev,
+                         int format,
+                         svn_boolean_t include_mergeinfo,
+                         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) ?
+                            SVN_FS_FS__KIND_FILE : SVN_FS_FS__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",
+                              svn_fs_fs__unparse_representation
+                                (noderev->data_rep,
+                                 format,
+                                 noderev->kind == svn_node_dir,
+                                 FALSE,
+                                 pool)->data));
+
+  if (noderev->prop_rep)
+    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
+                              svn_fs_fs__unparse_representation
+                                (noderev->prop_rep, format,
+                                 TRUE, FALSE, pool)->data));
+
+  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,
+                              noderev->copyfrom_path));
+
+  if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
+      (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
+    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
+                              " %s\n",
+                              noderev->copyroot_rev,
+                              noderev->copyroot_path));
+
+  if (noderev->is_fresh_txn_root)
+    SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
+
+  if (include_mergeinfo)
+    {
+      if (noderev->mergeinfo_count > 0)
+        SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
+                                  APR_INT64_T_FMT "\n",
+                                  noderev->mergeinfo_count));
+
+      if (noderev->has_mergeinfo)
+        SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
+    }
+
+  return svn_stream_puts(outfile, "\n");
+}
+
+svn_error_t *
+svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
+                           svn_stream_t *stream,
+                           apr_pool_t *pool)
+{
+  svn_stringbuf_t *buffer;
+  char *str, *last_str;
+  apr_int64_t val;
+  svn_boolean_t eol = FALSE;
+
+  SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, pool));
+
+  *header = apr_pcalloc(pool, sizeof(**header));
+  (*header)->header_size = buffer->len + 1;
+  if (strcmp(buffer->data, REP_PLAIN) == 0)
+    {
+      (*header)->type = svn_fs_fs__rep_plain;
+      return SVN_NO_ERROR;
+    }
+
+  if (strcmp(buffer->data, REP_DELTA) == 0)
+    {
+      /* This is a delta against the empty stream. */
+      (*header)->type = svn_fs_fs__rep_self_delta;
+      return SVN_NO_ERROR;
+    }
+
+  (*header)->type = svn_fs_fs__rep_delta;
+
+  /* We have hopefully a DELTA vs. a non-empty base revision. */
+  last_str = buffer->data;
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (! str || (strcmp(str, REP_DELTA) != 0))
+    goto error;
+
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (! str)
+    goto error;
+  (*header)->base_revision = SVN_STR_TO_REV(str);
+
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (! str)
+    goto error;
+  SVN_ERR(svn_cstring_atoi64(&val, str));
+  (*header)->base_offset = (apr_off_t)val;
+
+  str = svn_cstring_tokenize(" ", &last_str);
+  if (! str)
+    goto error;
+  SVN_ERR(svn_cstring_atoi64(&val, str));
+  (*header)->base_length = (svn_filesize_t)val;
+
+  return SVN_NO_ERROR;
+
+ error:
+  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                           _("Malformed representation header"));
+}
+
+svn_error_t *
+svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
+                            svn_stream_t *stream,
+                            apr_pool_t *pool)
+{
+  const char *text;
+  
+  switch (header->type)
+    {
+      case svn_fs_fs__rep_plain:
+        text = REP_PLAIN "\n";
+        break;
+
+      case svn_fs_fs__rep_self_delta:
+        text = REP_DELTA "\n";
+        break;
+
+      default:
+        text = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
+                            SVN_FILESIZE_T_FMT "\n",
+                            header->base_revision, header->base_offset,
+                            header->base_length);
+    }
+
+  return svn_error_trace(svn_stream_puts(stream, text));
+}

Added: subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.h?rev=1502517&view=auto
==============================================================================
--- subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.h (added)
+++ subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/low_level.h Fri Jul 12 11:35:48 2013
@@ -0,0 +1,172 @@
+/* low_level.c --- low level r/w access to fs_fs file structures
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "svn_fs.h"
+
+#include "fs_fs.h"
+#include "id.h"
+
+/* Kinds that a node-rev can be. */
+#define SVN_FS_FS__KIND_FILE          "file"
+#define SVN_FS_FS__KIND_DIR           "dir"
+
+/* The functions are grouped as follows:
+ *
+ * - revision trailer
+ * - changed path list
+ * - node revision
+ * - representation (as in "text:" and "props:" lines)
+ * - representation header ("PLAIN" and "DELTA" lines)
+ */
+
+/* Given the last "few" bytes (should be at least 40) of revision REV in
+ * TRAILER,  parse the last line and return the offset of the root noderev
+ * in *ROOT_OFFSET and the offset of the changed paths list in
+ * *CHANGES_OFFSET.  Offsets are relative to the revision's start offset.
+ * ROOT_OFFSET and / or CHANGES_OFFSET may be NULL.
+ * 
+ * Note that REV is only used to construct nicer error objects.
+ */
+svn_error_t *
+svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
+                                  apr_off_t *changes_offset,
+                                  svn_stringbuf_t *trailer,
+                                  svn_revnum_t rev);
+
+/* Given the offset of the root noderev in ROOT_OFFSET and the offset of
+ * the changed paths list in CHANGES_OFFSET,  return the corresponding
+ * revision's trailer.  Allocate it in POOL.
+ */
+svn_stringbuf_t *
+svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
+                                    apr_off_t changes_offset,
+                                    apr_pool_t *pool);
+
+/* Read all the changes from STREAM and store them in *CHANGES.  Do all
+   allocations in POOL. */
+svn_error_t *
+svn_fs_fs__read_changes(apr_array_header_t **changes,
+                        svn_stream_t *stream,
+                        apr_pool_t *pool);
+
+/* Write the changed path info from CHANGES in filesystem FS to the
+   output stream STREAM.  You may call this function multiple time on
+   the same stream but the last call should set TERMINATE_LIST to write
+   an extra empty line that marks the end of the changed paths list.
+   Perform temporary allocations in POOL.
+ */
+svn_error_t *
+svn_fs_fs__write_changes(svn_stream_t *stream,
+                         svn_fs_t *fs,
+                         apr_hash_t *changes,
+                         svn_boolean_t terminate_list,
+                         apr_pool_t *pool);
+
+/* Read a node-revision from STREAM. Set *NODEREV to the new structure,
+   allocated in POOL. */
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev,
+                        svn_stream_t *stream,
+                        apr_pool_t *pool);
+
+/* Write the node-revision NODEREV into the stream OUTFILE, compatible with
+   filesystem format FORMAT.  Only write mergeinfo-related metadata if
+   INCLUDE_MERGEINFO is true.  Temporary allocations are from POOL. */
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+                         node_revision_t *noderev,
+                         int format,
+                         svn_boolean_t include_mergeinfo,
+                         apr_pool_t *pool);
+
+/* Parse the description of a representation from TEXT and store it
+   into *REP_P.  Allocate *REP_P in POOL. */
+svn_error_t *
+svn_fs_fs__parse_representation(representation_t **rep_p,
+                                svn_stringbuf_t *text,
+                                apr_pool_t *pool);
+
+/* 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.  */
+svn_stringbuf_t *
+svn_fs_fs__unparse_representation(representation_t *rep,
+                                  int format,
+                                  svn_boolean_t mutable_rep_truncated,
+                                  svn_boolean_t may_be_corrupt,
+                                  apr_pool_t *pool);
+
+/* This type enumerates all forms of representations that we support. */
+typedef enum svn_fs_fs__rep_type_t
+{
+  /* this is a PLAIN representation */
+  svn_fs_fs__rep_plain,
+
+  /* this is a DELTA representation with no base representation */
+  svn_fs_fs__rep_self_delta,
+
+  /* this is a DELTA representation against some base representation */
+  svn_fs_fs__rep_delta
+} svn_fs_fs__rep_type_t;
+
+/* This structure is used to hold the information stored in a representation
+ * header. */
+typedef struct svn_fs_fs__rep_header_t
+{
+  /* type of the representation, i.e. whether it is PLAIN, self-DELTA etc. */
+  svn_fs_fs__rep_type_t type;
+
+  /* if this rep is a delta against some other rep, that base rep can
+   * be found in this revision.  Should be 0 if there is no base rep. */
+  svn_revnum_t base_revision;
+
+  /* if this rep is a delta against some other rep, that base rep can
+   * be found at this offset within the base rep's revision.  Should
+   * be 0 if there is no base rep. */
+  apr_off_t base_offset;
+
+  /* if this rep is a delta against some other rep, this is the (deltified)
+   * size of that base rep.  Should be 0 if there is no base rep. */
+  svn_filesize_t base_length;
+
+  /* length of the textual representation of the header in the rep or pack
+   * file, including EOL.  Only valid after reading it from disk.
+   * Should be 0 otherwise. */
+  apr_size_t header_size;
+} svn_fs_fs__rep_header_t;
+
+/* Read the next line from file FILE and parse it as a text
+   representation entry.  Return the parsed entry in *REP_ARGS_P.
+   Perform all allocations in POOL. */
+svn_error_t *
+svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
+                           svn_stream_t *stream,
+                           apr_pool_t *pool);
+
+/* Write the representation HEADER to STREAM.  Use POOL for allocations. */
+svn_error_t *
+svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
+                            svn_stream_t *stream,
+                            apr_pool_t *pool);