diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_fs_x/low_level.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_fs_x/low_level.c')
-rw-r--r-- | subversion/libsvn_fs_x/low_level.c | 1123 |
1 files changed, 1123 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/low_level.c b/subversion/libsvn_fs_x/low_level.c new file mode 100644 index 0000000..76f3fd2 --- /dev/null +++ b/subversion/libsvn_fs_x/low_level.c @@ -0,0 +1,1123 @@ +/* low_level.c --- low level r/w access to fs_x 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" +#include "util.h" +#include "pack.h" +#include "cached_data.h" + +/* Headers used to describe node-revision in the revision file. */ +#define HEADER_ID "id" +#define HEADER_NODE "node" +#define HEADER_COPY "copy" +#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_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_DELTA "DELTA" + +/* An arbitrary maximum path length, so clients can't run us out of memory + * by giving us arbitrarily large paths. */ +#define FSX_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 FSX_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_x__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_x__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)); +} + +/* 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_x__parse_representation(svn_fs_x__representation_t **rep_p, + svn_stringbuf_t *text, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_fs_x__representation_t *rep; + char *str; + apr_int64_t val; + char *string = text->data; + svn_checksum_t *checksum; + + rep = apr_pcalloc(result_pool, sizeof(*rep)); + *rep_p = rep; + + str = svn_cstring_tokenize(" ", &string); + if (str == NULL) + return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, + _("Malformed text representation offset line in node-rev")); + + SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str)); + + /* while in transactions, it is legal to simply write "-1" */ + if (rep->id.change_set == -1) + return SVN_NO_ERROR; + + str = svn_cstring_tokenize(" ", &string); + if (str == NULL) + { + if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET) + 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->id.number = (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(&checksum, svn_checksum_md5, str, + scratch_pool)); + 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)); + rep->has_sha1 = checksum != NULL; + if (checksum) + memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); + + 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(svn_fs_x__representation_t **rep_p, + char *string, + const svn_fs_x__id_t *noderev_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err + = svn_fs_x__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_x__id_unparse(noderev_id, scratch_pool); + where = apr_psprintf(scratch_pool, + _("While reading representation offsets " + "for node-revision '%s':"), + id_unparsed->data); + + return svn_error_quick_wrap(err, where); + } + + return SVN_NO_ERROR; +} + +/* If PATH needs to be escaped, return an escaped version of it, allocated + * from RESULT_POOL. Otherwise, return PATH directly. */ +static const char * +auto_escape_path(const char *path, + apr_pool_t *result_pool) +{ + apr_size_t len = strlen(path); + apr_size_t i; + const char esc = '\x1b'; + + for (i = 0; i < len; ++i) + if (path[i] < ' ') + { + svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len, + result_pool); + for (i = 0; i < len; ++i) + if (path[i] < ' ') + { + svn_stringbuf_appendbyte(escaped, esc); + svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1); + } + else + { + svn_stringbuf_appendbyte(escaped, path[i]); + } + + return escaped->data; + } + + return path; +} + +/* If PATH has been escaped, return the un-escaped version of it, allocated + * from RESULT_POOL. Otherwise, return PATH directly. */ +static const char * +auto_unescape_path(const char *path, + apr_pool_t *result_pool) +{ + const char esc = '\x1b'; + if (strchr(path, esc)) + { + apr_size_t len = strlen(path); + apr_size_t i; + + svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len, + result_pool); + for (i = 0; i < len; ++i) + if (path[i] == esc) + svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A'); + else + svn_stringbuf_appendbyte(unescaped, path[i]); + + return unescaped->data; + } + + return path; +} + +/* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */ +static svn_error_t * +read_id_part(svn_fs_x__id_t *id, + apr_hash_t *headers, + const char *header_name) +{ + const char *value = svn_hash_gets(headers, header_name); + if (value == NULL) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Missing %s field in node-rev"), + header_name); + + SVN_ERR(svn_fs_x__id_parse(id, value)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *headers; + svn_fs_x__noderev_t *noderev; + char *value; + const char *noderev_id; + + SVN_ERR(read_header_block(&headers, stream, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + + noderev = apr_pcalloc(result_pool, sizeof(*noderev)); + + /* for error messages later */ + noderev_id = svn_hash_gets(headers, HEADER_ID); + + /* Read the node-rev id. */ + SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID)); + SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE)); + SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY)); + + /* Read the type. */ + value = svn_hash_gets(headers, HEADER_TYPE); + + if ((value == NULL) || + ( strcmp(value, SVN_FS_X__KIND_FILE) + && strcmp(value, SVN_FS_X__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_X__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->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->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 = auto_unescape_path(apr_pstrdup(result_pool, + value), + result_pool); + } + + /* Get the predecessor ID. */ + value = svn_hash_gets(headers, HEADER_PRED); + if (value) + SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value)); + else + svn_fs_x__id_reset(&noderev->predecessor_id); + + /* Get the copyroot. */ + value = svn_hash_gets(headers, HEADER_COPYROOT); + if (value == NULL) + { + noderev->copyroot_path = noderev->created_path; + noderev->copyroot_rev + = svn_fs_x__get_revnum(noderev->noderev_id.change_set); + } + 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 = auto_unescape_path(apr_pstrdup(result_pool, + value), + result_pool); + } + + /* 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 = auto_unescape_path(apr_pstrdup(result_pool, + value), + result_pool); + } + + /* 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_x__unparse_representation(svn_fs_x__representation_t *rep, + svn_boolean_t mutable_rep_truncated, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (!rep->has_sha1) + return svn_stringbuf_createf + (result_pool, + "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT + " %" SVN_FILESIZE_T_FMT " %s", + rep->id.change_set, rep->id.number, rep->size, + rep->expanded_size, + format_digest(rep->md5_digest, svn_checksum_md5, FALSE, + scratch_pool)); + + return svn_stringbuf_createf + (result_pool, + "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT + " %" SVN_FILESIZE_T_FMT " %s %s", + rep->id.change_set, rep->id.number, 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_error_t * +svn_fs_x__write_noderev(svn_stream_t *outfile, + svn_fs_x__noderev_t *noderev, + apr_pool_t *scratch_pool) +{ + svn_string_t *str_id; + + str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool); + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n", + str_id->data)); + str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool); + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n", + str_id->data)); + str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool); + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n", + str_id->data)); + + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n", + (noderev->kind == svn_node_file) ? + SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR)); + + if (svn_fs_x__id_used(&noderev->predecessor_id)) + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n", + svn_fs_x__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_x__unparse_representation + (noderev->data_rep, + 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_x__unparse_representation + (noderev->prop_rep, + TRUE, scratch_pool, scratch_pool)->data)); + + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n", + auto_escape_path(noderev->created_path, + scratch_pool))); + + if (noderev->copyfrom_path) + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld" + " %s\n", + noderev->copyfrom_rev, + auto_escape_path(noderev->copyfrom_path, + scratch_pool))); + + if ( ( noderev->copyroot_rev + != svn_fs_x__get_revnum(noderev->noderev_id.change_set)) + || (strcmp(noderev->copyroot_path, noderev->created_path) != 0)) + SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld" + " %s\n", + noderev->copyroot_rev, + auto_escape_path(noderev->copyroot_path, + scratch_pool))); + + 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_x__read_rep_header(svn_fs_x__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_DELTA) == 0) + { + /* This is a delta against the empty stream. */ + (*header)->type = svn_fs_x__rep_self_delta; + return SVN_NO_ERROR; + } + + (*header)->type = svn_fs_x__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_x__write_rep_header(svn_fs_x__rep_header_t *header, + svn_stream_t *stream, + apr_pool_t *scratch_pool) +{ + const char *text; + + switch (header->type) + { + case svn_fs_x__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)); +} + +/* 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(svn_fs_x__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; + svn_fs_x__change_t *change; + char *str, *last_str, *kind_str; + + /* 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)); + 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_x__id_parse(&change->noderev_id, str)); + + /* 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_X__KIND_FILE) == 0) + change->node_kind = svn_node_file; + else if (strcmp(kind_str, SVN_FS_X__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->change_kind = svn_fs_path_change_modify; + } + else if (strcmp(str, ACTION_ADD) == 0) + { + change->change_kind = svn_fs_path_change_add; + } + else if (strcmp(str, ACTION_DELETE) == 0) + { + change->change_kind = svn_fs_path_change_delete; + } + else if (strcmp(str, ACTION_REPLACE) == 0) + { + change->change_kind = svn_fs_path_change_replace; + } + else if (strcmp(str, ACTION_RESET) == 0) + { + change->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 mergeinfo-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->mergeinfo_mod = svn_tristate_true; + } + else if (strcmp(str, FLAG_FALSE) == 0) + { + change->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.data = auto_unescape_path(apr_pstrmemdup(result_pool, + last_str, + strlen(last_str)), + result_pool); + change->path.len = strlen(change->path.data); + + /* Read the next line, the copyfrom line. */ + SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); + change->copyfrom_known = TRUE; + if (eof || line->len == 0) + { + change->copyfrom_rev = SVN_INVALID_REVNUM; + change->copyfrom_path = NULL; + } + else + { + last_str = line->data; + SVN_ERR(parse_revnum(&change->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")); + + change->copyfrom_path = auto_unescape_path(last_str, result_pool); + } + + *change_p = change; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_x__read_changes(apr_array_header_t **changes, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_fs_x__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(svn_fs_x__change_t *)); + + SVN_ERR(read_change(&change, stream, result_pool, scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + while (change) + { + APR_ARRAY_PUSH(*changes, svn_fs_x__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_x__read_changes_incrementally(svn_stream_t *stream, + svn_fs_x__change_receiver_t + change_receiver, + void *change_receiver_baton, + apr_pool_t *scratch_pool) +{ + svn_fs_x__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. + + All temporary allocations are in SCRATCH_POOL. */ +static svn_error_t * +write_change_entry(svn_stream_t *stream, + svn_fs_x__change_t *change, + apr_pool_t *scratch_pool) +{ + const char *idstr; + const char *change_string = NULL; + const char *kind_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); + } + + idstr = svn_fs_x__id_unparse(&change->noderev_id, scratch_pool)->data; + + 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_X__KIND_DIR + : SVN_FS_X__KIND_FILE); + + 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, + change->mergeinfo_mod == svn_tristate_true + ? FLAG_TRUE : FLAG_FALSE, + auto_escape_path(change->path.data, scratch_pool)); + + if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) + { + svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s", + change->copyfrom_rev, + auto_escape_path(change->copyfrom_path, + scratch_pool))); + } + + 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_x__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); + 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 FSX 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_x__change_t *change; + + svn_pool_clear(iterpool); + change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value; + + /* Write out the new entry into the final rev-file. */ + SVN_ERR(write_change_entry(stream, change, iterpool)); + } + + if (terminate_list) + svn_stream_puts(stream, "\n"); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + |