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);
