summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_fs/low_level.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_fs/low_level.c')
-rw-r--r--subversion/libsvn_fs_fs/low_level.c1208
1 files changed, 1208 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_fs/low_level.c b/subversion/libsvn_fs_fs/low_level.c
new file mode 100644
index 0000000..d21e312
--- /dev/null
+++ b/subversion/libsvn_fs_fs/low_level.c
@@ -0,0 +1,1208 @@
+/* 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_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_fspath.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
+
+/* Convert the C string in *TEXT to a revision number and return it in *REV.
+ * Overflows, negative values other than -1 and terminating characters other
+ * than 0x20 or 0x0 will cause an error. Set *TEXT to the first char after
+ * the initial separator or to EOS.
+ */
+static svn_error_t *
+parse_revnum(svn_revnum_t *rev,
+ const char **text)
+{
+ const char *string = *text;
+ if ((string[0] == '-') && (string[1] == '1'))
+ {
+ *rev = SVN_INVALID_REVNUM;
+ string += 2;
+ }
+ else
+ {
+ SVN_ERR(svn_revnum_parse(rev, string, &string));
+ }
+
+ if (*string == ' ')
+ ++string;
+ else if (*string != '\0')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid character in revision number"));
+
+ *text = string;
+ return SVN_NO_ERROR;
+}
+
+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 *result_pool)
+{
+ return svn_stringbuf_createf(result_pool,
+ "%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
+ root_offset,
+ changes_offset);
+}
+
+svn_error_t *
+svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
+ svn_checksum_t **l2p_checksum,
+ apr_off_t *p2l_offset,
+ svn_checksum_t **p2l_checksum,
+ svn_stringbuf_t *footer,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool)
+{
+ apr_int64_t val;
+ char *last_str = footer->data;
+
+ /* Get the L2P offset. */
+ const char *str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid revision footer"));
+
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *l2p_offset = (apr_off_t)val;
+
+ /* Get the L2P checksum. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid revision footer"));
+
+ SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
+ result_pool));
+
+ /* Get the P2L offset. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid revision footer"));
+
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *p2l_offset = (apr_off_t)val;
+
+ /* Get the P2L checksum. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid revision footer"));
+
+ SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_stringbuf_t *
+svn_fs_fs__unparse_footer(apr_off_t l2p_offset,
+ svn_checksum_t *l2p_checksum,
+ apr_off_t p2l_offset,
+ svn_checksum_t *p2l_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_stringbuf_createf(result_pool,
+ "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
+ l2p_offset,
+ svn_checksum_to_cstring(l2p_checksum,
+ scratch_pool),
+ p2l_offset,
+ svn_checksum_to_cstring(p2l_checksum,
+ scratch_pool));
+}
+
+/* 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 *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ svn_boolean_t eof = TRUE;
+ change_t *change;
+ char *str, *last_str, *kind_str;
+ svn_fs_path_change2_t *info;
+
+ /* Default return value. */
+ *change_p = NULL;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+
+ /* Check for a blank line. */
+ if (eof || (line->len == 0))
+ return SVN_NO_ERROR;
+
+ change = apr_pcalloc(result_pool, sizeof(*change));
+ info = &change->info;
+ 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"));
+
+ SVN_ERR(svn_fs_fs__id_parse(&info->node_rev_id, str, result_pool));
+ if (info->node_rev_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. */
+ info->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)
+ info->node_kind = svn_node_file;
+ else if (strcmp(kind_str, SVN_FS_FS__KIND_DIR) == 0)
+ info->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)
+ {
+ info->change_kind = svn_fs_path_change_modify;
+ }
+ else if (strcmp(str, ACTION_ADD) == 0)
+ {
+ info->change_kind = svn_fs_path_change_add;
+ }
+ else if (strcmp(str, ACTION_DELETE) == 0)
+ {
+ info->change_kind = svn_fs_path_change_delete;
+ }
+ else if (strcmp(str, ACTION_REPLACE) == 0)
+ {
+ info->change_kind = svn_fs_path_change_replace;
+ }
+ else if (strcmp(str, ACTION_RESET) == 0)
+ {
+ info->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)
+ {
+ info->text_mod = TRUE;
+ }
+ else if (strcmp(str, FLAG_FALSE) == 0)
+ {
+ info->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)
+ {
+ info->prop_mod = TRUE;
+ }
+ else if (strcmp(str, FLAG_FALSE) == 0)
+ {
+ info->prop_mod = FALSE;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid prop-mod flag in rev-file"));
+ }
+
+ /* Get the mergeinfo-mod flag if given. Otherwise, the next thing
+ is the path starting with a slash. Also, we must initialize the
+ flag explicitly because 0 is not valid for a svn_tristate_t. */
+ info->mergeinfo_mod = svn_tristate_unknown;
+ if (*last_str != '/')
+ {
+ 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)
+ {
+ info->mergeinfo_mod = svn_tristate_true;
+ }
+ else if (strcmp(str, FLAG_FALSE) == 0)
+ {
+ info->mergeinfo_mod = svn_tristate_false;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid mergeinfo-mod flag in rev-file"));
+ }
+ }
+
+ /* Get the changed path. */
+ if (!svn_fspath__is_canonical(last_str))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid path in changes line"));
+
+ change->path.len = strlen(last_str);
+ change->path.data = apr_pstrdup(result_pool, last_str);
+
+ /* Read the next line, the copyfrom line. */
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ info->copyfrom_known = TRUE;
+ if (eof || line->len == 0)
+ {
+ info->copyfrom_rev = SVN_INVALID_REVNUM;
+ info->copyfrom_path = NULL;
+ }
+ else
+ {
+ last_str = line->data;
+ SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
+
+ if (!svn_fspath__is_canonical(last_str))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid copy-from path in changes line"));
+
+ info->copyfrom_path = apr_pstrdup(result_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 *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ change_t *change;
+ apr_pool_t *iterpool;
+
+ /* Pre-allocate enough room for most change lists.
+ (will be auto-expanded as necessary).
+
+ Chose the default to just below 2^N such that the doubling reallocs
+ will request roughly 2^M bytes from the OS without exceeding the
+ respective two-power by just a few bytes (leaves room array and APR
+ node overhead for large enough M).
+ */
+ *changes = apr_array_make(result_pool, 63, sizeof(change_t *));
+
+ SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+ while (change)
+ {
+ APR_ARRAY_PUSH(*changes, change_t*) = change;
+ SVN_ERR(read_change(&change, stream, result_pool, iterpool));
+ svn_pool_clear(iterpool);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
+ svn_fs_fs__change_receiver_t
+ change_receiver,
+ void *change_receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ change_t *change;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(read_change(&change, stream, iterpool, iterpool));
+ if (change)
+ SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
+ }
+ while (change);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Write a single change entry, path PATH, change CHANGE, to STREAM.
+
+ Only include the node kind field if INCLUDE_NODE_KIND is true. Only
+ include the mergeinfo-mod field if INCLUDE_MERGEINFO_MODS is true.
+ All temporary allocations are in SCRATCH_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,
+ svn_boolean_t include_mergeinfo_mods,
+ apr_pool_t *scratch_pool)
+{
+ const char *idstr;
+ const char *change_string = NULL;
+ const char *kind_string = "";
+ const char *mergeinfo_string = "";
+ svn_stringbuf_t *buf;
+ apr_size_t len;
+
+ 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, scratch_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(scratch_pool, "-%s",
+ change->node_kind == svn_node_dir
+ ? SVN_FS_FS__KIND_DIR
+ : SVN_FS_FS__KIND_FILE);
+ }
+
+ if (include_mergeinfo_mods && change->mergeinfo_mod != svn_tristate_unknown)
+ mergeinfo_string = apr_psprintf(scratch_pool, " %s",
+ change->mergeinfo_mod == svn_tristate_true
+ ? FLAG_TRUE
+ : FLAG_FALSE);
+
+ buf = svn_stringbuf_createf(scratch_pool, "%s %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,
+ mergeinfo_string,
+ path);
+
+ if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
+ {
+ svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
+ change->copyfrom_rev,
+ change->copyfrom_path));
+ }
+
+ svn_stringbuf_appendbyte(buf, '\n');
+
+ /* Write all change info in one write call. */
+ len = buf->len;
+ return svn_error_trace(svn_stream_write(stream, buf->data, &len));
+}
+
+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 *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_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;
+ svn_boolean_t include_mergeinfo_mods =
+ ffd->format >= SVN_FS_FS__MIN_MERGEINFO_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.
+
+ Also, this sorting is only effective in writing all entries with
+ a single call as write_final_changed_path_info() does. For the
+ list being written incrementally during transaction, we actually
+ *must not* change the order of entries from different calls.
+ */
+ sorted_changed_paths = svn_sort__hash(changes,
+ svn_sort_compare_items_lexically,
+ scratch_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,
+ include_mergeinfo_mods, 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
+ RESULT_POOL. */
+static svn_error_t *
+read_header_block(apr_hash_t **headers,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool)
+{
+ *headers = svn_hash__make(result_pool);
+
+ while (1)
+ {
+ svn_stringbuf_t *header_str;
+ const char *name, *value;
+ apr_size_t i = 0;
+ apr_size_t name_len;
+ svn_boolean_t eof;
+
+ SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
+ result_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;
+ name_len = i;
+
+ /* Check if we have enough data to parse. */
+ if (i + 2 > header_str->len)
+ {
+ /* Restore the original line for the error. */
+ header_str->data[i] = ':';
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Found malformed header '%s' in "
+ "revision file"),
+ header_str->data);
+ }
+
+ /* Skip over the NULL byte and the space following it. */
+ i += 2;
+
+ value = header_str->data + i;
+
+ /* header_str is safely in our pool, so we can use bits of it as
+ key and value. */
+ apr_hash_set(*headers, name, name_len, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__parse_representation(representation_t **rep_p,
+ svn_stringbuf_t *text,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ representation_t *rep;
+ char *str;
+ apr_int64_t val;
+ char *string = text->data;
+ svn_checksum_t *checksum;
+ const char *end;
+
+ rep = apr_pcalloc(result_pool, sizeof(*rep));
+ *rep_p = rep;
+
+ SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
+
+ /* initialize transaction info (never stored) */
+ svn_fs_fs__id_txn_reset(&rep->txn_id);
+
+ /* 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->item_index = (apr_uint64_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(&checksum, svn_checksum_md5, str,
+ scratch_pool));
+
+ /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
+ contains the correct value. */
+ if (checksum)
+ memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
+
+ /* 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(&checksum, svn_checksum_sha1, str,
+ scratch_pool));
+
+ /* We do have a valid SHA1 but it might be all 0.
+ We cannot be sure where that came from (Alas! legacy), so let's not
+ claim we know the SHA1 in that case. */
+ rep->has_sha1 = checksum != NULL;
+
+ /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
+ contains the correct value. */
+ if (checksum)
+ memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
+
+ /* 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"));
+
+ SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
+
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL || *str != '_')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ ++str;
+ rep->uniquifier.number = svn__base36toui64(&end, str);
+
+ if (*end)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ 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 *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err
+ = svn_fs_fs__parse_representation(rep_p,
+ svn_stringbuf_create_wrap(string,
+ scratch_pool),
+ result_pool,
+ scratch_pool);
+ if (err)
+ {
+ const svn_string_t *id_unparsed;
+ const char *where;
+
+ id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool);
+ where = apr_psprintf(scratch_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 *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *headers;
+ node_revision_t *noderev;
+ char *value;
+ const char *noderev_id;
+
+ SVN_ERR(read_header_block(&headers, stream, scratch_pool));
+
+ noderev = apr_pcalloc(result_pool, sizeof(*noderev));
+
+ /* Read the node-rev id. */
+ value = svn_hash_gets(headers, HEADER_ID);
+ if (value == NULL)
+ /* ### More information: filename/offset coordinates */
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing id field in node-rev"));
+
+ SVN_ERR(svn_stream_close(stream));
+
+ SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_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, result_pool, scratch_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, result_pool, scratch_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
+ {
+ if (!svn_fspath__is_canonical(value))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Non-canonical cpath field in node-rev '%s'"),
+ noderev_id);
+
+ noderev->created_path = apr_pstrdup(result_pool, value);
+ }
+
+ /* Get the predecessor ID. */
+ value = svn_hash_gets(headers, HEADER_PRED);
+ if (value)
+ SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
+ result_pool));
+
+ /* Get the copyroot. */
+ value = svn_hash_gets(headers, HEADER_COPYROOT);
+ if (value == NULL)
+ {
+ noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
+ noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
+ }
+ else
+ {
+ SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
+
+ if (!svn_fspath__is_canonical(value))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed copyroot line in node-rev '%s'"),
+ noderev_id);
+ noderev->copyroot_path = apr_pstrdup(result_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
+ {
+ SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
+
+ 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(result_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;
+}
+
+/* Return a textual representation of the DIGEST of given KIND.
+ * If IS_NULL is TRUE, no digest is available.
+ * Allocate the result in RESULT_POOL.
+ */
+static const char *
+format_digest(const unsigned char *digest,
+ svn_checksum_kind_t kind,
+ svn_boolean_t is_null,
+ apr_pool_t *result_pool)
+{
+ svn_checksum_t checksum;
+ checksum.digest = digest;
+ checksum.kind = kind;
+
+ if (is_null)
+ return "(null)";
+
+ return svn_checksum_to_cstring_display(&checksum, result_pool);
+}
+
+svn_stringbuf_t *
+svn_fs_fs__unparse_representation(representation_t *rep,
+ int format,
+ svn_boolean_t mutable_rep_truncated,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char buffer[SVN_INT64_BUFFER_SIZE];
+ if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
+ return svn_stringbuf_ncreate("-1", 2, result_pool);
+
+ if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1)
+ return svn_stringbuf_createf
+ (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
+ " %" SVN_FILESIZE_T_FMT " %s",
+ rep->revision, rep->item_index, rep->size,
+ rep->expanded_size,
+ format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
+ scratch_pool));
+
+ svn__ui64tobase36(buffer, rep->uniquifier.number);
+ return svn_stringbuf_createf
+ (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
+ " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s",
+ rep->revision, rep->item_index, rep->size,
+ rep->expanded_size,
+ format_digest(rep->md5_digest, svn_checksum_md5,
+ FALSE, scratch_pool),
+ format_digest(rep->sha1_digest, svn_checksum_sha1,
+ !rep->has_sha1, scratch_pool),
+ svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id,
+ scratch_pool),
+ buffer);
+}
+
+
+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 *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
+ svn_fs_fs__id_unparse(noderev->id,
+ scratch_pool)->data));
+
+ SVN_ERR(svn_stream_printf(outfile, scratch_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, scratch_pool, HEADER_PRED ": %s\n",
+ svn_fs_fs__id_unparse(noderev->predecessor_id,
+ scratch_pool)->data));
+
+ SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
+ noderev->predecessor_count));
+
+ if (noderev->data_rep)
+ SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
+ svn_fs_fs__unparse_representation
+ (noderev->data_rep,
+ format,
+ noderev->kind == svn_node_dir,
+ scratch_pool, scratch_pool)->data));
+
+ if (noderev->prop_rep)
+ SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
+ svn_fs_fs__unparse_representation
+ (noderev->prop_rep, format,
+ TRUE, scratch_pool, scratch_pool)->data));
+
+ SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
+ noderev->created_path));
+
+ if (noderev->copyfrom_path)
+ SVN_ERR(svn_stream_printf(outfile, scratch_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, scratch_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, scratch_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 *result_pool,
+ apr_pool_t *scratch_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, scratch_pool));
+
+ *header = apr_pcalloc(result_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;
+
+ SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ goto error;
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ (*header)->base_item_index = (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 *scratch_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(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
+ " %" SVN_FILESIZE_T_FMT "\n",
+ header->base_revision, header->base_item_index,
+ header->base_length);
+ }
+
+ return svn_error_trace(svn_stream_puts(stream, text));
+}