summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_fs
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-08-05 16:22:51 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-08-05 16:22:51 +0000
commitcf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch)
treeda27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_fs_fs
parentbb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff)
downloadsubversion-tarball-cf46733632c7279a9fd0fe6ce26f9185a4ae82a9.tar.gz
Diffstat (limited to 'subversion/libsvn_fs_fs')
-rw-r--r--subversion/libsvn_fs_fs/cached_data.c3502
-rw-r--r--subversion/libsvn_fs_fs/cached_data.h178
-rw-r--r--subversion/libsvn_fs_fs/caching.c356
-rw-r--r--subversion/libsvn_fs_fs/dag.c207
-rw-r--r--subversion/libsvn_fs_fs/dag.h68
-rw-r--r--subversion/libsvn_fs_fs/dump-index.c90
-rw-r--r--subversion/libsvn_fs_fs/fs.c302
-rw-r--r--subversion/libsvn_fs_fs/fs.h261
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.c11577
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.h500
-rw-r--r--subversion/libsvn_fs_fs/hotcopy.c1097
-rw-r--r--subversion/libsvn_fs_fs/hotcopy.h51
-rw-r--r--subversion/libsvn_fs_fs/id.c620
-rw-r--r--subversion/libsvn_fs_fs/id.h89
-rw-r--r--subversion/libsvn_fs_fs/index.c3470
-rw-r--r--subversion/libsvn_fs_fs/index.h356
-rw-r--r--subversion/libsvn_fs_fs/key-gen.c159
-rw-r--r--subversion/libsvn_fs_fs/key-gen.h91
-rw-r--r--subversion/libsvn_fs_fs/libsvn_fs_fs.pc.in12
-rw-r--r--subversion/libsvn_fs_fs/load-index.c98
-rw-r--r--subversion/libsvn_fs_fs/lock.c944
-rw-r--r--subversion/libsvn_fs_fs/lock.h25
-rw-r--r--subversion/libsvn_fs_fs/low_level.c1208
-rw-r--r--subversion/libsvn_fs_fs/low_level.h230
-rw-r--r--subversion/libsvn_fs_fs/pack.c2061
-rw-r--r--subversion/libsvn_fs_fs/pack.h70
-rw-r--r--subversion/libsvn_fs_fs/recovery.c509
-rw-r--r--subversion/libsvn_fs_fs/recovery.h36
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.h10
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.sql3
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.c161
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.h26
-rw-r--r--subversion/libsvn_fs_fs/rev_file.c306
-rw-r--r--subversion/libsvn_fs_fs/rev_file.h145
-rw-r--r--subversion/libsvn_fs_fs/revprops.c1381
-rw-r--r--subversion/libsvn_fs_fs/revprops.h159
-rw-r--r--subversion/libsvn_fs_fs/stats.c1255
-rw-r--r--subversion/libsvn_fs_fs/structure235
-rw-r--r--subversion/libsvn_fs_fs/structure-indexes352
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.c431
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.h57
-rw-r--r--subversion/libsvn_fs_fs/transaction.c3964
-rw-r--r--subversion/libsvn_fs_fs/transaction.h294
-rw-r--r--subversion/libsvn_fs_fs/tree.c1155
-rw-r--r--subversion/libsvn_fs_fs/tree.h17
-rw-r--r--subversion/libsvn_fs_fs/util.c694
-rw-r--r--subversion/libsvn_fs_fs/util.h408
-rw-r--r--subversion/libsvn_fs_fs/verify.c883
-rw-r--r--subversion/libsvn_fs_fs/verify.h42
49 files changed, 27308 insertions, 12837 deletions
diff --git a/subversion/libsvn_fs_fs/cached_data.c b/subversion/libsvn_fs_fs/cached_data.c
new file mode 100644
index 0000000..6581a6c
--- /dev/null
+++ b/subversion/libsvn_fs_fs/cached_data.c
@@ -0,0 +1,3502 @@
+/* cached_data.c --- cached (read) access to FSFS data
+ *
+ * ====================================================================
+ * 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 "cached_data.h"
+
+#include <assert.h>
+
+#include "svn_hash.h"
+#include "svn_ctype.h"
+#include "svn_sorts.h"
+#include "private/svn_delta_private.h"
+#include "private/svn_io_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_temp_serializer.h"
+
+#include "fs_fs.h"
+#include "id.h"
+#include "index.h"
+#include "low_level.h"
+#include "pack.h"
+#include "util.h"
+#include "temp_serializer.h"
+
+#include "../libsvn_fs/fs-loader.h"
+#include "../libsvn_delta/delta.h" /* for SVN_DELTA_WINDOW_SIZE */
+
+#include "svn_private_config.h"
+
+/* forward-declare. See implementation for the docstring */
+static svn_error_t *
+block_read(void **result,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_uint64_t item_index,
+ svn_fs_fs__revision_file_t *revision_file,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Defined this to enable access logging via dgb__log_access
+#define SVN_FS_FS__LOG_ACCESS
+ */
+
+/* When SVN_FS_FS__LOG_ACCESS has been defined, write a line to console
+ * showing where REVISION, ITEM_INDEX is located in FS and use ITEM to
+ * show details on it's contents if not NULL. To support format 6 and
+ * earlier repos, ITEM_TYPE (SVN_FS_FS__ITEM_TYPE_*) must match ITEM.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * For pre-format7 repos, the display will be restricted.
+ */
+static svn_error_t *
+dbg_log_access(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_uint64_t item_index,
+ void *item,
+ apr_uint32_t item_type,
+ apr_pool_t *scratch_pool)
+{
+ /* no-op if this macro is not defined */
+#ifdef SVN_FS_FS__LOG_ACCESS
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_off_t end_offset = 0;
+ svn_fs_fs__p2l_entry_t *entry = NULL;
+ static const char *types[] = {"<n/a>", "frep ", "drep ", "fprop", "dprop",
+ "node ", "chgs ", "rep "};
+ const char *description = "";
+ const char *type = types[item_type];
+ const char *pack = "";
+ apr_off_t offset;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision,
+ scratch_pool));
+
+ /* determine rev / pack file offset */
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, revision, NULL,
+ item_index, scratch_pool));
+
+ /* constructing the pack file description */
+ if (revision < ffd->min_unpacked_rev)
+ pack = apr_psprintf(scratch_pool, "%4ld|",
+ revision / ffd->max_files_per_dir);
+
+ /* construct description if possible */
+ if (item_type == SVN_FS_FS__ITEM_TYPE_NODEREV && item != NULL)
+ {
+ node_revision_t *node = item;
+ const char *data_rep
+ = node->data_rep
+ ? apr_psprintf(scratch_pool, " d=%ld/%" APR_UINT64_T_FMT,
+ node->data_rep->revision,
+ node->data_rep->item_index)
+ : "";
+ const char *prop_rep
+ = node->prop_rep
+ ? apr_psprintf(scratch_pool, " p=%ld/%" APR_UINT64_T_FMT,
+ node->prop_rep->revision,
+ node->prop_rep->item_index)
+ : "";
+ description = apr_psprintf(scratch_pool, "%s (pc=%d%s%s)",
+ node->created_path,
+ node->predecessor_count,
+ data_rep,
+ prop_rep);
+ }
+ else if (item_type == SVN_FS_FS__ITEM_TYPE_ANY_REP)
+ {
+ svn_fs_fs__rep_header_t *header = item;
+ if (header == NULL)
+ description = " (txdelta window)";
+ else if (header->type == svn_fs_fs__rep_plain)
+ description = " PLAIN";
+ else if (header->type == svn_fs_fs__rep_self_delta)
+ description = " DELTA";
+ else
+ description = apr_psprintf(scratch_pool,
+ " DELTA against %ld/%" APR_UINT64_T_FMT,
+ header->base_revision,
+ header->base_item_index);
+ }
+ else if (item_type == SVN_FS_FS__ITEM_TYPE_CHANGES && item != NULL)
+ {
+ apr_array_header_t *changes = item;
+ switch (changes->nelts)
+ {
+ case 0: description = " no change";
+ break;
+ case 1: description = " 1 change";
+ break;
+ default: description = apr_psprintf(scratch_pool, " %d changes",
+ changes->nelts);
+ }
+ }
+
+ /* some info is only available in format7 repos */
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ /* reverse index lookup: get item description in ENTRY */
+ SVN_ERR(svn_fs_fs__p2l_entry_lookup(&entry, fs, rev_file, revision,
+ offset, scratch_pool));
+ if (entry)
+ {
+ /* more details */
+ end_offset = offset + entry->size;
+ type = types[entry->type];
+ }
+
+ /* line output */
+ printf("%5s%4lx:%04lx -%4lx:%04lx %s %7ld %5"APR_UINT64_T_FMT" %s\n",
+ pack, (long)(offset / ffd->block_size),
+ (long)(offset % ffd->block_size),
+ (long)(end_offset / ffd->block_size),
+ (long)(end_offset % ffd->block_size),
+ type, revision, item_index, description);
+ }
+ else
+ {
+ /* reduced logging for format 6 and earlier */
+ printf("%5s%10" APR_UINT64_T_HEX_FMT " %s %7ld %7" APR_UINT64_T_FMT \
+ " %s\n",
+ pack, (apr_uint64_t)(offset), type, revision, item_index,
+ description);
+ }
+
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+/* Convenience wrapper around svn_io_file_aligned_seek, taking filesystem
+ FS instead of a block size. */
+static svn_error_t *
+aligned_seek(svn_fs_t *fs,
+ apr_file_t *file,
+ apr_off_t *buffer_start,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return svn_error_trace(svn_io_file_aligned_seek(file, ffd->block_size,
+ buffer_start, offset,
+ pool));
+}
+
+/* Open the revision file for revision REV in filesystem FS and store
+ the newly opened file in FILE. Seek to location OFFSET before
+ returning. Perform temporary allocations in POOL. */
+static svn_error_t *
+open_and_seek_revision(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint64_t item,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__revision_file_t *rev_file;
+ apr_off_t offset = -1;
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool, pool));
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL, item,
+ pool));
+
+ SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, pool));
+
+ *file = rev_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the representation REP for a node-revision in filesystem FS, seek
+ to its position and store the newly opened file in FILE. Perform
+ temporary allocations in POOL. */
+static svn_error_t *
+open_and_seek_transaction(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ apr_off_t offset;
+
+ SVN_ERR(svn_fs_fs__open_proto_rev_file(file, fs, &rep->txn_id, pool, pool));
+
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, NULL, SVN_INVALID_REVNUM,
+ &rep->txn_id, rep->item_index, pool));
+ SVN_ERR(aligned_seek(fs, (*file)->file, NULL, offset, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a node-id ID, and a representation REP in filesystem FS, open
+ the correct file and seek to the correction location. Store this
+ file in *FILE_P. Perform any allocations in POOL. */
+static svn_error_t *
+open_and_seek_representation(svn_fs_fs__revision_file_t **file_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ if (! svn_fs_fs__id_txn_used(&rep->txn_id))
+ return open_and_seek_revision(file_p, fs, rep->revision, rep->item_index,
+ pool);
+ else
+ return open_and_seek_transaction(file_p, fs, rep, pool);
+}
+
+
+
+static svn_error_t *
+err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
+{
+ svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ID_NOT_FOUND, 0,
+ _("Reference to non-existent node '%s' in filesystem '%s'"),
+ id_str->data, fs->path);
+}
+
+/* Return TRUE, if FS is of a format that supports block-read and the
+ feature has been enabled. */
+static svn_boolean_t
+use_block_read(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return svn_fs_fs__use_log_addressing(fs) && ffd->use_block_read;
+}
+
+/* Get the node-revision for the node ID in FS.
+ Set *NODEREV_P to the new node-revision structure, allocated in POOL.
+ See svn_fs_fs__get_node_revision, which wraps this and adds another
+ error. */
+static svn_error_t *
+get_node_revision_body(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_boolean_t is_cached = FALSE;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (svn_fs_fs__id_is_txn(id))
+ {
+ apr_file_t *file;
+
+ /* This is a transaction node-rev. Its storage logic is very
+ different from that of rev / pack files. */
+ err = svn_io_file_open(&file,
+ svn_fs_fs__path_txn_node_rev(fs, id,
+ scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ scratch_pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err_dangling_id(fs, id));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
+ svn_stream_from_aprfile2(file,
+ FALSE,
+ scratch_pool),
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ svn_fs_fs__revision_file_t *revision_file;
+
+ /* noderevs in rev / pack files can be cached */
+ const svn_fs_fs__id_part_t *rev_item = svn_fs_fs__id_rev_item(id);
+ pair_cache_key_t key = { 0 };
+ key.revision = rev_item->revision;
+ key.second = rev_item->number;
+
+ /* Not found or not applicable. Try a noderev cache lookup.
+ * If that succeeds, we are done here. */
+ if (ffd->node_revision_cache)
+ {
+ SVN_ERR(svn_cache__get((void **) noderev_p,
+ &is_cached,
+ ffd->node_revision_cache,
+ &key,
+ result_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ /* read the data from disk */
+ SVN_ERR(open_and_seek_revision(&revision_file, fs,
+ rev_item->revision,
+ rev_item->number,
+ scratch_pool));
+
+ if (use_block_read(fs))
+ {
+ /* block-read will parse the whole block and will also return
+ the one noderev that we need right now. */
+ SVN_ERR(block_read((void **)noderev_p, fs,
+ rev_item->revision,
+ rev_item->number,
+ revision_file,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ /* physical addressing mode reading, parsing and caching */
+ SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
+ revision_file->stream,
+ result_pool,
+ scratch_pool));
+
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ (*noderev_p)->is_fresh_txn_root = FALSE;
+
+ /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
+ if (ffd->node_revision_cache)
+ SVN_ERR(svn_cache__set(ffd->node_revision_cache,
+ &key,
+ *noderev_p,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_fs__close_revision_file(revision_file));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_fs_fs__id_part_t *rev_item = svn_fs_fs__id_rev_item(id);
+
+ svn_error_t *err = get_node_revision_body(noderev_p, fs, id,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ svn_string_t *id_string = svn_fs_fs__id_unparse(id, scratch_pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt node-revision '%s'",
+ id_string->data);
+ }
+
+ SVN_ERR(dbg_log_access(fs,
+ rev_item->revision,
+ rev_item->number,
+ *noderev_p,
+ SVN_FS_FS__ITEM_TYPE_NODEREV,
+ scratch_pool));
+
+ return svn_error_trace(err);
+}
+
+
+/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
+ of the header located at OFFSET and store it in *ID_P. Allocate
+ temporary variables from POOL. */
+static svn_error_t *
+get_fs_id_at_offset(svn_fs_id_t **id_p,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, pool));
+ SVN_ERR(svn_fs_fs__read_noderev(&noderev,
+ rev_file->stream,
+ pool, pool));
+
+ /* noderev->id is const, get rid of that */
+ *id_p = svn_fs_fs__id_copy(noderev->id, pool);
+
+ /* assert that the txn_id is REV
+ * (asserting on offset would be harder because we the rev_offset is not
+ * known here) */
+ assert(svn_fs_fs__id_rev(*id_p) == rev);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
+ specifies the offset to the root node-id and to the changed path
+ information. Store the root node offset in *ROOT_OFFSET and the
+ changed path offset in *CHANGES_OFFSET. If either of these
+ pointers is NULL, do nothing with it.
+
+ Allocate temporary variables from POOL. */
+static svn_error_t *
+get_root_changes_offset(apr_off_t *root_offset,
+ apr_off_t *changes_offset,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_off_t rev_offset;
+ apr_seek_where_t seek_relative;
+ svn_stringbuf_t *trailer;
+ char buffer[64];
+ apr_off_t start;
+ apr_off_t end;
+ apr_size_t len;
+
+ /* Determine where to seek to in the file.
+
+ If we've got a pack file, we want to seek to the end of the desired
+ revision. But we don't track that, so we seek to the beginning of the
+ next revision.
+
+ Unless the next revision is in a different file, in which case, we can
+ just seek to the end of the pack file -- just like we do in the
+ non-packed case. */
+ if (rev_file->is_packed && ((rev + 1) % ffd->max_files_per_dir != 0))
+ {
+ SVN_ERR(svn_fs_fs__get_packed_offset(&end, fs, rev + 1, pool));
+ seek_relative = APR_SET;
+ }
+ else
+ {
+ seek_relative = APR_END;
+ end = 0;
+ }
+
+ /* Offset of the revision from the start of the pack file, if applicable. */
+ if (rev_file->is_packed)
+ SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, rev, pool));
+ else
+ rev_offset = 0;
+
+ /* We will assume that the last line containing the two offsets
+ will never be longer than 64 characters. */
+ SVN_ERR(svn_io_file_seek(rev_file->file, seek_relative, &end, pool));
+
+ if (end < sizeof(buffer))
+ {
+ len = (apr_size_t)end;
+ start = 0;
+ }
+ else
+ {
+ len = sizeof(buffer);
+ start = end - sizeof(buffer);
+ }
+
+ /* Read in this last block, from which we will identify the last line. */
+ SVN_ERR(aligned_seek(fs, rev_file->file, NULL, start, pool));
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, len, NULL, NULL,
+ pool));
+
+ /* Parse the last line. */
+ trailer = svn_stringbuf_ncreate(buffer, len, pool);
+ SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset,
+ changes_offset,
+ trailer,
+ rev));
+
+ /* return absolute offsets */
+ if (root_offset)
+ *root_offset += rev_offset;
+ if (changes_offset)
+ *changes_offset += rev_offset;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool));
+
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ *root_id_p = svn_fs_fs__id_create_root(rev, result_pool);
+ }
+ else
+ {
+ svn_fs_fs__revision_file_t *revision_file;
+ apr_off_t root_offset;
+ svn_fs_id_t *root_id = NULL;
+ svn_boolean_t is_cached;
+
+ SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
+ ffd->rev_root_id_cache, &rev, result_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_root_changes_offset(&root_offset, NULL,
+ revision_file, fs, rev,
+ scratch_pool));
+
+ SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
+ root_offset, result_pool));
+
+ SVN_ERR(svn_fs_fs__close_revision_file(revision_file));
+
+ SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id,
+ scratch_pool));
+
+ *root_id_p = root_id;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Describes a lazily opened rev / pack file. Instances will be shared
+ between multiple instances of rep_state_t. */
+typedef struct shared_file_t
+{
+ /* The opened file. NULL while file is not open, yet. */
+ svn_fs_fs__revision_file_t *rfile;
+
+ /* file system to open the file in */
+ svn_fs_t *fs;
+
+ /* a revision contained in the FILE. Since this file may be shared,
+ that value may be different from REP_STATE_T->REVISION. */
+ svn_revnum_t revision;
+
+ /* pool to use when creating the FILE. This guarantees that the file
+ remains open / valid beyond the respective local context that required
+ the file to be opened eventually. */
+ apr_pool_t *pool;
+} shared_file_t;
+
+/* Represents where in the current svndiff data block each
+ representation is. */
+typedef struct rep_state_t
+{
+ /* shared lazy-open rev/pack file structure */
+ shared_file_t *sfile;
+ /* The txdelta window cache to use or NULL. */
+ svn_cache__t *raw_window_cache;
+ /* Caches raw (unparsed) windows. May be NULL. */
+ svn_cache__t *window_cache;
+ /* Caches un-deltified windows. May be NULL. */
+ svn_cache__t *combined_cache;
+ /* revision containing the representation */
+ svn_revnum_t revision;
+ /* representation's item index in REVISION */
+ apr_uint64_t item_index;
+ /* length of the header at the start of the rep.
+ 0 iff this is rep is stored in a container
+ (i.e. does not have a header) */
+ apr_size_t header_size;
+ apr_off_t start; /* The starting offset for the raw
+ svndiff/plaintext data minus header.
+ -1 if the offset is yet unknown. */
+ apr_off_t current;/* The current offset relative to START. */
+ apr_off_t size; /* The on-disk size of the representation. */
+ int ver; /* If a delta, what svndiff version?
+ -1 for unknown delta version. */
+ int chunk_index; /* number of the window to read */
+} rep_state_t;
+
+/* Simple wrapper around svn_fs_fs__get_file_offset to simplify callers. */
+static svn_error_t *
+get_file_offset(apr_off_t *offset,
+ rep_state_t *rs,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_fs__get_file_offset(offset,
+ rs->sfile->rfile->file,
+ pool));
+}
+
+/* Simple wrapper around svn_io_file_aligned_seek to simplify callers. */
+static svn_error_t *
+rs_aligned_seek(rep_state_t *rs,
+ apr_off_t *buffer_start,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = rs->sfile->fs->fsap_data;
+ return svn_error_trace(svn_io_file_aligned_seek(rs->sfile->rfile->file,
+ ffd->block_size,
+ buffer_start, offset,
+ pool));
+}
+
+/* Open FILE->FILE and FILE->STREAM if they haven't been opened, yet. */
+static svn_error_t*
+auto_open_shared_file(shared_file_t *file)
+{
+ if (file->rfile == NULL)
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file->rfile, file->fs,
+ file->revision, file->pool,
+ file->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set RS->START to the begin of the representation raw in RS->FILE->FILE,
+ if that hasn't been done yet. Use POOL for temporary allocations. */
+static svn_error_t*
+auto_set_start_offset(rep_state_t *rs, apr_pool_t *pool)
+{
+ if (rs->start == -1)
+ {
+ SVN_ERR(svn_fs_fs__item_offset(&rs->start, rs->sfile->fs,
+ rs->sfile->rfile, rs->revision, NULL,
+ rs->item_index, pool));
+ rs->start += rs->header_size;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set RS->VER depending on what is found in the already open RS->FILE->FILE
+ if the diff version is still unknown. Use POOL for temporary allocations.
+ */
+static svn_error_t*
+auto_read_diff_version(rep_state_t *rs, apr_pool_t *pool)
+{
+ if (rs->ver == -1)
+ {
+ char buf[4];
+ SVN_ERR(rs_aligned_seek(rs, NULL, rs->start, pool));
+ SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, buf,
+ sizeof(buf), NULL, NULL, pool));
+
+ /* ### Layering violation */
+ if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed svndiff data in representation"));
+ rs->ver = buf[3];
+
+ rs->chunk_index = 0;
+ rs->current = 4;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* See create_rep_state, which wraps this and adds another error. */
+static svn_error_t *
+create_rep_state_body(rep_state_t **rep_state,
+ svn_fs_fs__rep_header_t **rep_header,
+ shared_file_t **shared_file,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ rep_state_t *rs = apr_pcalloc(result_pool, sizeof(*rs));
+ svn_fs_fs__rep_header_t *rh;
+ svn_boolean_t is_cached = FALSE;
+ apr_uint64_t estimated_window_storage;
+
+ /* If the hint is
+ * - given,
+ * - refers to a valid revision,
+ * - refers to a packed revision,
+ * - as does the rep we want to read, and
+ * - refers to the same pack file as the rep
+ * we can re-use the same, already open file object
+ */
+ svn_boolean_t reuse_shared_file
+ = shared_file && *shared_file && (*shared_file)->rfile
+ && SVN_IS_VALID_REVNUM((*shared_file)->revision)
+ && (*shared_file)->revision < ffd->min_unpacked_rev
+ && rep->revision < ffd->min_unpacked_rev
+ && ( ((*shared_file)->revision / ffd->max_files_per_dir)
+ == (rep->revision / ffd->max_files_per_dir));
+
+ pair_cache_key_t key;
+ key.revision = rep->revision;
+ key.second = rep->item_index;
+
+ /* continue constructing RS and RA */
+ rs->size = rep->size;
+ rs->revision = rep->revision;
+ rs->item_index = rep->item_index;
+ rs->raw_window_cache = ffd->raw_window_cache;
+ rs->ver = -1;
+ rs->start = -1;
+
+ /* Very long files stored as self-delta will produce a huge number of
+ delta windows. Don't cache them lest we don't thrash the cache.
+ Since we don't know the depth of the delta chain, let's assume, the
+ whole contents get rewritten 3 times.
+ */
+ estimated_window_storage
+ = 4 * ( (rep->expanded_size ? rep->expanded_size : rep->size)
+ + SVN_DELTA_WINDOW_SIZE);
+ estimated_window_storage = MIN(estimated_window_storage, APR_SIZE_MAX);
+
+ rs->window_cache = ffd->txdelta_window_cache
+ && svn_cache__is_cachable(ffd->txdelta_window_cache,
+ (apr_size_t)estimated_window_storage)
+ ? ffd->txdelta_window_cache
+ : NULL;
+ rs->combined_cache = ffd->combined_window_cache
+ && svn_cache__is_cachable(ffd->combined_window_cache,
+ (apr_size_t)estimated_window_storage)
+ ? ffd->combined_window_cache
+ : NULL;
+
+ /* cache lookup, i.e. skip reading the rep header if possible */
+ if (ffd->rep_header_cache && !svn_fs_fs__id_txn_used(&rep->txn_id))
+ SVN_ERR(svn_cache__get((void **) &rh, &is_cached,
+ ffd->rep_header_cache, &key, result_pool));
+
+ /* initialize the (shared) FILE member in RS */
+ if (reuse_shared_file)
+ {
+ rs->sfile = *shared_file;
+ }
+ else
+ {
+ shared_file_t *file = apr_pcalloc(result_pool, sizeof(*file));
+ file->revision = rep->revision;
+ file->pool = result_pool;
+ file->fs = fs;
+ rs->sfile = file;
+
+ /* remember the current file, if suggested by the caller */
+ if (shared_file)
+ *shared_file = file;
+ }
+
+ /* read rep header, if necessary */
+ if (!is_cached)
+ {
+ /* ensure file is open and navigate to the start of rep header */
+ if (reuse_shared_file)
+ {
+ apr_off_t offset;
+
+ /* ... we can re-use the same, already open file object.
+ * This implies that we don't read from a txn.
+ */
+ rs->sfile = *shared_file;
+ SVN_ERR(auto_open_shared_file(rs->sfile));
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rs->sfile->rfile,
+ rep->revision, NULL, rep->item_index,
+ scratch_pool));
+ SVN_ERR(rs_aligned_seek(rs, NULL, offset, scratch_pool));
+ }
+ else
+ {
+ /* otherwise, create a new file object. May or may not be
+ * an in-txn file.
+ */
+ SVN_ERR(open_and_seek_representation(&rs->sfile->rfile, fs, rep,
+ result_pool));
+ }
+
+ SVN_ERR(svn_fs_fs__read_rep_header(&rh, rs->sfile->rfile->stream,
+ result_pool, scratch_pool));
+ SVN_ERR(get_file_offset(&rs->start, rs, result_pool));
+
+ /* populate the cache if appropriate */
+ if (! svn_fs_fs__id_txn_used(&rep->txn_id))
+ {
+ if (use_block_read(fs))
+ SVN_ERR(block_read(NULL, fs, rep->revision, rep->item_index,
+ rs->sfile->rfile, result_pool, scratch_pool));
+ else
+ if (ffd->rep_header_cache)
+ SVN_ERR(svn_cache__set(ffd->rep_header_cache, &key, rh,
+ scratch_pool));
+ }
+ }
+
+ /* finalize */
+ SVN_ERR(dbg_log_access(fs, rep->revision, rep->item_index, rh,
+ SVN_FS_FS__ITEM_TYPE_ANY_REP, scratch_pool));
+
+ rs->header_size = rh->header_size;
+ *rep_state = rs;
+ *rep_header = rh;
+
+ if (rh->type == svn_fs_fs__rep_plain)
+ /* This is a plaintext, so just return the current rep_state. */
+ return SVN_NO_ERROR;
+
+ /* skip "SVNx" diff marker */
+ rs->current = 4;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the rep args for REP in filesystem FS and create a rep_state
+ for reading the representation. Return the rep_state in *REP_STATE
+ and the rep header in *REP_HEADER, both allocated in POOL.
+
+ When reading multiple reps, i.e. a skip delta chain, you may provide
+ non-NULL SHARED_FILE. (If SHARED_FILE is not NULL, in the first
+ call it should be a pointer to NULL.) The function will use this
+ variable to store the previous call results and tries to re-use it.
+ This may result in significant savings in I/O for packed files and
+ number of open file handles.
+ */
+static svn_error_t *
+create_rep_state(rep_state_t **rep_state,
+ svn_fs_fs__rep_header_t **rep_header,
+ shared_file_t **shared_file,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = create_rep_state_body(rep_state, rep_header,
+ shared_file, rep, fs,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *rep_str;
+
+ /* ### This always returns "-1" for transaction reps, because
+ ### this particular bit of code doesn't know if the rep is
+ ### stored in the protorev or in the mutable area (for props
+ ### or dir contents). It is pretty rare for FSFS to *read*
+ ### from the protorev file, though, so this is probably OK.
+ ### And anyone going to debug corruption errors is probably
+ ### going to jump straight to this comment anyway! */
+ rep_str = rep
+ ? svn_fs_fs__unparse_representation
+ (rep, ffd->format, TRUE, scratch_pool, scratch_pool)->data
+ : "(null)";
+
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt representation '%s'",
+ rep_str);
+ }
+ /* ### Call representation_string() ? */
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_fs__check_rep(representation_t *rep,
+ svn_fs_t *fs,
+ void **hint,
+ apr_pool_t *scratch_pool)
+{
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ apr_off_t offset;
+ svn_fs_fs__p2l_entry_t *entry;
+ svn_fs_fs__revision_file_t *rev_file = NULL;
+
+ /* Reuse the revision file provided by *HINT, if it is given and
+ * actually the rev / pack file that we want. */
+ svn_revnum_t start_rev = svn_fs_fs__packed_base_rev(fs, rep->revision);
+ if (hint)
+ rev_file = *(svn_fs_fs__revision_file_t **)hint;
+
+ if (rev_file == NULL || rev_file->start_revision != start_rev)
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rep->revision,
+ scratch_pool, scratch_pool));
+
+ if (hint)
+ *hint = rev_file;
+
+ /* This will auto-retry if there was a background pack. */
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rep->revision,
+ NULL, rep->item_index, scratch_pool));
+
+ /* This may fail if there is a background pack operation (can't auto-
+ retry because the item offset lookup has to be redone as well). */
+ SVN_ERR(svn_fs_fs__p2l_entry_lookup(&entry, fs, rev_file,
+ rep->revision, offset,
+ scratch_pool, scratch_pool));
+
+ if ( entry == NULL
+ || entry->type < SVN_FS_FS__ITEM_TYPE_FILE_REP
+ || entry->type > SVN_FS_FS__ITEM_TYPE_DIR_PROPS)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("No representation found at offset %s "
+ "for item %s in revision %ld"),
+ apr_off_t_toa(scratch_pool, offset),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_FMT,
+ rep->item_index),
+ rep->revision);
+ }
+ else
+ {
+ rep_state_t *rs;
+ svn_fs_fs__rep_header_t *rep_header;
+
+ /* ### Should this be using read_rep_line() directly? */
+ SVN_ERR(create_rep_state(&rs, &rep_header, (shared_file_t**)hint,
+ rep, fs, scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rep_chain_length(int *chain_length,
+ int *shard_count,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_revnum_t shard_size = ffd->max_files_per_dir
+ ? ffd->max_files_per_dir
+ : 1;
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t is_delta = FALSE;
+ int count = 0;
+ int shards = 1;
+ svn_revnum_t last_shard = rep->revision / shard_size;
+
+ /* Check whether the length of the deltification chain is acceptable.
+ * Otherwise, shared reps may form a non-skipping delta chain in
+ * extreme cases. */
+ representation_t base_rep = *rep;
+
+ /* re-use open files between iterations */
+ shared_file_t *file_hint = NULL;
+
+ svn_fs_fs__rep_header_t *header;
+
+ /* follow the delta chain towards the end but for at most
+ * MAX_CHAIN_LENGTH steps. */
+ do
+ {
+ rep_state_t *rep_state;
+
+ svn_pool_clear(iterpool);
+
+ if (base_rep.revision / shard_size != last_shard)
+ {
+ last_shard = base_rep.revision / shard_size;
+ ++shards;
+ }
+
+ SVN_ERR(create_rep_state_body(&rep_state,
+ &header,
+ &file_hint,
+ &base_rep,
+ fs,
+ subpool,
+ iterpool));
+
+ base_rep.revision = header->base_revision;
+ base_rep.item_index = header->base_item_index;
+ base_rep.size = header->base_length;
+ svn_fs_fs__id_txn_reset(&base_rep.txn_id);
+ is_delta = header->type == svn_fs_fs__rep_delta;
+
+ /* Clear it the SUBPOOL once in a while. Doing it too frequently
+ * renders the FILE_HINT ineffective. Doing too infrequently, may
+ * leave us with too many open file handles.
+ *
+ * Note that this is mostly about efficiency, with larger values
+ * being more efficient, and any non-zero value is legal here. When
+ * reading deltified contents, we may keep 10s of rev files open at
+ * the same time and the system has to cope with that. Thus, the
+ * limit of 16 chosen below is in the same ballpark.
+ */
+ ++count;
+ if (count % 16 == 0)
+ {
+ file_hint = NULL;
+ svn_pool_clear(subpool);
+ }
+ }
+ while (is_delta && base_rep.revision);
+
+ *chain_length = count;
+ *shard_count = shards;
+ svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct rep_read_baton
+{
+ /* The FS from which we're reading. */
+ svn_fs_t *fs;
+
+ /* Representation to read. */
+ representation_t rep;
+
+ /* If not NULL, this is the base for the first delta window in rs_list */
+ svn_stringbuf_t *base_window;
+
+ /* The state of all prior delta representations. */
+ apr_array_header_t *rs_list;
+
+ /* The plaintext state, if there is a plaintext. */
+ rep_state_t *src_state;
+
+ /* The index of the current delta chunk, if we are reading a delta. */
+ int chunk_index;
+
+ /* The buffer where we store undeltified data. */
+ char *buf;
+ apr_size_t buf_pos;
+ apr_size_t buf_len;
+
+ /* A checksum context for summing the data read in order to verify it.
+ Note: we don't need to use the sha1 checksum because we're only doing
+ data verification, for which md5 is perfectly safe. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ svn_boolean_t checksum_finalized;
+
+ /* The stored checksum of the representation we are reading, its
+ length, and the amount we've read so far. Some of this
+ information is redundant with rs_list and src_state, but it's
+ convenient for the checksumming code to have it here. */
+ unsigned char md5_digest[APR_MD5_DIGESTSIZE];
+
+ svn_filesize_t len;
+ svn_filesize_t off;
+
+ /* The key for the fulltext cache for this rep, if there is a
+ fulltext cache. */
+ pair_cache_key_t fulltext_cache_key;
+ /* The text we've been reading, if we're going to cache it. */
+ svn_stringbuf_t *current_fulltext;
+
+ /* If not NULL, attempt to read the data from this cache.
+ Once that lookup fails, reset it to NULL. */
+ svn_cache__t *fulltext_cache;
+
+ /* Bytes delivered from the FULLTEXT_CACHE so far. If the next
+ lookup fails, we need to skip that much data from the reconstructed
+ window stream before we continue normal operation. */
+ svn_filesize_t fulltext_delivered;
+
+ /* Used for temporary allocations during the read. */
+ apr_pool_t *pool;
+
+ /* Pool used to store file handles and other data that is persistant
+ for the entire stream read. */
+ apr_pool_t *filehandle_pool;
+};
+
+/* Set window key in *KEY to address the window described by RS.
+ For convenience, return the KEY. */
+static window_cache_key_t *
+get_window_key(window_cache_key_t *key, rep_state_t *rs)
+{
+ assert(rs->revision <= APR_UINT32_MAX);
+ key->revision = (apr_uint32_t)rs->revision;
+ key->item_index = rs->item_index;
+ key->chunk_index = rs->chunk_index;
+
+ return key;
+}
+
+/* Implement svn_cache__partial_getter_func_t for raw txdelta windows.
+ * Parse the raw data and return a svn_fs_fs__txdelta_cached_window_t.
+ */
+static svn_error_t *
+parse_raw_window(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_string_t raw_window;
+ svn_stream_t *stream;
+
+ /* unparsed and parsed window */
+ const svn_fs_fs__raw_cached_window_t *window
+ = (const svn_fs_fs__raw_cached_window_t *)data;
+ svn_fs_fs__txdelta_cached_window_t *result
+ = apr_pcalloc(result_pool, sizeof(*result));
+
+ /* create a read stream taking the raw window as input */
+ raw_window.data = svn_temp_deserializer__ptr(window,
+ (const void * const *)&window->window.data);
+ raw_window.len = window->window.len;
+ stream = svn_stream_from_string(&raw_window, result_pool);
+
+ /* parse it */
+ SVN_ERR(svn_txdelta_read_svndiff_window(&result->window, stream, 1,
+ result_pool));
+
+ /* complete the window and return it */
+ result->end_offset = window->end_offset;
+ *out = result;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read the WINDOW_P number CHUNK_INDEX for the representation given in
+ * rep state RS from the current FSFS session's cache. This will be a
+ * no-op and IS_CACHED will be set to FALSE if no cache has been given.
+ * If a cache is available IS_CACHED will inform the caller about the
+ * success of the lookup. Allocations of the window in will be made
+ * from RESULT_POOL. Use SCRATCH_POOL for temporary allocations.
+ *
+ * If the information could be found, put RS to CHUNK_INDEX.
+ */
+static svn_error_t *
+get_cached_window(svn_txdelta_window_t **window_p,
+ rep_state_t *rs,
+ int chunk_index,
+ svn_boolean_t *is_cached,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (! rs->window_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ svn_fs_fs__txdelta_cached_window_t *cached_window;
+ window_cache_key_t key = { 0 };
+ get_window_key(&key, rs);
+ key.chunk_index = chunk_index;
+ SVN_ERR(svn_cache__get((void **) &cached_window,
+ is_cached,
+ rs->window_cache,
+ &key,
+ result_pool));
+
+ /* If we did not find a parsed txdelta window, we might have a raw
+ version of it in our cache. If so, read, parse and re-cache it. */
+ if (!*is_cached && rs->raw_window_cache)
+ {
+ SVN_ERR(svn_cache__get_partial((void **) &cached_window, is_cached,
+ rs->raw_window_cache, &key,
+ parse_raw_window, NULL, result_pool));
+ if (*is_cached)
+ SVN_ERR(svn_cache__set(rs->window_cache, &key, cached_window,
+ scratch_pool));
+ }
+
+ /* Return cached information. */
+ if (*is_cached)
+ {
+ /* found it. Pass it back to the caller. */
+ *window_p = cached_window->window;
+
+ /* manipulate the RS as if we just read the data */
+ rs->current = cached_window->end_offset;
+ rs->chunk_index = chunk_index;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read for the rep state RS in the current FSFS
+ * session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_window(svn_txdelta_window_t *window,
+ rep_state_t *rs,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->window_cache)
+ {
+ /* store the window and the first offset _past_ it */
+ svn_fs_fs__txdelta_cached_window_t cached_window;
+ window_cache_key_t key = {0};
+
+ cached_window.window = window;
+ cached_window.end_offset = rs->current;
+
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ SVN_ERR(svn_cache__set(rs->window_cache,
+ get_window_key(&key, rs),
+ &cached_window,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particular) will be made from POOL.
+ */
+static svn_error_t *
+get_cached_combined_window(svn_stringbuf_t **window_p,
+ rep_state_t *rs,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ if (! rs->combined_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ window_cache_key_t key = { 0 };
+ return svn_cache__get((void **)window_p,
+ is_cached,
+ rs->combined_cache,
+ get_window_key(&key, rs),
+ pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read for the rep state RS in the current FSFS session's
+ * cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_combined_window(svn_stringbuf_t *window,
+ rep_state_t *rs,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->combined_cache)
+ {
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ window_cache_key_t key = { 0 };
+ return svn_cache__set(rs->combined_cache,
+ get_window_key(&key, rs),
+ window,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Build an array of rep_state structures in *LIST giving the delta
+ reps from first_rep to a plain-text or self-compressed rep. Set
+ *SRC_STATE to the plain-text rep we find at the end of the chain,
+ or to NULL if the final delta representation is self-compressed.
+ The representation to start from is designated by filesystem FS, id
+ ID, and representation REP.
+ Also, set *WINDOW_P to the base window content for *LIST, if it
+ could be found in cache. Otherwise, *LIST will contain the base
+ representation for the whole delta chain.
+ Finally, return the expanded size of the representation in
+ *EXPANDED_SIZE. It will take care of cases where only the on-disk
+ size is known. */
+static svn_error_t *
+build_rep_list(apr_array_header_t **list,
+ svn_stringbuf_t **window_p,
+ rep_state_t **src_state,
+ svn_filesize_t *expanded_size,
+ svn_fs_t *fs,
+ representation_t *first_rep,
+ apr_pool_t *pool)
+{
+ representation_t rep;
+ rep_state_t *rs = NULL;
+ svn_fs_fs__rep_header_t *rep_header;
+ svn_boolean_t is_cached = FALSE;
+ shared_file_t *shared_file = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ *list = apr_array_make(pool, 1, sizeof(rep_state_t *));
+ rep = *first_rep;
+
+ /* The value as stored in the data struct.
+ 0 is either for unknown length or actually zero length. */
+ *expanded_size = first_rep->expanded_size;
+
+ /* for the top-level rep, we need the rep_args */
+ SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, &rep, fs, pool,
+ iterpool));
+
+ /* Unknown size or empty representation?
+ That implies the this being the first iteration.
+ Usually size equals on-disk size, except for empty,
+ compressed representations (delta, size = 4).
+ Please note that for all non-empty deltas have
+ a 4-byte header _plus_ some data. */
+ if (*expanded_size == 0)
+ if (rep_header->type == svn_fs_fs__rep_plain || first_rep->size != 4)
+ *expanded_size = first_rep->size;
+
+ while (1)
+ {
+ svn_pool_clear(iterpool);
+
+ /* fetch state, if that has not been done already */
+ if (!rs)
+ SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file,
+ &rep, fs, pool, iterpool));
+
+ /* for txn reps, there won't be a cached combined window */
+ if (!svn_fs_fs__id_txn_used(&rep.txn_id))
+ SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
+
+ if (is_cached)
+ {
+ /* We already have a reconstructed window in our cache.
+ Write a pseudo rep_state with the full length. */
+ rs->start = 0;
+ rs->current = 0;
+ rs->size = (*window_p)->len;
+ *src_state = rs;
+ break;
+ }
+
+ if (rep_header->type == svn_fs_fs__rep_plain)
+ {
+ /* This is a plaintext, so just return the current rep_state. */
+ *src_state = rs;
+ break;
+ }
+
+ /* Push this rep onto the list. If it's self-compressed, we're done. */
+ APR_ARRAY_PUSH(*list, rep_state_t *) = rs;
+ if (rep_header->type == svn_fs_fs__rep_self_delta)
+ {
+ *src_state = NULL;
+ break;
+ }
+
+ rep.revision = rep_header->base_revision;
+ rep.item_index = rep_header->base_item_index;
+ rep.size = rep_header->base_length;
+ svn_fs_fs__id_txn_reset(&rep.txn_id);
+
+ rs = NULL;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a rep_read_baton structure for node revision NODEREV in
+ filesystem FS and store it in *RB_P. Perform all allocations in
+ POOL. If rep is mutable, it must be for file contents. */
+static svn_error_t *
+rep_read_get_baton(struct rep_read_baton **rb_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ pair_cache_key_t fulltext_cache_key,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->fs = fs;
+ b->rep = *rep;
+ b->base_window = NULL;
+ b->chunk_index = 0;
+ b->buf = NULL;
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->checksum_finalized = FALSE;
+ memcpy(b->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
+ b->len = rep->expanded_size;
+ b->off = 0;
+ b->fulltext_cache_key = fulltext_cache_key;
+ b->pool = svn_pool_create(pool);
+ b->filehandle_pool = svn_pool_create(pool);
+ b->fulltext_cache = NULL;
+ b->fulltext_delivered = 0;
+ b->current_fulltext = NULL;
+
+ /* Save our output baton. */
+ *rb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
+ window into *NWIN. Note that RS->CHUNK_INDEX will be THIS_CHUNK rather
+ than THIS_CHUNK + 1 when this function returns. */
+static svn_error_t *
+read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
+ rep_state_t *rs, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_cached;
+ apr_off_t start_offset;
+ apr_off_t end_offset;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
+
+ SVN_ERR(dbg_log_access(rs->sfile->fs, rs->revision, rs->item_index,
+ NULL, SVN_FS_FS__ITEM_TYPE_ANY_REP, scratch_pool));
+
+ /* Read the next window. But first, try to find it in the cache. */
+ SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached,
+ result_pool, scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* someone has to actually read the data from file. Open it */
+ SVN_ERR(auto_open_shared_file(rs->sfile));
+
+ /* invoke the 'block-read' feature for non-txn data.
+ However, don't do that if we are in the middle of some representation,
+ because the block is unlikely to contain other data. */
+ if ( rs->chunk_index == 0
+ && SVN_IS_VALID_REVNUM(rs->revision)
+ && use_block_read(rs->sfile->fs)
+ && rs->raw_window_cache)
+ {
+ SVN_ERR(block_read(NULL, rs->sfile->fs, rs->revision, rs->item_index,
+ rs->sfile->rfile, result_pool, scratch_pool));
+
+ /* reading the whole block probably also provided us with the
+ desired txdelta window */
+ SVN_ERR(get_cached_window(nwin, rs, this_chunk, &is_cached,
+ result_pool, scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ /* data is still not cached -> we need to read it.
+ Make sure we have all the necessary info. */
+ SVN_ERR(auto_set_start_offset(rs, scratch_pool));
+ SVN_ERR(auto_read_diff_version(rs, scratch_pool));
+
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ start_offset = rs->start + rs->current;
+ SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, scratch_pool));
+
+ /* Skip windows to reach the current chunk if we aren't there yet. */
+ iterpool = svn_pool_create(scratch_pool);
+ while (rs->chunk_index < this_chunk)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_txdelta_skip_svndiff_window(rs->sfile->rfile->file,
+ rs->ver, iterpool));
+ rs->chunk_index++;
+ SVN_ERR(get_file_offset(&start_offset, rs, iterpool));
+ rs->current = start_offset - rs->start;
+ if (rs->current >= rs->size)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read "
+ "beyond the end of the "
+ "representation"));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Actually read the next window. */
+ SVN_ERR(svn_txdelta_read_svndiff_window(nwin, rs->sfile->rfile->stream,
+ rs->ver, result_pool));
+ SVN_ERR(get_file_offset(&end_offset, rs, scratch_pool));
+ rs->current = end_offset - rs->start;
+ if (rs->current > rs->size)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read beyond "
+ "the end of the representation"));
+
+ /* the window has not been cached before, thus cache it now
+ * (if caching is used for them at all) */
+ if (SVN_IS_VALID_REVNUM(rs->revision))
+ SVN_ERR(set_cached_window(*nwin, rs, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read SIZE bytes from the representation RS and return it in *NWIN. */
+static svn_error_t *
+read_plain_window(svn_stringbuf_t **nwin, rep_state_t *rs,
+ apr_size_t size, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_off_t offset;
+
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(auto_open_shared_file(rs->sfile));
+ SVN_ERR(auto_set_start_offset(rs, scratch_pool));
+
+ offset = rs->start + rs->current;
+ SVN_ERR(rs_aligned_seek(rs, NULL, offset, scratch_pool));
+
+ /* Read the plain data. */
+ *nwin = svn_stringbuf_create_ensure(size, result_pool);
+ SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, (*nwin)->data, size,
+ NULL, NULL, result_pool));
+ (*nwin)->data[size] = 0;
+
+ /* Update RS. */
+ rs->current += (apr_off_t)size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Skip SIZE bytes from the PLAIN representation RS. */
+static svn_error_t *
+skip_plain_window(rep_state_t *rs,
+ apr_size_t size)
+{
+ /* Update RS. */
+ rs->current += (apr_off_t)size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the undeltified window that is a result of combining all deltas
+ from the current desired representation identified in *RB with its
+ base representation. Store the window in *RESULT. */
+static svn_error_t *
+get_combined_window(svn_stringbuf_t **result,
+ struct rep_read_baton *rb)
+{
+ apr_pool_t *pool, *new_pool, *window_pool;
+ int i;
+ apr_array_header_t *windows;
+ svn_stringbuf_t *source, *buf = rb->base_window;
+ rep_state_t *rs;
+ apr_pool_t *iterpool;
+
+ /* Read all windows that we need to combine. This is fine because
+ the size of each window is relatively small (100kB) and skip-
+ delta limits the number of deltas in a chain to well under 100.
+ Stop early if one of them does not depend on its predecessors. */
+ window_pool = svn_pool_create(rb->pool);
+ windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
+ iterpool = svn_pool_create(rb->pool);
+ for (i = 0; i < rb->rs_list->nelts; ++i)
+ {
+ svn_txdelta_window_t *window;
+
+ svn_pool_clear(iterpool);
+
+ rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
+ SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool,
+ iterpool));
+
+ APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
+ if (window->src_ops == 0)
+ {
+ ++i;
+ break;
+ }
+ }
+
+ /* Combine in the windows from the other delta reps. */
+ pool = svn_pool_create(rb->pool);
+ for (--i; i >= 0; --i)
+ {
+ svn_txdelta_window_t *window;
+
+ svn_pool_clear(iterpool);
+
+ rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
+ window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
+
+ /* Maybe, we've got a PLAIN start representation. If we do, read
+ as much data from it as the needed for the txdelta window's source
+ view.
+ Note that BUF / SOURCE may only be NULL in the first iteration.
+ Also note that we may have short-cut reading the delta chain --
+ in which case SRC_OPS is 0 and it might not be a PLAIN rep. */
+ source = buf;
+ if (source == NULL && rb->src_state != NULL)
+ {
+ /* Even if we don't need the source rep now, we still must keep
+ * its read offset in sync with what we might need for the next
+ * window. */
+ if (window->src_ops)
+ SVN_ERR(read_plain_window(&source, rb->src_state,
+ window->sview_len,
+ pool, iterpool));
+ else
+ SVN_ERR(skip_plain_window(rb->src_state, window->sview_len));
+ }
+
+ /* Combine this window with the current one. */
+ new_pool = svn_pool_create(rb->pool);
+ buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
+ buf->len = window->tview_len;
+
+ svn_txdelta_apply_instructions(window, source ? source->data : NULL,
+ buf->data, &buf->len);
+ if (buf->len != window->tview_len)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("svndiff window length is "
+ "corrupt"));
+
+ /* Cache windows only if the whole rep content could be read as a
+ single chunk. Only then will no other chunk need a deeper RS
+ list than the cached chunk. */
+ if ( (rb->chunk_index == 0) && (rs->current == rs->size)
+ && SVN_IS_VALID_REVNUM(rs->revision))
+ SVN_ERR(set_cached_combined_window(buf, rs, new_pool));
+
+ rs->chunk_index++;
+
+ /* Cycle pools so that we only need to hold three windows at a time. */
+ svn_pool_destroy(pool);
+ pool = new_pool;
+ }
+ svn_pool_destroy(iterpool);
+
+ svn_pool_destroy(window_pool);
+
+ *result = buf;
+ return SVN_NO_ERROR;
+}
+
+/* Returns whether or not the expanded fulltext of the file is cachable
+ * based on its size SIZE. The decision depends on the cache used by RB.
+ */
+static svn_boolean_t
+fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
+{
+ return (size < APR_SIZE_MAX)
+ && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
+}
+
+/* Close method used on streams returned by read_representation().
+ */
+static svn_error_t *
+rep_read_contents_close(void *baton)
+{
+ struct rep_read_baton *rb = baton;
+
+ svn_pool_destroy(rb->pool);
+ svn_pool_destroy(rb->filehandle_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the next *LEN bytes of the rep from our plain / delta windows
+ and store them in *BUF. */
+static svn_error_t *
+get_contents_from_windows(struct rep_read_baton *rb,
+ char *buf,
+ apr_size_t *len)
+{
+ apr_size_t copy_len, remaining = *len;
+ char *cur = buf;
+ rep_state_t *rs;
+
+ /* Special case for when there are no delta reps, only a plain
+ text. */
+ if (rb->rs_list->nelts == 0)
+ {
+ copy_len = remaining;
+ rs = rb->src_state;
+
+ if (rb->base_window != NULL)
+ {
+ /* We got the desired rep directly from the cache.
+ This is where we need the pseudo rep_state created
+ by build_rep_list(). */
+ apr_size_t offset = (apr_size_t)rs->current;
+ if (copy_len + offset > rb->base_window->len)
+ copy_len = offset < rb->base_window->len
+ ? rb->base_window->len - offset
+ : 0ul;
+
+ memcpy (cur, rb->base_window->data + offset, copy_len);
+ }
+ else
+ {
+ apr_off_t offset;
+ if (((apr_off_t) copy_len) > rs->size - rs->current)
+ copy_len = (apr_size_t) (rs->size - rs->current);
+
+ SVN_ERR(auto_open_shared_file(rs->sfile));
+ SVN_ERR(auto_set_start_offset(rs, rb->pool));
+
+ offset = rs->start + rs->current;
+ SVN_ERR(rs_aligned_seek(rs, NULL, offset, rb->pool));
+ SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, cur,
+ copy_len, NULL, NULL, rb->pool));
+ }
+
+ rs->current += copy_len;
+ *len = copy_len;
+ return SVN_NO_ERROR;
+ }
+
+ while (remaining > 0)
+ {
+ /* If we have buffered data from a previous chunk, use that. */
+ if (rb->buf)
+ {
+ /* Determine how much to copy from the buffer. */
+ copy_len = rb->buf_len - rb->buf_pos;
+ if (copy_len > remaining)
+ copy_len = remaining;
+
+ /* Actually copy the data. */
+ memcpy(cur, rb->buf + rb->buf_pos, copy_len);
+ rb->buf_pos += copy_len;
+ cur += copy_len;
+ remaining -= copy_len;
+
+ /* If the buffer is all used up, clear it and empty the
+ local pool. */
+ if (rb->buf_pos == rb->buf_len)
+ {
+ svn_pool_clear(rb->pool);
+ rb->buf = NULL;
+ }
+ }
+ else
+ {
+ svn_stringbuf_t *sbuf = NULL;
+
+ rs = APR_ARRAY_IDX(rb->rs_list, 0, rep_state_t *);
+ if (rs->current == rs->size)
+ break;
+
+ /* Get more buffered data by evaluating a chunk. */
+ SVN_ERR(get_combined_window(&sbuf, rb));
+
+ rb->chunk_index++;
+ rb->buf_len = sbuf->len;
+ rb->buf = sbuf->data;
+ rb->buf_pos = 0;
+ }
+ }
+
+ *len = cur - buf;
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton type for get_fulltext_partial. */
+typedef struct fulltext_baton_t
+{
+ /* Target buffer to write to; of at least LEN bytes. */
+ char *buffer;
+
+ /* Offset within the respective fulltext at which we shall start to
+ copy data into BUFFER. */
+ apr_size_t start;
+
+ /* Number of bytes to copy. The actual amount may be less in case
+ the fulltext is short(er). */
+ apr_size_t len;
+
+ /* Number of bytes actually copied into BUFFER. */
+ apr_size_t read;
+} fulltext_baton_t;
+
+/* Implement svn_cache__partial_getter_func_t for fulltext caches.
+ * From the fulltext in DATA, we copy the range specified by the
+ * fulltext_baton_t* BATON into the buffer provided by that baton.
+ * OUT and RESULT_POOL are not used.
+ */
+static svn_error_t *
+get_fulltext_partial(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ fulltext_baton_t *fulltext_baton = baton;
+
+ /* We cached the fulltext with an NUL appended to it. */
+ apr_size_t fulltext_len = data_len - 1;
+
+ /* Clip the copy range to what the fulltext size allows. */
+ apr_size_t start = MIN(fulltext_baton->start, fulltext_len);
+ fulltext_baton->read = MIN(fulltext_len - start, fulltext_baton->len);
+
+ /* Copy the data to the output buffer and be done. */
+ memcpy(fulltext_baton->buffer, (const char *)data + start,
+ fulltext_baton->read);
+
+ return SVN_NO_ERROR;
+}
+
+/* Find the fulltext specified in BATON in the fulltext cache given
+ * as well by BATON. If that succeeds, set *CACHED to TRUE and copy
+ * up to the next *LEN bytes into BUFFER. Set *LEN to the actual
+ * number of bytes copied.
+ */
+static svn_error_t *
+get_contents_from_fulltext(svn_boolean_t *cached,
+ struct rep_read_baton *baton,
+ char *buffer,
+ apr_size_t *len)
+{
+ void *dummy;
+ fulltext_baton_t fulltext_baton;
+
+ SVN_ERR_ASSERT((apr_size_t)baton->fulltext_delivered
+ == baton->fulltext_delivered);
+ fulltext_baton.buffer = buffer;
+ fulltext_baton.start = (apr_size_t)baton->fulltext_delivered;
+ fulltext_baton.len = *len;
+ fulltext_baton.read = 0;
+
+ SVN_ERR(svn_cache__get_partial(&dummy, cached, baton->fulltext_cache,
+ &baton->fulltext_cache_key,
+ get_fulltext_partial, &fulltext_baton,
+ baton->pool));
+
+ if (*cached)
+ {
+ baton->fulltext_delivered += fulltext_baton.read;
+ *len = fulltext_baton.read;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Determine the optimal size of a string buf that shall receive a
+ * (full-) text of NEEDED bytes.
+ *
+ * The critical point is that those buffers may be very large and
+ * can cause memory fragmentation. We apply simple heuristics to
+ * make fragmentation less likely.
+ */
+static apr_size_t
+optimimal_allocation_size(apr_size_t needed)
+{
+ /* For all allocations, assume some overhead that is shared between
+ * OS memory managemnt, APR memory management and svn_stringbuf_t. */
+ const apr_size_t overhead = 0x400;
+ apr_size_t optimal;
+
+ /* If an allocation size if safe for other ephemeral buffers, it should
+ * be safe for ours. */
+ if (needed <= SVN__STREAM_CHUNK_SIZE)
+ return needed;
+
+ /* Paranoia edge case:
+ * Skip our heuristics if they created arithmetical overflow.
+ * Beware to make this test work for NEEDED = APR_SIZE_MAX as well! */
+ if (needed >= APR_SIZE_MAX / 2 - overhead)
+ return needed;
+
+ /* As per definition SVN__STREAM_CHUNK_SIZE is a power of two.
+ * Since we know NEEDED to be larger than that, use it as the
+ * starting point.
+ *
+ * Heuristics: Allocate a power-of-two number of bytes that fit
+ * NEEDED plus some OVERHEAD. The APR allocator
+ * will round it up to the next full page size.
+ */
+ optimal = SVN__STREAM_CHUNK_SIZE;
+ while (optimal - overhead < needed)
+ optimal *= 2;
+
+ /* This is above or equal to NEEDED. */
+ return optimal - overhead;
+}
+
+/* After a fulltext cache lookup failure, we will continue to read from
+ * combined delta or plain windows. However, we must first make that data
+ * stream in BATON catch up tho the position LEN already delivered from the
+ * fulltext cache. Also, we need to store the reconstructed fulltext if we
+ * want to cache it at the end.
+ */
+static svn_error_t *
+skip_contents(struct rep_read_baton *baton,
+ svn_filesize_t len)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Do we want to cache the reconstructed fulltext? */
+ if (SVN_IS_VALID_REVNUM(baton->fulltext_cache_key.revision))
+ {
+ char *buffer;
+ svn_filesize_t to_alloc = MAX(len, baton->len);
+
+ /* This should only be happening if BATON->LEN and LEN are
+ * cacheable, implying they fit into memory. */
+ SVN_ERR_ASSERT((apr_size_t)to_alloc == to_alloc);
+
+ /* Allocate the fulltext buffer. */
+ baton->current_fulltext = svn_stringbuf_create_ensure(
+ optimimal_allocation_size((apr_size_t)to_alloc),
+ baton->filehandle_pool);
+
+ /* Read LEN bytes from the window stream and store the data
+ * in the fulltext buffer (will be filled by further reads later). */
+ baton->current_fulltext->len = (apr_size_t)len;
+ baton->current_fulltext->data[(apr_size_t)len] = 0;
+
+ buffer = baton->current_fulltext->data;
+ while (len > 0 && !err)
+ {
+ apr_size_t to_read = (apr_size_t)len;
+ err = get_contents_from_windows(baton, buffer, &to_read);
+ len -= to_read;
+ buffer += to_read;
+ }
+ }
+ else if (len > 0)
+ {
+ /* Simply drain LEN bytes from the window stream. */
+ apr_pool_t *subpool = subpool = svn_pool_create(baton->pool);
+ char *buffer = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE);
+
+ while (len > 0 && !err)
+ {
+ apr_size_t to_read = len > SVN__STREAM_CHUNK_SIZE
+ ? SVN__STREAM_CHUNK_SIZE
+ : (apr_size_t)len;
+
+ err = get_contents_from_windows(baton, buffer, &to_read);
+ len -= to_read;
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ return svn_error_trace(err);
+}
+
+/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
+ representation and store them in *BUF. Sum as we read and verify
+ the MD5 sum at the end. */
+static svn_error_t *
+rep_read_contents(void *baton,
+ char *buf,
+ apr_size_t *len)
+{
+ struct rep_read_baton *rb = baton;
+
+ /* Get data from the fulltext cache for as long as we can. */
+ if (rb->fulltext_cache)
+ {
+ svn_boolean_t cached;
+ SVN_ERR(get_contents_from_fulltext(&cached, rb, buf, len));
+ if (cached)
+ return SVN_NO_ERROR;
+
+ /* Cache miss. From now on, we will never read from the fulltext
+ * cache for this representation anymore. */
+ rb->fulltext_cache = NULL;
+ }
+
+ /* No fulltext cache to help us. We must read from the window stream. */
+ if (!rb->rs_list)
+ {
+ /* Window stream not initialized, yet. Do it now. */
+ SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window,
+ &rb->src_state, &rb->len, rb->fs, &rb->rep,
+ rb->filehandle_pool));
+
+ /* In case we did read from the fulltext cache before, make the
+ * window stream catch up. Also, initialize the fulltext buffer
+ * if we want to cache the fulltext at the end. */
+ SVN_ERR(skip_contents(rb, rb->fulltext_delivered));
+ }
+
+ /* Get the next block of data.
+ * Keep in mind that the representation might be empty and leave us
+ * already positioned at the end of the rep. */
+ if (rb->off == rb->len)
+ *len = 0;
+ else
+ SVN_ERR(get_contents_from_windows(rb, buf, len));
+
+ if (rb->current_fulltext)
+ svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
+
+ /* Perform checksumming. We want to check the checksum as soon as
+ the last byte of data is read, in case the caller never performs
+ a short read, but we don't want to finalize the MD5 context
+ twice. */
+ if (!rb->checksum_finalized)
+ {
+ SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
+ rb->off += *len;
+ if (rb->off == rb->len)
+ {
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t expected;
+ expected.kind = svn_checksum_md5;
+ expected.digest = rb->md5_digest;
+
+ rb->checksum_finalized = TRUE;
+ SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
+ rb->pool));
+ if (!svn_checksum_match(md5_checksum, &expected))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(&expected, md5_checksum,
+ rb->pool,
+ _("Checksum mismatch while reading representation")),
+ NULL);
+ }
+ }
+
+ if (rb->off == rb->len && rb->current_fulltext)
+ {
+ fs_fs_data_t *ffd = rb->fs->fsap_data;
+ SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
+ rb->current_fulltext, rb->pool));
+ rb->current_fulltext = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_contents(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ svn_boolean_t cache_fulltext,
+ apr_pool_t *pool)
+{
+ if (! rep)
+ {
+ *contents_p = svn_stream_empty(pool);
+ }
+ else
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
+ struct rep_read_baton *rb;
+
+ pair_cache_key_t fulltext_cache_key = { 0 };
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->item_index;
+
+ /* Initialize the reader baton. Some members may added lazily
+ * while reading from the stream */
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
+
+ /* Make the stream attempt fulltext cache lookups if the fulltext
+ * is cacheable. If it is not, then also don't try to buffer and
+ * cache it. */
+ if (ffd->fulltext_cache && cache_fulltext
+ && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, len))
+ {
+ rb->fulltext_cache = ffd->fulltext_cache;
+ }
+ else
+ {
+ /* This will also prevent the reconstructed fulltext from being
+ put into the cache. */
+ rb->fulltext_cache_key.revision = SVN_INVALID_REVNUM;
+ }
+
+ *contents_p = svn_stream_create(rb, pool);
+ svn_stream_set_read2(*contents_p, NULL /* only full read support */,
+ rep_read_contents);
+ svn_stream_set_close(*contents_p, rep_read_contents_close);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_contents_from_file(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_file_t *file,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *rb;
+ pair_cache_key_t fulltext_cache_key = { SVN_INVALID_REVNUM, 0 };
+ rep_state_t *rs = apr_pcalloc(pool, sizeof(*rs));
+ svn_fs_fs__rep_header_t *rh;
+
+ /* Initialize the reader baton. Some members may added lazily
+ * while reading from the stream. */
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
+
+ /* Continue constructing RS. Leave caches as NULL. */
+ rs->size = rep->size;
+ rs->revision = SVN_INVALID_REVNUM;
+ rs->item_index = 0;
+ rs->ver = -1;
+ rs->start = -1;
+
+ /* Provide just enough file access info to allow for a basic read from
+ * FILE but leave all index / footer info with empty values b/c FILE
+ * probably is not a complete revision file. */
+ rs->sfile = apr_pcalloc(pool, sizeof(*rs->sfile));
+ rs->sfile->revision = rep->revision;
+ rs->sfile->pool = pool;
+ rs->sfile->fs = fs;
+ rs->sfile->rfile = apr_pcalloc(pool, sizeof(*rs->sfile->rfile));
+ rs->sfile->rfile->start_revision = SVN_INVALID_REVNUM;
+ rs->sfile->rfile->file = file;
+ rs->sfile->rfile->stream = svn_stream_from_aprfile2(file, TRUE, pool);
+
+ /* Read the rep header. */
+ SVN_ERR(aligned_seek(fs, file, NULL, offset, pool));
+ SVN_ERR(svn_fs_fs__read_rep_header(&rh, rs->sfile->rfile->stream,
+ pool, pool));
+ SVN_ERR(get_file_offset(&rs->start, rs, pool));
+ rs->header_size = rh->header_size;
+
+ /* Log the access. */
+ SVN_ERR(dbg_log_access(fs, SVN_INVALID_REVNUM, 0, rh,
+ SVN_FS_FS__ITEM_TYPE_ANY_REP, pool));
+
+ /* Build the representation list (delta chain). */
+ if (rh->type == svn_fs_fs__rep_plain)
+ {
+ rb->rs_list = apr_array_make(pool, 0, sizeof(rep_state_t *));
+ rb->src_state = rs;
+ }
+ else if (rh->type == svn_fs_fs__rep_self_delta)
+ {
+ rb->rs_list = apr_array_make(pool, 1, sizeof(rep_state_t *));
+ APR_ARRAY_PUSH(rb->rs_list, rep_state_t *) = rs;
+ rb->src_state = NULL;
+ }
+ else
+ {
+ representation_t next_rep = { 0 };
+
+ /* skip "SVNx" diff marker */
+ rs->current = 4;
+
+ /* REP's base rep is inside a proper revision.
+ * It can be reconstructed in the usual way. */
+ next_rep.revision = rh->base_revision;
+ next_rep.item_index = rh->base_item_index;
+ next_rep.size = rh->base_length;
+ svn_fs_fs__id_txn_reset(&next_rep.txn_id);
+
+ SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window,
+ &rb->src_state, &rb->len, rb->fs, &next_rep,
+ rb->filehandle_pool));
+
+ /* Insert the access to REP as the first element of the delta chain. */
+ svn_sort__array_insert(rb->rs_list, &rs, 0);
+ }
+
+ /* Now, the baton is complete and we can assemble the stream around it. */
+ *contents_p = svn_stream_create(rb, pool);
+ svn_stream_set_read2(*contents_p, NULL /* only full read support */,
+ rep_read_contents);
+ svn_stream_set_close(*contents_p, rep_read_contents_close);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for cache_access_wrapper. Wraps the original parameters of
+ * svn_fs_fs__try_process_file_content().
+ */
+typedef struct cache_access_wrapper_baton_t
+{
+ svn_fs_process_contents_func_t func;
+ void* baton;
+} cache_access_wrapper_baton_t;
+
+/* Wrapper to translate between svn_fs_process_contents_func_t and
+ * svn_cache__partial_getter_func_t.
+ */
+static svn_error_t *
+cache_access_wrapper(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ cache_access_wrapper_baton_t *wrapper_baton = baton;
+
+ SVN_ERR(wrapper_baton->func((const unsigned char *)data,
+ data_len - 1, /* cache adds terminating 0 */
+ wrapper_baton->baton,
+ pool));
+
+ /* non-NULL value to signal the calling cache that all went well */
+ *out = baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ representation_t *rep = noderev->data_rep;
+ if (rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key = { 0 };
+
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->item_index;
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, rep->expanded_size))
+ {
+ cache_access_wrapper_baton_t wrapper_baton;
+ void *dummy = NULL;
+
+ wrapper_baton.func = processor;
+ wrapper_baton.baton = baton;
+ return svn_cache__get_partial(&dummy, success,
+ ffd->fulltext_cache,
+ &fulltext_cache_key,
+ cache_access_wrapper,
+ &wrapper_baton,
+ pool);
+ }
+ }
+
+ *success = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton used when reading delta windows. */
+struct delta_read_baton
+{
+ rep_state_t *rs;
+ unsigned char md5_digest[APR_MD5_DIGESTSIZE];
+};
+
+/* This implements the svn_txdelta_next_window_fn_t interface. */
+static svn_error_t *
+delta_read_next_window(svn_txdelta_window_t **window, void *baton,
+ apr_pool_t *pool)
+{
+ struct delta_read_baton *drb = baton;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ *window = NULL;
+ if (drb->rs->current < drb->rs->size)
+ {
+ SVN_ERR(read_delta_window(window, drb->rs->chunk_index, drb->rs, pool,
+ scratch_pool));
+ drb->rs->chunk_index++;
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_txdelta_md5_digest_fn_t interface. */
+static const unsigned char *
+delta_read_md5_digest(void *baton)
+{
+ struct delta_read_baton *drb = baton;
+ return drb->md5_digest;
+}
+
+/* Return a txdelta stream for on-disk representation REP_STATE
+ * of TARGET. Allocate the result in POOL.
+ */
+static svn_txdelta_stream_t *
+get_storaged_delta_stream(rep_state_t *rep_state,
+ node_revision_t *target,
+ apr_pool_t *pool)
+{
+ /* Create the delta read baton. */
+ struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
+ drb->rs = rep_state;
+ memcpy(drb->md5_digest, target->data_rep->md5_digest,
+ sizeof(drb->md5_digest));
+ return svn_txdelta_stream_create(drb, delta_read_next_window,
+ delta_read_md5_digest, pool);
+}
+
+svn_error_t *
+svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source_stream, *target_stream;
+ rep_state_t *rep_state;
+ svn_fs_fs__rep_header_t *rep_header;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Try a shortcut: if the target is stored as a delta against the source,
+ then just use that delta. However, prefer using the fulltext cache
+ whenever that is available. */
+ if (target->data_rep && (source || ! ffd->fulltext_cache))
+ {
+ /* Read target's base rep if any. */
+ SVN_ERR(create_rep_state(&rep_state, &rep_header, NULL,
+ target->data_rep, fs, pool, pool));
+
+ if (source && source->data_rep && target->data_rep)
+ {
+ /* If that matches source, then use this delta as is.
+ Note that we want an actual delta here. E.g. a self-delta would
+ not be good enough. */
+ if (rep_header->type == svn_fs_fs__rep_delta
+ && rep_header->base_revision == source->data_rep->revision
+ && rep_header->base_item_index == source->data_rep->item_index)
+ {
+ *stream_p = get_storaged_delta_stream(rep_state, target, pool);
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (!source)
+ {
+ /* We want a self-delta. There is a fair chance that TARGET got
+ added in this revision and is already stored in the requested
+ format. */
+ if (rep_header->type == svn_fs_fs__rep_self_delta)
+ {
+ *stream_p = get_storaged_delta_stream(rep_state, target, pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Don't keep file handles open for longer than necessary. */
+ if (rep_state->sfile->rfile)
+ {
+ SVN_ERR(svn_fs_fs__close_revision_file(rep_state->sfile->rfile));
+ rep_state->sfile->rfile = NULL;
+ }
+ }
+
+ /* Read both fulltexts and construct a delta. */
+ if (source)
+ SVN_ERR(svn_fs_fs__get_contents(&source_stream, fs, source->data_rep,
+ TRUE, pool));
+ else
+ source_stream = svn_stream_empty(pool);
+ SVN_ERR(svn_fs_fs__get_contents(&target_stream, fs, target->data_rep,
+ TRUE, pool));
+
+ /* Because source and target stream will already verify their content,
+ * there is no need to do this once more. In particular if the stream
+ * content is being fetched from cache. */
+ svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE when all svn_fs_dirent_t* in ENTRIES are already sorted
+ by their respective name. */
+static svn_boolean_t
+sorted(apr_array_header_t *entries)
+{
+ int i;
+
+ const svn_fs_dirent_t * const *dirents = (const void *)entries->elts;
+ for (i = 0; i < entries->nelts-1; ++i)
+ if (strcmp(dirents[i]->name, dirents[i+1]->name) > 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Compare the names of the two dirents given in **A and **B. */
+static int
+compare_dirents(const void *a, const void *b)
+{
+ const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a);
+ const svn_fs_dirent_t *rhs = *((const svn_fs_dirent_t * const *) b);
+
+ return strcmp(lhs->name, rhs->name);
+}
+
+/* Compare the name of the dirents given in **A with the C string in *B. */
+static int
+compare_dirent_name(const void *a, const void *b)
+{
+ const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a);
+ const char *rhs = b;
+
+ return strcmp(lhs->name, rhs);
+}
+
+/* Into ENTRIES, read all directories entries from the key-value text in
+ * STREAM. If INCREMENTAL is TRUE, read until the end of the STREAM and
+ * update the data. ID is provided for nicer error messages.
+ */
+static svn_error_t *
+read_dir_entries(apr_array_header_t *entries,
+ svn_stream_t *stream,
+ svn_boolean_t incremental,
+ const svn_fs_id_t *id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *hash = incremental ? svn_hash__make(scratch_pool) : NULL;
+ const char *terminator = SVN_HASH_TERMINATOR;
+
+ /* Read until the terminator (non-incremental) or the end of STREAM
+ (incremental mode). In the latter mode, we use a temporary HASH
+ to make updating and removing entries cheaper. */
+ while (1)
+ {
+ svn_hash__entry_t entry;
+ svn_fs_dirent_t *dirent;
+ char *str;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_hash__read_entry(&entry, stream, terminator,
+ incremental, iterpool));
+
+ /* End of directory? */
+ if (entry.key == NULL)
+ {
+ /* In incremental mode, we skip the terminator and read the
+ increments following it until the end of the stream. */
+ if (incremental && terminator)
+ terminator = NULL;
+ else
+ break;
+ }
+
+ /* Deleted entry? */
+ if (entry.val == NULL)
+ {
+ /* We must be in incremental mode */
+ assert(hash);
+ apr_hash_set(hash, entry.key, entry.keylen, NULL);
+ continue;
+ }
+
+ /* Add a new directory entry. */
+ dirent = apr_pcalloc(result_pool, sizeof(*dirent));
+ dirent->name = apr_pstrmemdup(result_pool, entry.key, entry.keylen);
+
+ str = svn_cstring_tokenize(" ", &entry.val);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ svn_fs_fs__id_unparse(id, scratch_pool)->data);
+
+ if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
+ {
+ dirent->kind = svn_node_file;
+ }
+ else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
+ {
+ dirent->kind = svn_node_dir;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ svn_fs_fs__id_unparse(id, scratch_pool)->data);
+ }
+
+ str = svn_cstring_tokenize(" ", &entry.val);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ svn_fs_fs__id_unparse(id, scratch_pool)->data);
+
+ SVN_ERR(svn_fs_fs__id_parse(&dirent->id, str, result_pool));
+
+ /* In incremental mode, update the hash; otherwise, write to the
+ * final array. Be sure to use hash keys that survive this iteration.
+ */
+ if (incremental)
+ apr_hash_set(hash, dirent->name, entry.keylen, dirent);
+ else
+ APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = dirent;
+ }
+
+ /* Convert container to a sorted array. */
+ if (incremental)
+ {
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(iterpool, hash); hi; hi = apr_hash_next(hi))
+ APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = apr_hash_this_val(hi);
+ }
+
+ if (!sorted(entries))
+ svn_sort__array(entries, compare_dirents);
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the contents of a directory into ENTRIES. Values are stored
+ as filename to string mappings; further conversion is necessary to
+ convert them into svn_fs_dirent_t values. */
+static svn_error_t *
+get_dir_contents(apr_array_header_t **entries,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *contents;
+
+ *entries = apr_array_make(result_pool, 16, sizeof(svn_fs_dirent_t *));
+ if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id))
+ {
+ const char *filename
+ = svn_fs_fs__path_txn_node_children(fs, noderev->id, scratch_pool);
+
+ /* The representation is mutable. Read the old directory
+ contents from the mutable children file, followed by the
+ changes we've made in this transaction. */
+ SVN_ERR(svn_stream_open_readonly(&contents, filename, scratch_pool,
+ scratch_pool));
+ SVN_ERR(read_dir_entries(*entries, contents, TRUE, noderev->id,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_stream_close(contents));
+ }
+ else if (noderev->data_rep)
+ {
+ /* Undeltify content before parsing it. Otherwise, we could only
+ * parse it byte-by-byte.
+ */
+ apr_size_t len = noderev->data_rep->expanded_size
+ ? (apr_size_t)noderev->data_rep->expanded_size
+ : (apr_size_t)noderev->data_rep->size;
+ svn_stringbuf_t *text;
+
+ /* The representation is immutable. Read it normally. */
+ SVN_ERR(svn_fs_fs__get_contents(&contents, fs, noderev->data_rep,
+ FALSE, scratch_pool));
+ SVN_ERR(svn_stringbuf_from_stream(&text, contents, len, scratch_pool));
+ SVN_ERR(svn_stream_close(contents));
+
+ /* de-serialize hash */
+ contents = svn_stream_from_stringbuf(text, scratch_pool);
+ SVN_ERR(read_dir_entries(*entries, contents, FALSE, noderev->id,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the cache object in FS responsible to storing the directory the
+ * NODEREV plus the corresponding *KEY. If no cache exists, return NULL.
+ * PAIR_KEY must point to some key struct, which does not need to be
+ * initialized. We use it to avoid dynamic allocation.
+ */
+static svn_cache__t *
+locate_dir_cache(svn_fs_t *fs,
+ const void **key,
+ pair_cache_key_t *pair_key,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (svn_fs_fs__id_is_txn(noderev->id))
+ {
+ /* data in txns requires the expensive fs_id-based addressing mode */
+ *key = svn_fs_fs__id_unparse(noderev->id, pool)->data;
+ return ffd->txn_dir_cache;
+ }
+ else
+ {
+ /* committed data can use simple rev,item pairs */
+ if (noderev->data_rep)
+ {
+ pair_key->revision = noderev->data_rep->revision;
+ pair_key->second = noderev->data_rep->item_index;
+ *key = pair_key;
+ }
+ else
+ {
+ /* no data rep -> empty directory.
+ A NULL key causes a cache miss. */
+ *key = NULL;
+ }
+
+ return ffd->dir_cache;
+ }
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ pair_cache_key_t pair_key = { 0 };
+ const void *key;
+
+ /* find the cache we may use */
+ svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev,
+ scratch_pool);
+ if (cache)
+ {
+ svn_boolean_t found;
+
+ SVN_ERR(svn_cache__get((void **)entries_p, &found, cache, key,
+ result_pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* Read in the directory contents. */
+ SVN_ERR(get_dir_contents(entries_p, fs, noderev, result_pool,
+ scratch_pool));
+
+ /* Update the cache, if we are to use one.
+ *
+ * Don't even attempt to serialize very large directories; it would cause
+ * an unnecessary memory allocation peak. 150 bytes/entry is about right.
+ */
+ if (cache && svn_cache__is_cachable(cache, 150 * (*entries_p)->nelts))
+ SVN_ERR(svn_cache__set(cache, key, *entries_p, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_fs_dirent_t *
+svn_fs_fs__find_dir_entry(apr_array_header_t *entries,
+ const char *name,
+ int *hint)
+{
+ svn_fs_dirent_t **result
+ = svn_sort__array_lookup(entries, name, hint, compare_dirent_name);
+ return result ? *result : NULL;
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t found = FALSE;
+
+ /* find the cache we may use */
+ pair_cache_key_t pair_key = { 0 };
+ const void *key;
+ svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev,
+ scratch_pool);
+ if (cache)
+ {
+ /* Cache lookup. */
+ SVN_ERR(svn_cache__get_partial((void **)dirent,
+ &found,
+ cache,
+ key,
+ svn_fs_fs__extract_dir_entry,
+ (void*)name,
+ result_pool));
+ }
+
+ /* fetch data from disk if we did not find it in the cache */
+ if (! found)
+ {
+ apr_array_header_t *entries;
+ svn_fs_dirent_t *entry;
+ svn_fs_dirent_t *entry_copy = NULL;
+
+ /* read the dir from the file system. It will probably be put it
+ into the cache for faster lookup in future calls. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
+ scratch_pool, scratch_pool));
+
+ /* find desired entry and return a copy in POOL, if found */
+ entry = svn_fs_fs__find_dir_entry(entries, name, NULL);
+ if (entry)
+ {
+ entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
+ entry_copy->name = apr_pstrdup(result_pool, entry->name);
+ entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
+ entry_copy->kind = entry->kind;
+ }
+
+ *dirent = entry_copy;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ apr_hash_t *proplist;
+ svn_stream_t *stream;
+
+ if (noderev->prop_rep && svn_fs_fs__id_txn_used(&noderev->prop_rep->txn_id))
+ {
+ svn_error_t *err;
+ const char *filename
+ = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
+ proplist = apr_hash_make(pool);
+
+ SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
+ err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
+ if (err)
+ {
+ svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
+
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_quick_wrapf(err,
+ _("malformed property list for node-revision '%s' in '%s'"),
+ id_str->data, filename);
+ }
+ SVN_ERR(svn_stream_close(stream));
+ }
+ else if (noderev->prop_rep)
+ {
+ svn_error_t *err;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ representation_t *rep = noderev->prop_rep;
+ pair_cache_key_t key = { 0 };
+
+ key.revision = rep->revision;
+ key.second = rep->item_index;
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->properties_cache, &key, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ proplist = apr_hash_make(pool);
+ SVN_ERR(svn_fs_fs__get_contents(&stream, fs, noderev->prop_rep, FALSE,
+ pool));
+ err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
+ if (err)
+ {
+ svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
+
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_quick_wrapf(err,
+ _("malformed property list for node-revision '%s'"),
+ id_str->data);
+ }
+ SVN_ERR(svn_stream_close(stream));
+
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
+ }
+ else
+ {
+ /* return an empty prop list if the node doesn't have any props */
+ proplist = apr_hash_make(pool);
+ }
+
+ *proplist_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool)
+{
+ apr_off_t changes_offset = SVN_FS_FS__ITEM_INDEX_CHANGES;
+ svn_fs_fs__revision_file_t *revision_file;
+ svn_boolean_t found;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+
+ /* try cache lookup first */
+
+ if (ffd->changes_cache)
+ {
+ SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
+ &rev, result_pool));
+ }
+ else
+ {
+ found = FALSE;
+ }
+
+ if (!found)
+ {
+ /* read changes from revision file */
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev,
+ scratch_pool, scratch_pool));
+
+ if (use_block_read(fs))
+ {
+ /* 'block-read' will also provide us with the desired data */
+ SVN_ERR(block_read((void **)changes, fs,
+ rev, SVN_FS_FS__ITEM_INDEX_CHANGES,
+ revision_file, result_pool, scratch_pool));
+ }
+ else
+ {
+ /* Addressing is very different for old formats
+ * (needs to read the revision trailer). */
+ if (svn_fs_fs__use_log_addressing(fs))
+ SVN_ERR(svn_fs_fs__item_offset(&changes_offset, fs,
+ revision_file, rev, NULL,
+ SVN_FS_FS__ITEM_INDEX_CHANGES,
+ scratch_pool));
+ else
+ SVN_ERR(get_root_changes_offset(NULL, &changes_offset,
+ revision_file, fs, rev,
+ scratch_pool));
+
+ /* Actual reading and parsing are the same, though. */
+ SVN_ERR(aligned_seek(fs, revision_file->file, NULL, changes_offset,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__read_changes(changes, revision_file->stream,
+ result_pool, scratch_pool));
+
+ /* cache for future reference */
+
+ if (ffd->changes_cache)
+ {
+ /* Guesstimate for the size of the in-cache representation. */
+ apr_size_t estimated_size = (apr_size_t)250 * (*changes)->nelts;
+
+ /* Don't even serialize data that probably won't fit into the
+ * cache. This often implies that either CHANGES is very
+ * large, memory is scarce or both. Having a huge temporary
+ * copy would not be a good thing in either case. */
+ if (svn_cache__is_cachable(ffd->changes_cache, estimated_size))
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_fs_fs__close_revision_file(revision_file));
+ }
+
+ SVN_ERR(dbg_log_access(fs, rev, changes_offset, *changes,
+ SVN_FS_FS__ITEM_TYPE_CHANGES, scratch_pool));
+
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Inialize the representation read state RS for the given REP_HEADER and
+ * p2l index ENTRY. If not NULL, assign FILE and STREAM to RS.
+ * Use RESULT_POOL for allocations.
+ */
+static svn_error_t *
+init_rep_state(rep_state_t *rs,
+ svn_fs_fs__rep_header_t *rep_header,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *file,
+ svn_fs_fs__p2l_entry_t* entry,
+ apr_pool_t *result_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ shared_file_t *shared_file = apr_pcalloc(result_pool, sizeof(*shared_file));
+
+ /* this function does not apply to representation containers */
+ SVN_ERR_ASSERT(entry->type >= SVN_FS_FS__ITEM_TYPE_FILE_REP
+ && entry->type <= SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
+
+ shared_file->rfile = file;
+ shared_file->fs = fs;
+ shared_file->revision = entry->item.revision;
+ shared_file->pool = result_pool;
+
+ rs->sfile = shared_file;
+ rs->revision = entry->item.revision;
+ rs->item_index = entry->item.number;
+ rs->header_size = rep_header->header_size;
+ rs->start = entry->offset + rs->header_size;
+ rs->current = rep_header->type == svn_fs_fs__rep_plain ? 0 : 4;
+ rs->size = entry->size - rep_header->header_size - 7;
+ rs->ver = 1;
+ rs->chunk_index = 0;
+ rs->raw_window_cache = ffd->raw_window_cache;
+ rs->window_cache = ffd->txdelta_window_cache;
+ rs->combined_cache = ffd->combined_window_cache;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__partial_getter_func_t for txdelta windows.
+ * Instead of the whole window data, return only END_OFFSET member.
+ */
+static svn_error_t *
+get_txdelta_window_end(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ const svn_fs_fs__txdelta_cached_window_t *window
+ = (const svn_fs_fs__txdelta_cached_window_t *)data;
+ *(apr_off_t*)out = window->end_offset;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__partial_getter_func_t for raw windows.
+ * Instead of the whole window data, return only END_OFFSET member.
+ */
+static svn_error_t *
+get_raw_window_end(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ const svn_fs_fs__raw_cached_window_t *window
+ = (const svn_fs_fs__raw_cached_window_t *)data;
+ *(apr_off_t*)out = window->end_offset;
+
+ return SVN_NO_ERROR;
+}
+
+/* Walk through all windows in the representation addressed by RS in FS
+ * (excluding the delta bases) and put those not already cached into the
+ * window caches. If MAX_OFFSET is not -1, don't read windows that start
+ * at or beyond that offset. Use POOL for temporary allocations.
+ *
+ * This function requires RS->RAW_WINDOW_CACHE and RS->WINDOW_CACHE to
+ * be non-NULL.
+ */
+static svn_error_t *
+cache_windows(svn_fs_t *fs,
+ rep_state_t *rs,
+ apr_off_t max_offset,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ while (rs->current < rs->size)
+ {
+ apr_off_t end_offset;
+ svn_boolean_t found = FALSE;
+ window_cache_key_t key = { 0 };
+
+ svn_pool_clear(iterpool);
+
+ if (max_offset != -1 && rs->start + rs->current >= max_offset)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ /* We don't need to read the data again if it is already in cache.
+ * It might be cached as either raw or parsed window.
+ */
+ SVN_ERR(svn_cache__get_partial((void **) &end_offset, &found,
+ rs->raw_window_cache,
+ get_window_key(&key, rs),
+ get_raw_window_end, NULL,
+ iterpool));
+ if (! found)
+ SVN_ERR(svn_cache__get_partial((void **) &end_offset, &found,
+ rs->window_cache, &key,
+ get_txdelta_window_end, NULL,
+ iterpool));
+
+ if (found)
+ {
+ rs->current = end_offset;
+ }
+ else
+ {
+ /* Read, decode and cache the window. */
+ svn_fs_fs__raw_cached_window_t window;
+ apr_off_t start_offset = rs->start + rs->current;
+ apr_size_t window_len;
+ char *buf;
+
+ /* navigate to the current window */
+ SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, iterpool));
+ SVN_ERR(svn_txdelta__read_raw_window_len(&window_len,
+ rs->sfile->rfile->stream,
+ iterpool));
+
+ /* Read the raw window. */
+ buf = apr_palloc(iterpool, window_len + 1);
+ SVN_ERR(rs_aligned_seek(rs, NULL, start_offset, iterpool));
+ SVN_ERR(svn_io_file_read_full2(rs->sfile->rfile->file, buf,
+ window_len, NULL, NULL, iterpool));
+ buf[window_len] = 0;
+
+ /* update relative offset in representation */
+ rs->current += window_len;
+
+ /* Construct the cachable raw window object. */
+ window.end_offset = rs->current;
+ window.window.len = window_len;
+ window.window.data = buf;
+
+ /* cache the window now */
+ SVN_ERR(svn_cache__set(rs->raw_window_cache, &key, &window,
+ iterpool));
+ }
+
+ if (rs->current > rs->size)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read beyond "
+ "the end of the representation"));
+
+ rs->chunk_index++;
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Read all txdelta / plain windows following REP_HEADER in FS as described
+ * by ENTRY. Read the data from the already open FILE and the wrapping
+ * STREAM object. If MAX_OFFSET is not -1, don't read windows that start
+ * at or beyond that offset. Use SCRATCH_POOL for temporary allocations.
+ * If caching is not enabled, this is a no-op.
+ */
+static svn_error_t *
+block_read_windows(svn_fs_fs__rep_header_t *rep_header,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t* entry,
+ apr_off_t max_offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ rep_state_t rs = { 0 };
+ apr_off_t offset;
+ window_cache_key_t key = { 0 };
+
+ if ( (rep_header->type != svn_fs_fs__rep_plain
+ && (!ffd->txdelta_window_cache || !ffd->raw_window_cache))
+ || (rep_header->type == svn_fs_fs__rep_plain
+ && !ffd->combined_window_cache))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(init_rep_state(&rs, rep_header, fs, rev_file, entry,
+ result_pool));
+
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ offset = rs.start + rs.current;
+ if (rep_header->type == svn_fs_fs__rep_plain)
+ {
+ svn_stringbuf_t *plaintext;
+ svn_boolean_t is_cached;
+
+ /* already in cache? */
+ SVN_ERR(svn_cache__has_key(&is_cached, rs.combined_cache,
+ get_window_key(&key, &rs),
+ scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* for larger reps, the header may have crossed a block boundary.
+ * make sure we still read blocks properly aligned, i.e. don't use
+ * plain seek here. */
+ SVN_ERR(aligned_seek(fs, rev_file->file, NULL, offset, scratch_pool));
+
+ plaintext = svn_stringbuf_create_ensure(rs.size, result_pool);
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, plaintext->data,
+ rs.size, &plaintext->len, NULL,
+ result_pool));
+ plaintext->data[plaintext->len] = 0;
+ rs.current += rs.size;
+
+ SVN_ERR(set_cached_combined_window(plaintext, &rs, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(cache_windows(fs, &rs, max_offset, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to get the representation header identified by KEY from FS's cache.
+ * If it has not been cached, read it from the current position in STREAM
+ * and put it into the cache (if caching has been enabled for rep headers).
+ * Return the result in *REP_HEADER. Use POOL for allocations.
+ */
+static svn_error_t *
+read_rep_header(svn_fs_fs__rep_header_t **rep_header,
+ svn_fs_t *fs,
+ svn_stream_t *stream,
+ pair_cache_key_t *key,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t is_cached = FALSE;
+
+ if (ffd->rep_header_cache)
+ {
+ SVN_ERR(svn_cache__get((void**)rep_header, &is_cached,
+ ffd->rep_header_cache, key,
+ result_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_fs_fs__read_rep_header(rep_header, stream, result_pool,
+ scratch_pool));
+
+ if (ffd->rep_header_cache)
+ SVN_ERR(svn_cache__set(ffd->rep_header_cache, key, *rep_header,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the representation data (header, txdelta / plain windows)
+ * addressed by ENTRY->ITEM in FS and cache it if caches are enabled.
+ * Read the data from the already open FILE and the wrapping
+ * STREAM object. If MAX_OFFSET is not -1, don't read windows that start
+ * at or beyond that offset.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+block_read_contents(svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t* entry,
+ apr_off_t max_offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ pair_cache_key_t header_key = { 0 };
+ svn_fs_fs__rep_header_t *rep_header;
+
+ header_key.revision = (apr_int32_t)entry->item.revision;
+ header_key.second = entry->item.number;
+
+ SVN_ERR(read_rep_header(&rep_header, fs, rev_file->stream, &header_key,
+ result_pool, scratch_pool));
+ SVN_ERR(block_read_windows(rep_header, fs, rev_file, entry, max_offset,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* For the given REV_FILE in FS, in *STREAM return a stream covering the
+ * item specified by ENTRY. Also, verify the item's content by low-level
+ * checksum. Allocate the result in POOL.
+ */
+static svn_error_t *
+read_item(svn_stream_t **stream,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t* entry,
+ apr_pool_t *pool)
+{
+ apr_uint32_t digest;
+ svn_checksum_t *expected, *actual;
+ apr_uint32_t plain_digest;
+
+ /* Read item into string buffer. */
+ svn_stringbuf_t *text = svn_stringbuf_create_ensure(entry->size, pool);
+ text->len = entry->size;
+ text->data[text->len] = 0;
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, text->data, text->len,
+ NULL, NULL, pool));
+
+ /* Return (construct, calculate) stream and checksum. */
+ *stream = svn_stream_from_stringbuf(text, pool);
+ digest = svn__fnv1a_32x4(text->data, text->len);
+
+ /* Checksums will match most of the time. */
+ if (entry->fnv1_checksum == digest)
+ return SVN_NO_ERROR;
+
+ /* Construct proper checksum objects from their digests to allow for
+ * nice error messages. */
+ plain_digest = htonl(entry->fnv1_checksum);
+ expected = svn_checksum__from_digest_fnv1a_32x4(
+ (const unsigned char *)&plain_digest, pool);
+ plain_digest = htonl(digest);
+ actual = svn_checksum__from_digest_fnv1a_32x4(
+ (const unsigned char *)&plain_digest, pool);
+
+ /* Construct the full error message with all the info we have. */
+ return svn_checksum_mismatch_err(expected, actual, pool,
+ _("Low-level checksum mismatch while reading\n"
+ "%s bytes of meta data at offset %s "
+ "for item %s in revision %ld"),
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->size),
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->offset),
+ apr_psprintf(pool, "%" APR_UINT64_T_FMT, entry->item.number),
+ entry->item.revision);
+}
+
+/* If not already cached or if MUST_READ is set, read the changed paths
+ * list addressed by ENTRY in FS and retúrn it in *CHANGES. Cache the
+ * result if caching is enabled. Read the data from the already open
+ * FILE and wrapping FILE_STREAM. Use POOL for allocations.
+ */
+static svn_error_t *
+block_read_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ svn_boolean_t must_read,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *stream;
+ if (!must_read && !ffd->changes_cache)
+ return SVN_NO_ERROR;
+
+ /* already in cache? */
+ if (!must_read && ffd->changes_cache)
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_cache,
+ &entry->item.revision,
+ scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool));
+
+ /* read changes from revision file */
+ SVN_ERR(svn_fs_fs__read_changes(changes, stream, result_pool,
+ scratch_pool));
+
+ /* cache for future reference */
+ if (ffd->changes_cache)
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &entry->item.revision,
+ *changes, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* If not already cached or if MUST_READ is set, read the nod revision
+ * addressed by ENTRY in FS and retúrn it in *NODEREV_P. Cache the
+ * result if caching is enabled. Read the data from the already open
+ * FILE and wrapping FILE_STREAM. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+block_read_noderev(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ svn_boolean_t must_read,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *stream;
+
+ pair_cache_key_t key = { 0 };
+ key.revision = entry->item.revision;
+ key.second = entry->item.number;
+
+ if (!must_read && !ffd->node_revision_cache)
+ return SVN_NO_ERROR;
+
+ /* already in cache? */
+ if (!must_read && ffd->node_revision_cache)
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__has_key(&is_cached, ffd->node_revision_cache,
+ &key, scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool));
+
+ /* read node rev from revision file */
+ SVN_ERR(svn_fs_fs__read_noderev(noderev_p, stream,
+ result_pool, scratch_pool));
+
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ (*noderev_p)->is_fresh_txn_root = FALSE;
+
+ if (ffd->node_revision_cache)
+ SVN_ERR(svn_cache__set(ffd->node_revision_cache, &key, *noderev_p,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the whole (e.g. 64kB) block containing ITEM_INDEX of REVISION in FS
+ * and put all data into cache. If necessary and depending on heuristics,
+ * neighboring blocks may also get read. The data is being read from
+ * already open REVISION_FILE, which must be the correct rev / pack file
+ * w.r.t. REVISION.
+ *
+ * For noderevs and changed path lists, the item fetched can be allocated
+ * RESULT_POOL and returned in *RESULT. Otherwise, RESULT must be NULL.
+ */
+static svn_error_t *
+block_read(void **result,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_uint64_t item_index,
+ svn_fs_fs__revision_file_t *revision_file,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_off_t offset, wanted_offset = 0;
+ apr_off_t block_start = 0;
+ apr_array_header_t *entries;
+ int run_count = 0;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* Block read is an optional feature. If the caller does not want anything
+ * specific we may not have to read anything. */
+ if (!result)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* don't try this on transaction protorev files */
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+
+ /* index lookup: find the OFFSET of the item we *must* read plus (in the
+ * "do-while" block) the list of items in the same block. */
+ SVN_ERR(svn_fs_fs__item_offset(&wanted_offset, fs, revision_file,
+ revision, NULL, item_index, iterpool));
+
+ offset = wanted_offset;
+
+ /* Heuristics:
+ *
+ * Read this block. If the last item crosses the block boundary, read
+ * the next block but stop there. Because cross-boundary items cause
+ * blocks to be read twice, this heuristics will limit this effect to
+ * approx. 50% of blocks, probably less, while providing a sensible
+ * amount of read-ahead.
+ */
+ do
+ {
+ /* fetch list of items in the block surrounding OFFSET */
+ block_start = offset - (offset % ffd->block_size);
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, fs, revision_file,
+ revision, block_start,
+ ffd->block_size, scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(aligned_seek(fs, revision_file->file, &block_start, offset,
+ iterpool));
+
+ /* read all items from the block */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_boolean_t is_result, is_wanted;
+ apr_pool_t *pool;
+ svn_fs_fs__p2l_entry_t* entry;
+
+ svn_pool_clear(iterpool);
+
+ /* skip empty sections */
+ entry = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ continue;
+
+ /* the item / container we were looking for? */
+ is_wanted = entry->offset == wanted_offset
+ && entry->item.revision == revision
+ && entry->item.number == item_index;
+ is_result = result && is_wanted;
+
+ /* select the pool that we want the item to be allocated in */
+ pool = is_result ? result_pool : iterpool;
+
+ /* handle all items that start within this block and are relatively
+ * small (i.e. < block size). Always read the item we need to return.
+ */
+ if (is_result || ( entry->offset >= block_start
+ && entry->size < ffd->block_size))
+ {
+ void *item = NULL;
+ SVN_ERR(svn_io_file_seek(revision_file->file, APR_SET,
+ &entry->offset, iterpool));
+ switch (entry->type)
+ {
+ case SVN_FS_FS__ITEM_TYPE_FILE_REP:
+ case SVN_FS_FS__ITEM_TYPE_DIR_REP:
+ case SVN_FS_FS__ITEM_TYPE_FILE_PROPS:
+ case SVN_FS_FS__ITEM_TYPE_DIR_PROPS:
+ SVN_ERR(block_read_contents(fs, revision_file, entry,
+ is_wanted
+ ? -1
+ : block_start + ffd->block_size,
+ pool, iterpool));
+ break;
+
+ case SVN_FS_FS__ITEM_TYPE_NODEREV:
+ if (ffd->node_revision_cache || is_result)
+ SVN_ERR(block_read_noderev((node_revision_t **)&item,
+ fs, revision_file,
+ entry, is_result, pool,
+ iterpool));
+ break;
+
+ case SVN_FS_FS__ITEM_TYPE_CHANGES:
+ SVN_ERR(block_read_changes((apr_array_header_t **)&item,
+ fs, revision_file,
+ entry, is_result,
+ pool, iterpool));
+ break;
+
+ default:
+ break;
+ }
+
+ if (is_result)
+ *result = item;
+
+ /* if we crossed a block boundary, read the remainder of
+ * the last block as well */
+ offset = entry->offset + entry->size;
+ if (offset > block_start + ffd->block_size)
+ ++run_count;
+ }
+ }
+
+ }
+ while(run_count++ == 1); /* can only be true once and only if a block
+ * boundary got crossed */
+
+ /* if the caller requested a result, we must have provided one by now */
+ assert(!result || *result);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/cached_data.h b/subversion/libsvn_fs_fs/cached_data.h
new file mode 100644
index 0000000..07fa956
--- /dev/null
+++ b/subversion/libsvn_fs_fs/cached_data.h
@@ -0,0 +1,178 @@
+/* cached_data.h --- cached (read) access to FSFS data
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__CACHED_DATA_H
+#define SVN_LIBSVN_FS__CACHED_DATA_H
+
+#include "svn_pools.h"
+#include "svn_fs.h"
+
+#include "fs.h"
+
+
+
+/* Set *NODEREV_P to the node-revision for the node ID in FS. Do any
+ allocations in POOL. */
+svn_error_t *
+svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *ROOT_ID to the node-id for the root of revision REV in
+ filesystem FS. Do any allocations in POOL. */
+svn_error_t *
+svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Verify that representation REP in FS can be accessed. Successive calls
+ to this function should pass a non-NULL value to HINT. In that case,
+ many file open / close operations can be eliminated.
+ Do any allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_fs_fs__check_rep(representation_t *rep,
+ svn_fs_t *fs,
+ void **hint,
+ apr_pool_t *scratch_pool);
+
+/* Follow the representation delta chain in FS starting with REP. The
+ number of reps (including REP) in the chain will be returned in
+ *CHAIN_LENGTH. *SHARD_COUNT will be set to the number of shards
+ accessed. Do any allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_fs_fs__rep_chain_length(int *chain_length,
+ int *shard_count,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool);
+
+/* Set *CONTENTS to be a readable svn_stream_t that receives the text
+ representation REP as seen in filesystem FS. If CACHE_FULLTEXT is
+ not set, bypass fulltext cache lookup for this rep and don't put the
+ reconstructed fulltext into cache.
+ Use POOL for allocations. */
+svn_error_t *
+svn_fs_fs__get_contents(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ svn_boolean_t cache_fulltext,
+ apr_pool_t *pool);
+
+/* Set *CONTENTS_P to be a readable svn_stream_t that receives the text
+ representation REP as seen in filesystem FS. Read the latest element
+ of the delta chain from FILE at offset OFFSET.
+ Use POOL for allocations. */
+svn_error_t *
+svn_fs_fs__get_contents_from_file(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_file_t *file,
+ apr_off_t offset,
+ apr_pool_t *pool);
+
+/* Attempt to fetch the text representation of node-revision NODEREV as
+ seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
+ Set *SUCCESS only of the data could be provided and the processing
+ had been called.
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
+/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
+ the contents of the file TARGET, allocated in POOL.
+ If SOURCE is null, the empty string will be used. */
+svn_error_t *
+svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool);
+
+/* Set *ENTRIES to an apr_array_header_t of dirent structs that contain
+ the directory entries of node-revision NODEREV in filesystem FS. The
+ returned table is allocated in RESULT_POOL and entries are sorted
+ lexicographically. SCRATCH_POOL is used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return the directory entry from ENTRIES that matches NAME. If no such
+ entry exists, return NULL. If HINT is not NULL, set *HINT to the array
+ index of the entry returned. Successive calls in a linear scan scenario
+ will be faster called with the same HINT variable. */
+svn_fs_dirent_t *
+svn_fs_fs__find_dir_entry(apr_array_header_t *entries,
+ const char *name,
+ int *hint);
+
+/* Set *DIRENT to the entry identified by NAME in the directory given
+ by NODEREV in filesystem FS. If no such entry exits, *DIRENT will
+ be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL
+ used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *PROPLIST to be an apr_hash_t containing the property list of
+ node-revision NODEREV as seen in filesystem FS. Use POOL for
+ temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_proplist(apr_hash_t **proplist,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Set *HAS_PROPS to TRUE if NODEREV has properties in FS, otherwise
+ to FALSE. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__has_props(svn_boolean_t *has_props,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *scratch_pool);
+
+/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
+ * Allocate the result in POOL.
+ */
+svn_error_t *
+svn_fs_fs__get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c
index 42898cb..b54d69b 100644
--- a/subversion/libsvn_fs_fs/caching.c
+++ b/subversion/libsvn_fs_fs/caching.c
@@ -25,6 +25,7 @@
#include "id.h"
#include "dag.h"
#include "tree.h"
+#include "index.h"
#include "temp_serializer.h"
#include "../libsvn_fs/fs-loader.h"
@@ -65,32 +66,18 @@ normalize_key_part(const char *original,
return normalized->data;
}
-/* Return a memcache in *MEMCACHE_P for FS if it's configured to use
- memcached, or NULL otherwise. Also, sets *FAIL_STOP to a boolean
- indicating whether cache errors should be returned to the caller or
- just passed to the FS warning handler.
-
- *CACHE_TXDELTAS, *CACHE_FULLTEXTS and *CACHE_REVPROPS flags will be set
- according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix
- to use.
+/* *CACHE_TXDELTAS, *CACHE_FULLTEXTS flags will be set according to
+ FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to use.
Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
for temporary allocations. */
static svn_error_t *
-read_config(svn_memcache_t **memcache_p,
- svn_boolean_t *fail_stop,
- const char **cache_namespace,
+read_config(const char **cache_namespace,
svn_boolean_t *cache_txdeltas,
svn_boolean_t *cache_fulltexts,
- svn_boolean_t *cache_revprops,
svn_fs_t *fs,
apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- SVN_ERR(svn_cache__make_memcache_from_config(memcache_p, ffd->config,
- fs->pool));
-
/* No cache namespace by default. I.e. all FS instances share the
* cached data. If you specify different namespaces, the data will
* share / compete for the same cache memory but keys will not match
@@ -116,7 +103,8 @@ read_config(svn_memcache_t **memcache_p,
*cache_txdeltas
= svn_hash__get_bool(fs->config,
SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
- FALSE);
+ TRUE);
+
/* by default, cache fulltexts.
* Most SVN tools care about reconstructed file content.
* Thus, this is a reasonable default.
@@ -129,13 +117,7 @@ read_config(svn_memcache_t **memcache_p,
SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
TRUE);
- /* For now, always disable revprop caching.
- */
- *cache_revprops = FALSE;
-
- return svn_config_get_bool(ffd->config, fail_stop,
- CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
- FALSE);
+ return SVN_NO_ERROR;
}
@@ -196,9 +178,10 @@ dump_cache_statistics(void *baton_void)
TRUE,
baton->pool);
- if (! err)
+ /* skip unused caches */
+ if (! err && (info.gets > 0 || info.sets > 0))
{
- text_stats = svn_cache__format_info(&info, baton->pool);
+ text_stats = svn_cache__format_info(&info, TRUE, baton->pool);
lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
for (i = 0; i < lines->nelts; ++i)
@@ -219,6 +202,29 @@ dump_cache_statistics(void *baton_void)
return result;
}
+
+static apr_status_t
+dump_global_cache_statistics(void *baton_void)
+{
+ apr_pool_t *pool = baton_void;
+
+ svn_cache__info_t *info = svn_cache__membuffer_get_global_info(pool);
+ svn_string_t *text_stats = svn_cache__format_info(info, FALSE, pool);
+ apr_array_header_t *lines = svn_cstring_split(text_stats->data, "\n",
+ FALSE, pool);
+
+ int i;
+ for (i = 0; i < lines->nelts; ++i)
+ {
+ const char *line = APR_ARRAY_IDX(lines, i, const char *);
+#ifdef SVN_DEBUG
+ SVN_DBG(("%s\n", line));
+#endif
+ }
+
+ return APR_SUCCESS;
+}
+
#endif /* SVN_DEBUG_CACHE_DUMP_STATS */
/* This function sets / registers the required callbacks for a given
@@ -267,12 +273,13 @@ init_callbacks(svn_cache__t *cache,
* Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
* MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
* MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
- * otherwise.
+ * otherwise. Use the given PRIORITY class for the new cache. If it
+ * is 0, then use the default priority class.
*
* Unless NO_HANDLER is true, register an error handler that reports errors
* as warnings to the FS warning callback.
*
- * Cache is allocated in POOL.
+ * Cache is allocated in RESULT_POOL, temporaries in SCRATCH_POOL.
* */
static svn_error_t *
create_cache(svn_cache__t **cache_p,
@@ -284,19 +291,23 @@ create_cache(svn_cache__t **cache_p,
svn_cache__deserialize_func_t deserializer,
apr_ssize_t klen,
const char *prefix,
+ apr_uint32_t priority,
svn_fs_t *fs,
svn_boolean_t no_handler,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_cache__error_handler_t error_handler = no_handler
? NULL
: warn_and_fail_on_cache_errors;
+ if (priority == 0)
+ priority = SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY;
if (memcache)
{
SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
serializer, deserializer, klen,
- prefix, pool));
+ prefix, result_pool));
error_handler = no_handler
? NULL
: warn_and_continue_on_cache_errors;
@@ -305,20 +316,20 @@ create_cache(svn_cache__t **cache_p,
{
SVN_ERR(svn_cache__create_membuffer_cache(
cache_p, membuffer, serializer, deserializer,
- klen, prefix, FALSE, pool));
+ klen, prefix, priority, FALSE, result_pool, scratch_pool));
}
else if (pages)
{
SVN_ERR(svn_cache__create_inprocess(
cache_p, serializer, deserializer, klen, pages,
- items_per_page, FALSE, prefix, pool));
+ items_per_page, FALSE, prefix, result_pool));
}
else
{
*cache_p = NULL;
}
- SVN_ERR(init_callbacks(*cache_p, fs, error_handler, pool));
+ SVN_ERR(init_callbacks(*cache_p, fs, error_handler, result_pool));
return SVN_NO_ERROR;
}
@@ -332,29 +343,45 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
"fsfs:", fs->uuid,
"/", normalize_key_part(fs->path, pool),
":",
- (char *)NULL);
- svn_memcache_t *memcache;
+ SVN_VA_NULL);
svn_membuffer_t *membuffer;
- svn_boolean_t no_handler;
+ svn_boolean_t no_handler = ffd->fail_stop;
svn_boolean_t cache_txdeltas;
svn_boolean_t cache_fulltexts;
- svn_boolean_t cache_revprops;
const char *cache_namespace;
/* Evaluating the cache configuration. */
- SVN_ERR(read_config(&memcache,
- &no_handler,
- &cache_namespace,
+ SVN_ERR(read_config(&cache_namespace,
&cache_txdeltas,
&cache_fulltexts,
- &cache_revprops,
fs,
pool));
- prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, NULL);
+ prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL);
membuffer = svn_cache__get_global_membuffer_cache();
+ /* General rules for assigning cache priorities:
+ *
+ * - Data that can be reconstructed from other elements has low prio
+ * (e.g. fulltexts, directories etc.)
+ * - Index data required to find any of the other data has high prio
+ * (e.g. noderevs, L2P and P2L index pages)
+ * - everthing else should use default prio
+ */
+
+#ifdef SVN_DEBUG_CACHE_DUMP_STATS
+
+ /* schedule printing the global access statistics upon pool cleanup,
+ * i.e. when the repo instance gets closed / cleaned up.
+ */
+ if (membuffer)
+ apr_pool_cleanup_register(fs->pool,
+ fs->pool,
+ dump_global_cache_statistics,
+ apr_pool_cleanup_null);
+#endif
+
/* Make the cache for revision roots. For the vast majority of
* commands, this is only going to contain a few entries (svnadmin
* dump/verify is an exception here), so to reduce overhead let's
@@ -370,10 +397,11 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_fs_fs__serialize_id,
svn_fs_fs__deserialize_id,
sizeof(svn_revnum_t),
- apr_pstrcat(pool, prefix, "RRI", (char *)NULL),
+ apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL),
+ 0,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* Rough estimate: revision DAG nodes have size around 320 bytes, so
* let's put 16 on a page. */
@@ -384,13 +412,14 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_fs_fs__dag_serialize,
svn_fs_fs__dag_deserialize,
APR_HASH_KEY_STRING,
- apr_pstrcat(pool, prefix, "DAG", (char *)NULL),
+ apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* 1st level DAG node cache */
- ffd->dag_node_cache = svn_fs_fs__create_dag_cache(pool);
+ ffd->dag_node_cache = svn_fs_fs__create_dag_cache(fs->pool);
/* Very rough estimate: 1K per directory. */
SVN_ERR(create_cache(&(ffd->dir_cache),
@@ -399,11 +428,12 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
1024, 8,
svn_fs_fs__serialize_dir_entries,
svn_fs_fs__deserialize_dir_entries,
- APR_HASH_KEY_STRING,
- apr_pstrcat(pool, prefix, "DIR", (char *)NULL),
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* Only 16 bytes per entry (a revision number + the corresponding offset).
Since we want ~8k pages, that means 512 entries per page. */
@@ -415,51 +445,69 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_fs_fs__deserialize_manifest,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "PACK-MANIFEST",
- (char *)NULL),
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* initialize node revision cache, if caching has been enabled */
SVN_ERR(create_cache(&(ffd->node_revision_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 32, 32, /* ~200 byte / entry; 1k entries total */
svn_fs_fs__serialize_node_revision,
svn_fs_fs__deserialize_node_revision,
sizeof(pair_cache_key_t),
- apr_pstrcat(pool, prefix, "NODEREVS", (char *)NULL),
+ apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ fs,
+ no_handler,
+ fs->pool, pool));
+
+ /* initialize representation header cache, if caching has been enabled */
+ SVN_ERR(create_cache(&(ffd->rep_header_cache),
+ NULL,
+ membuffer,
+ 1, 1000, /* ~8 bytes / entry; 1k entries total */
+ svn_fs_fs__serialize_rep_header,
+ svn_fs_fs__deserialize_rep_header,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* initialize node change list cache, if caching has been enabled */
SVN_ERR(create_cache(&(ffd->changes_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 1, 8, /* 1k / entry; 8 entries total, rarely used */
svn_fs_fs__serialize_changes,
svn_fs_fs__deserialize_changes,
sizeof(svn_revnum_t),
- apr_pstrcat(pool, prefix, "CHANGES", (char *)NULL),
+ apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL),
+ 0,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
/* if enabled, cache fulltext and other derived information */
if (cache_fulltexts)
{
SVN_ERR(create_cache(&(ffd->fulltext_cache),
- memcache,
+ ffd->memcache,
membuffer,
0, 0, /* Do not use inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
sizeof(pair_cache_key_t),
- apr_pstrcat(pool, prefix, "TEXT", (char *)NULL),
+ apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
SVN_ERR(create_cache(&(ffd->properties_cache),
NULL,
@@ -469,10 +517,11 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_fs_fs__deserialize_properties,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "PROP",
- (char *)NULL),
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
NULL,
@@ -482,10 +531,11 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_fs_fs__deserialize_mergeinfo,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "MERGEINFO",
- (char *)NULL),
+ SVN_VA_NULL),
+ 0,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
NULL,
@@ -495,10 +545,11 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
NULL, NULL,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
- (char *)NULL),
+ SVN_VA_NULL),
+ 0,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
}
else
{
@@ -508,42 +559,36 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
ffd->mergeinfo_existence_cache = NULL;
}
- /* initialize revprop cache, if full-text caching has been enabled */
- if (cache_revprops)
+ /* if enabled, cache text deltas and their combinations */
+ if (cache_txdeltas)
{
- SVN_ERR(create_cache(&(ffd->revprop_cache),
+ SVN_ERR(create_cache(&(ffd->raw_window_cache),
NULL,
membuffer,
0, 0, /* Do not use inprocess cache */
- svn_fs_fs__serialize_properties,
- svn_fs_fs__deserialize_properties,
- sizeof(pair_cache_key_t),
- apr_pstrcat(pool, prefix, "REVPROP",
- (char *)NULL),
+ svn_fs_fs__serialize_raw_window,
+ svn_fs_fs__deserialize_raw_window,
+ sizeof(window_cache_key_t),
+ apr_pstrcat(pool, prefix, "RAW_WINDOW",
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
fs,
no_handler,
- fs->pool));
- }
- else
- {
- ffd->revprop_cache = NULL;
- }
+ fs->pool, pool));
- /* if enabled, cache text deltas and their combinations */
- if (cache_txdeltas)
- {
SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
NULL,
membuffer,
0, 0, /* Do not use inprocess cache */
svn_fs_fs__serialize_txdelta_window,
svn_fs_fs__deserialize_txdelta_window,
- APR_HASH_KEY_STRING,
+ sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
- (char *)NULL),
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
SVN_ERR(create_cache(&(ffd->combined_window_cache),
NULL,
@@ -551,12 +596,13 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
0, 0, /* Do not use inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
- APR_HASH_KEY_STRING,
+ sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
- (char *)NULL),
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
fs,
no_handler,
- fs->pool));
+ fs->pool, pool));
}
else
{
@@ -564,6 +610,61 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
ffd->combined_window_cache = NULL;
}
+ SVN_ERR(create_cache(&(ffd->l2p_header_cache),
+ NULL,
+ membuffer,
+ 64, 16, /* entry size varies but we must cover
+ a reasonable number of revisions (1k) */
+ svn_fs_fs__serialize_l2p_header,
+ svn_fs_fs__deserialize_l2p_header,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "L2P_HEADER",
+ (char *)NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ fs,
+ no_handler,
+ fs->pool, pool));
+ SVN_ERR(create_cache(&(ffd->l2p_page_cache),
+ NULL,
+ membuffer,
+ 64, 16, /* entry size varies but we must cover
+ a reasonable number of revisions (1k) */
+ svn_fs_fs__serialize_l2p_page,
+ svn_fs_fs__deserialize_l2p_page,
+ sizeof(svn_fs_fs__page_cache_key_t),
+ apr_pstrcat(pool, prefix, "L2P_PAGE",
+ (char *)NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ fs,
+ no_handler,
+ fs->pool, pool));
+ SVN_ERR(create_cache(&(ffd->p2l_header_cache),
+ NULL,
+ membuffer,
+ 4, 1, /* Large entries. Rarely used. */
+ svn_fs_fs__serialize_p2l_header,
+ svn_fs_fs__deserialize_p2l_header,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "P2L_HEADER",
+ (char *)NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ fs,
+ no_handler,
+ fs->pool, pool));
+ SVN_ERR(create_cache(&(ffd->p2l_page_cache),
+ NULL,
+ membuffer,
+ 4, 16, /* Variably sized entries. Rarely used. */
+ svn_fs_fs__serialize_p2l_page,
+ svn_fs_fs__deserialize_p2l_page,
+ sizeof(svn_fs_fs__page_cache_key_t),
+ apr_pstrcat(pool, prefix, "P2L_PAGE",
+ (char *)NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ fs,
+ no_handler,
+ fs->pool, pool));
+
return SVN_NO_ERROR;
}
@@ -575,12 +676,44 @@ struct txn_cleanup_baton_t
/* the position where to reset it */
svn_cache__t **to_reset;
+
+ /* pool that TXN_CACHE was allocated in */
+ apr_pool_t *txn_pool;
+
+ /* pool that the FS containing the TO_RESET pointer was allocator */
+ apr_pool_t *fs_pool;
};
+/* Forward declaration. */
+static apr_status_t
+remove_txn_cache_fs(void *baton_void);
+
+/* APR pool cleanup handler that will reset the cache pointer given in
+ BATON_VOID when the TXN_POOL gets cleaned up. */
+static apr_status_t
+remove_txn_cache_txn(void *baton_void)
+{
+ struct txn_cleanup_baton_t *baton = baton_void;
+
+ /* be careful not to hurt performance by resetting newer txn's caches. */
+ if (*baton->to_reset == baton->txn_cache)
+ {
+ /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
+ *baton->to_reset = NULL;
+ }
+
+ /* It's cleaned up now. Prevent double cleanup. */
+ apr_pool_cleanup_kill(baton->fs_pool,
+ baton,
+ remove_txn_cache_fs);
+
+ return APR_SUCCESS;
+}
+
/* APR pool cleanup handler that will reset the cache pointer given in
- BATON_VOID. */
+ BATON_VOID when the FS_POOL gets cleaned up. */
static apr_status_t
-remove_txn_cache(void *baton_void)
+remove_txn_cache_fs(void *baton_void)
{
struct txn_cleanup_baton_t *baton = baton_void;
@@ -588,19 +721,25 @@ remove_txn_cache(void *baton_void)
if (*baton->to_reset == baton->txn_cache)
{
/* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
- *baton->to_reset = NULL;
+ *baton->to_reset = NULL;
}
+ /* It's cleaned up now. Prevent double cleanup. */
+ apr_pool_cleanup_kill(baton->txn_pool,
+ baton,
+ remove_txn_cache_txn);
+
return APR_SUCCESS;
}
/* This function sets / registers the required callbacks for a given
- * transaction-specific *CACHE object, if CACHE is not NULL and a no-op
- * otherwise. In particular, it will ensure that *CACHE gets reset to NULL
- * upon POOL destruction latest.
+ * transaction-specific *CACHE object in FS, if CACHE is not NULL and
+ * a no-op otherwise. In particular, it will ensure that *CACHE gets
+ * reset to NULL upon POOL or FS->POOL destruction latest.
*/
static void
-init_txn_callbacks(svn_cache__t **cache,
+init_txn_callbacks(svn_fs_t *fs,
+ svn_cache__t **cache,
apr_pool_t *pool)
{
if (*cache != NULL)
@@ -610,10 +749,20 @@ init_txn_callbacks(svn_cache__t **cache,
baton = apr_palloc(pool, sizeof(*baton));
baton->txn_cache = *cache;
baton->to_reset = cache;
+ baton->txn_pool = pool;
+ baton->fs_pool = fs->pool;
+ /* If any of these pools gets cleaned, we must reset the cache.
+ * We don't know which one will get cleaned up first, so register
+ * cleanup actions for both and during the cleanup action, unregister
+ * the respective other action. */
apr_pool_cleanup_register(pool,
baton,
- remove_txn_cache,
+ remove_txn_cache_txn,
+ apr_pool_cleanup_null);
+ apr_pool_cleanup_register(fs->pool,
+ baton,
+ remove_txn_cache_fs,
apr_pool_cleanup_null);
}
}
@@ -635,7 +784,7 @@ svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
"/", fs->path,
":", txn_id,
":", svn_uuid_generate(pool), ":",
- (char *)NULL);
+ SVN_VA_NULL);
/* We don't support caching for concurrent transactions in the SAME
* FSFS session. Maybe, you forgot to clean POOL. */
@@ -656,13 +805,14 @@ svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
svn_fs_fs__deserialize_dir_entries,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "TXNDIR",
- (char *)NULL),
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
fs,
TRUE,
- pool));
+ pool, pool));
/* reset the transaction-specific cache if the pool gets cleaned up. */
- init_txn_callbacks(&(ffd->txn_dir_cache), pool);
+ init_txn_callbacks(fs, &(ffd->txn_dir_cache), pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c
index 3c51ffd..d21c17c 100644
--- a/subversion/libsvn_fs_fs/dag.c
+++ b/subversion/libsvn_fs_fs/dag.c
@@ -28,11 +28,12 @@
#include "svn_props.h"
#include "svn_pools.h"
+#include "cached_data.h"
#include "dag.h"
#include "fs.h"
-#include "key-gen.h"
#include "fs_fs.h"
#include "id.h"
+#include "transaction.h"
#include "../libsvn_fs/fs-loader.h"
@@ -159,10 +160,13 @@ get_node_revision(node_revision_t **noderev_p,
if (! node->node_revision)
{
node_revision_t *noderev;
+ apr_pool_t *scratch_pool = svn_pool_create(node->node_pool);
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, node->fs,
- node->id, node->node_pool));
+ node->id, node->node_pool,
+ scratch_pool));
node->node_revision = noderev;
+ svn_pool_destroy(scratch_pool);
}
/* Now NODE->node_revision is set. */
@@ -173,7 +177,7 @@ get_node_revision(node_revision_t **noderev_p,
svn_boolean_t svn_fs_fs__dag_check_mutable(const dag_node_t *node)
{
- return (svn_fs_fs__id_txn_id(svn_fs_fs__dag_get_id(node)) != NULL);
+ return svn_fs_fs__id_is_txn(svn_fs_fs__dag_get_id(node));
}
@@ -312,8 +316,9 @@ dir_entry_id_from_node(const svn_fs_id_t **id_p,
{
svn_fs_dirent_t *dirent;
- SVN_ERR(svn_fs_fs__dag_dir_entry(&dirent, parent, name, scratch_pool));
- *id_p = dirent ? svn_fs_fs__id_copy(dirent->id, result_pool) : NULL;
+ SVN_ERR(svn_fs_fs__dag_dir_entry(&dirent, parent, name, result_pool,
+ scratch_pool));
+ *id_p = dirent ? dirent->id : NULL;
return SVN_NO_ERROR;
}
@@ -332,7 +337,7 @@ set_entry(dag_node_t *parent,
const char *name,
const svn_fs_id_t *id,
svn_node_kind_t kind,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
node_revision_t *parent_noderev;
@@ -359,7 +364,7 @@ make_entry(dag_node_t **child_p,
const char *parent_path,
const char *name,
svn_boolean_t is_dir,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *new_node_id;
@@ -413,7 +418,7 @@ make_entry(dag_node_t **child_p,
svn_error_t *
-svn_fs_fs__dag_dir_entries(apr_hash_t **entries,
+svn_fs_fs__dag_dir_entries(apr_array_header_t **entries,
dag_node_t *node,
apr_pool_t *pool)
{
@@ -425,14 +430,15 @@ svn_fs_fs__dag_dir_entries(apr_hash_t **entries,
return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
_("Can't get entries of non-directory"));
- return svn_fs_fs__rep_contents_dir(entries, node->fs, noderev, pool);
+ return svn_fs_fs__rep_contents_dir(entries, node->fs, noderev, pool, pool);
}
svn_error_t *
svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
dag_node_t *node,
const char* name,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
node_revision_t *noderev;
SVN_ERR(get_node_revision(&noderev, node));
@@ -442,8 +448,8 @@ svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
_("Can't get entries of non-directory"));
/* Get a dirent hash for this directory. */
- return svn_fs_fs__rep_contents_dir_entry(dirent, node->fs,
- noderev, name, pool, pool);
+ return svn_fs_fs__rep_contents_dir_entry(dirent, node->fs, noderev, name,
+ result_pool, scratch_pool);
}
@@ -452,7 +458,7 @@ svn_fs_fs__dag_set_entry(dag_node_t *node,
const char *entry_name,
const svn_fs_id_t *id,
svn_node_kind_t kind,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
/* Check it's a directory. */
@@ -492,6 +498,42 @@ svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_fs_fs__dag_has_props(svn_boolean_t *has_props,
+ dag_node_t *node,
+ apr_pool_t *scratch_pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ if (! noderev->prop_rep)
+ {
+ *has_props = FALSE; /* Easy out */
+ return SVN_NO_ERROR;
+ }
+
+ if (svn_fs_fs__id_txn_used(&noderev->prop_rep->txn_id))
+ {
+ /* We are in a commit or something. Check actual properties */
+ apr_hash_t *proplist;
+
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist, node->fs,
+ noderev, scratch_pool));
+
+ *has_props = proplist ? (0 < apr_hash_count(proplist)) : FALSE;
+ }
+ else
+ {
+ /* Properties are stored as a standard hash stream,
+ always ending with "END\n" (4 bytes) */
+ *has_props = (noderev->prop_rep->expanded_size > 4
+ || (noderev->prop_rep->expanded_size == 0
+ && noderev->prop_rep->size > 4));
+ }
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_fs_fs__dag_set_proplist(dag_node_t *node,
@@ -606,17 +648,31 @@ svn_fs_fs__dag_revision_root(dag_node_t **node_p,
svn_revnum_t rev,
apr_pool_t *pool)
{
- svn_fs_id_t *root_id;
+ dag_node_t *new_node;
- SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
- return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
+ /* Construct the node. */
+ new_node = apr_pcalloc(pool, sizeof(*new_node));
+ new_node->fs = fs;
+ SVN_ERR(svn_fs_fs__rev_get_root(&new_node->id, fs, rev, pool, pool));
+
+ /* Grab the contents so we can inspect the node's kind and created path. */
+ new_node->node_pool = pool;
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = svn_node_dir;
+ new_node->created_path = "/";
+ new_node->fresh_root_predecessor_id = NULL;
+
+ /* Return a fresh new node */
+ *node_p = new_node;
+ return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__dag_txn_root(dag_node_t **node_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *root_id, *ignored;
@@ -629,7 +685,7 @@ svn_fs_fs__dag_txn_root(dag_node_t **node_p,
svn_error_t *
svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *base_root_id, *ignored;
@@ -644,8 +700,8 @@ svn_fs_fs__dag_clone_child(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *copy_id,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
svn_boolean_t is_parent_copyroot,
apr_pool_t *pool)
{
@@ -668,6 +724,10 @@ svn_fs_fs__dag_clone_child(dag_node_t **child_p,
/* Find the node named NAME in PARENT's entries list if it exists. */
SVN_ERR(svn_fs_fs__dag_open(&cur_entry, parent, name, pool, subpool));
+ if (! cur_entry)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ "Attempted to open non-existent child node '%s'", name);
/* Check for mutability in the node we found. If it's mutable, we
don't need to clone it. */
@@ -718,7 +778,7 @@ svn_fs_fs__dag_clone_child(dag_node_t **child_p,
svn_error_t *
svn_fs_fs__dag_clone_root(dag_node_t **root_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *base_root_id, *root_id;
@@ -745,7 +805,7 @@ svn_fs_fs__dag_clone_root(dag_node_t **root_p,
svn_error_t *
svn_fs_fs__dag_delete(dag_node_t *parent,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
node_revision_t *parent_noderev;
@@ -840,23 +900,22 @@ svn_fs_fs__dag_delete_if_mutable(svn_fs_t *fs,
/* Else it's mutable. Recurse on directories... */
if (node->kind == svn_node_dir)
{
- apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *entries;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
- /* Loop over hash entries */
+ /* Loop over directory entries */
SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
if (entries)
- {
- for (hi = apr_hash_first(pool, entries);
- hi;
- hi = apr_hash_next(hi))
- {
- svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
-
- SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs, dirent->id,
- pool));
- }
- }
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs,
+ APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *)->id,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
}
/* ... then delete the node itself, after deleting any mutable
@@ -869,7 +928,7 @@ svn_fs_fs__dag_make_file(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
/* Call our little helper function */
@@ -882,7 +941,7 @@ svn_fs_fs__dag_make_dir(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
/* Call our little helper function */
@@ -909,7 +968,7 @@ svn_fs_fs__dag_get_contents(svn_stream_t **contents_p,
/* Get a stream to the contents. */
SVN_ERR(svn_fs_fs__get_contents(&contents, file->fs,
- noderev, pool));
+ noderev->data_rep, TRUE, pool));
*contents_p = contents;
@@ -1107,7 +1166,7 @@ svn_fs_fs__dag_serialize(void **data,
/* The deserializer will use its own pool. */
svn_temp_serializer__set_null(context,
- (const void * const *)&node->node_pool);
+ (const void * const *)&node->node_pool);
/* serialize other sub-structures */
svn_fs_fs__id_serialize(context, (const svn_fs_id_t **)&node->id);
@@ -1164,9 +1223,10 @@ svn_fs_fs__dag_open(dag_node_t **child_p,
SVN_ERR(dir_entry_id_from_node(&node_id, parent, name,
scratch_pool, scratch_pool));
if (! node_id)
- return svn_error_createf
- (SVN_ERR_FS_NOT_FOUND, NULL,
- "Attempted to open non-existent child node '%s'", name);
+ {
+ *child_p = NULL;
+ return SVN_NO_ERROR;
+ }
/* Make sure that NAME is a single path component. */
if (! svn_path_is_single_path_component(name))
@@ -1187,7 +1247,7 @@ svn_fs_fs__dag_copy(dag_node_t *to_node,
svn_boolean_t preserve_history,
svn_revnum_t from_rev,
const char *from_path,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *id;
@@ -1195,7 +1255,7 @@ svn_fs_fs__dag_copy(dag_node_t *to_node,
if (preserve_history)
{
node_revision_t *from_noderev, *to_noderev;
- const char *copy_id;
+ svn_fs_fs__id_part_t copy_id;
const svn_fs_id_t *src_id = svn_fs_fs__dag_get_id(from_node);
svn_fs_t *fs = svn_fs_fs__dag_get_fs(from_node);
@@ -1221,7 +1281,7 @@ svn_fs_fs__dag_copy(dag_node_t *to_node,
to_noderev->copyroot_path = NULL;
SVN_ERR(svn_fs_fs__create_successor(&id, fs, src_id, to_noderev,
- copy_id, txn_id, pool));
+ &copy_id, txn_id, pool));
}
else /* don't preserve history */
@@ -1242,7 +1302,9 @@ svn_error_t *
svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
svn_boolean_t *contents_changed,
dag_node_t *node1,
- dag_node_t *node2)
+ dag_node_t *node2,
+ svn_boolean_t strict,
+ apr_pool_t *pool)
{
node_revision_t *noderev1, *noderev2;
@@ -1255,16 +1317,49 @@ svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
SVN_ERR(get_node_revision(&noderev1, node1));
SVN_ERR(get_node_revision(&noderev2, node2));
- /* Compare property keys. */
- if (props_changed != NULL)
- *props_changed = (! svn_fs_fs__noderev_same_rep_key(noderev1->prop_rep,
- noderev2->prop_rep));
+ if (strict)
+ {
+ /* In strict mode, compare text and property representations in the
+ svn_fs_contents_different() / svn_fs_props_different() manner.
+
+ See the "No-op changes no longer dumped by 'svnadmin dump' in 1.9"
+ discussion (http://svn.haxx.se/dev/archive-2015-09/0269.shtml) and
+ issue #4598 (https://issues.apache.org/jira/browse/SVN-4598). */
+ svn_fs_t *fs = svn_fs_fs__dag_get_fs(node1);
+ svn_boolean_t same;
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ {
+ SVN_ERR(svn_fs_fs__prop_rep_equal(&same, fs, noderev1,
+ noderev2, pool));
+ *props_changed = !same;
+ }
- /* Compare contents keys. */
- if (contents_changed != NULL)
- *contents_changed =
- (! svn_fs_fs__noderev_same_rep_key(noderev1->data_rep,
- noderev2->data_rep));
+ /* Compare contents keys. */
+ if (contents_changed != NULL)
+ {
+ SVN_ERR(svn_fs_fs__file_text_rep_equal(&same, fs, noderev1,
+ noderev2, pool));
+ *contents_changed = !same;
+ }
+ }
+ else
+ {
+ /* Otherwise, compare representation keys -- as in Subversion 1.8. */
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ *props_changed =
+ !svn_fs_fs__noderev_same_rep_key(noderev1->prop_rep,
+ noderev2->prop_rep);
+
+ /* Compare contents keys. */
+ if (contents_changed != NULL)
+ *contents_changed =
+ !svn_fs_fs__noderev_same_rep_key(noderev1->data_rep,
+ noderev2->data_rep);
+ }
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/dag.h b/subversion/libsvn_fs_fs/dag.h
index 867b025..edab6d5 100644
--- a/subversion/libsvn_fs_fs/dag.h
+++ b/subversion/libsvn_fs_fs/dag.h
@@ -27,6 +27,8 @@
#include "svn_delta.h"
#include "private/svn_cache.h"
+#include "id.h"
+
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
@@ -175,6 +177,12 @@ svn_error_t *svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
dag_node_t *node,
apr_pool_t *pool);
+/* Set *HAS_PROPS to TRUE if NODE has properties. Use SCRATCH_POOL
+ for temporary allocations */
+svn_error_t *svn_fs_fs__dag_has_props(svn_boolean_t *has_props,
+ dag_node_t *node,
+ apr_pool_t *scratch_pool);
+
/* Set the property list of NODE to PROPLIST, allocating from POOL.
The node being changed must be mutable.
@@ -224,7 +232,7 @@ svn_error_t *svn_fs_fs__dag_revision_root(dag_node_t **node_p,
for a transaction, call svn_fs_fs__dag_clone_root. */
svn_error_t *svn_fs_fs__dag_txn_root(dag_node_t **node_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -232,7 +240,7 @@ svn_error_t *svn_fs_fs__dag_txn_root(dag_node_t **node_p,
allocating from POOL. Allocate the node in TRAIL->pool. */
svn_error_t *svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -242,7 +250,7 @@ svn_error_t *svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
root directory clone. Allocate *ROOT_P in POOL. */
svn_error_t *svn_fs_fs__dag_clone_root(dag_node_t **root_p,
svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -252,7 +260,8 @@ svn_error_t *svn_fs_fs__dag_clone_root(dag_node_t **root_p,
/* Open the node named NAME in the directory PARENT. Set *CHILD_P to
the new node, allocated in RESULT_POOL. NAME must be a single path
- component; it cannot be a slash-separated directory path.
+ component; it cannot be a slash-separated directory path. If NAME does
+ not exist within PARENT, set *CHILD_P to NULL.
*/
svn_error_t *
svn_fs_fs__dag_open(dag_node_t **child_p,
@@ -262,24 +271,25 @@ svn_fs_fs__dag_open(dag_node_t **child_p,
apr_pool_t *scratch_pool);
-/* Set *ENTRIES_P to a hash table of NODE's entries. The keys of the
- table are entry names, and the values are svn_fs_dirent_t's. The
- returned table (and its keys and values) is allocated in POOL,
- which is also used for temporary allocations. */
-svn_error_t *svn_fs_fs__dag_dir_entries(apr_hash_t **entries_p,
+/* Set *ENTRIES_P to an array of NODE's entries, sorted by entry names,
+ and the values are svn_fs_dirent_t's. The returned table (and elements)
+ is allocated in POOL, which is also used for temporary allocations. */
+svn_error_t *svn_fs_fs__dag_dir_entries(apr_array_header_t **entries_p,
dag_node_t *node,
apr_pool_t *pool);
/* Fetches the NODE's entries and returns a copy of the entry selected
by the key value given in NAME and set *DIRENT to a copy of that
- entry. If such entry was found, the copy will be allocated in POOL.
+ entry. If such entry was found, the copy will be allocated in
+ RESULT_POOL. Temporary data will be used in SCRATCH_POOL.
Otherwise, the *DIRENT will be set to NULL.
*/
/* ### This function is currently only called from dag.c. */
svn_error_t * svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
dag_node_t *node,
const char* name,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* Set ENTRY_NAME in NODE to point to ID (with kind KIND), allocating
from POOL. NODE must be a mutable directory. ID can refer to a
@@ -294,7 +304,7 @@ svn_error_t *svn_fs_fs__dag_set_entry(dag_node_t *node,
const char *entry_name,
const svn_fs_id_t *id,
svn_node_kind_t kind,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -321,8 +331,8 @@ svn_error_t *svn_fs_fs__dag_clone_child(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *copy_id,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
svn_boolean_t is_parent_copyroot,
apr_pool_t *pool);
@@ -341,7 +351,7 @@ svn_error_t *svn_fs_fs__dag_clone_child(dag_node_t **child_p,
*/
svn_error_t *svn_fs_fs__dag_delete(dag_node_t *parent,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -384,7 +394,7 @@ svn_error_t *svn_fs_fs__dag_make_dir(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -495,7 +505,7 @@ svn_error_t *svn_fs_fs__dag_make_file(dag_node_t **child_p,
dag_node_t *parent,
const char *parent_path,
const char *name,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
@@ -522,33 +532,31 @@ svn_error_t *svn_fs_fs__dag_copy(dag_node_t *to_node,
svn_boolean_t preserve_history,
svn_revnum_t from_rev,
const char *from_path,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
/* Comparison */
-/* Find out what is the same between two nodes.
+/* Find out what is the same between two nodes. If STRICT is FALSE,
+ this function may report false positives, i.e. report changes even
+ if the resulting contents / props are equal.
If PROPS_CHANGED is non-null, set *PROPS_CHANGED to 1 if the two
nodes have different property lists, or to 0 if same.
If CONTENTS_CHANGED is non-null, set *CONTENTS_CHANGED to 1 if the
- two nodes have different contents, or to 0 if same. For files,
- file contents are compared; for directories, the entries lists are
- compared. If one is a file and the other is a directory, the one's
- contents will be compared to the other's entries list. (Not
- terribly useful, I suppose, but that's the caller's business.)
-
- ### todo: This function only compares rep keys at the moment. This
- may leave us with a slight chance of a false positive, though I
- don't really see how that would happen in practice. Nevertheless,
- it should probably be fixed.
+ two nodes have different contents, or to 0 if same. NODE1 and NODE2
+ must refer to files from the same filesystem.
+
+ Use POOL for temporary allocations.
*/
svn_error_t *svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
svn_boolean_t *contents_changed,
dag_node_t *node1,
- dag_node_t *node2);
+ dag_node_t *node2,
+ svn_boolean_t strict,
+ apr_pool_t *pool);
/* Set *REV and *PATH to the copyroot revision and path of node NODE, or
diff --git a/subversion/libsvn_fs_fs/dump-index.c b/subversion/libsvn_fs_fs/dump-index.c
new file mode 100644
index 0000000..c97be10
--- /dev/null
+++ b/subversion/libsvn_fs_fs/dump-index.c
@@ -0,0 +1,90 @@
+/* dump-index.c -- implements the svn_fs_fs__dump_index private API
+ *
+ * ====================================================================
+ * 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_pools.h"
+#include "private/svn_fs_fs_private.h"
+
+#include "index.h"
+#include "rev_file.h"
+#include "util.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+svn_error_t *
+svn_fs_fs__dump_index(svn_fs_t *fs,
+ svn_revnum_t revision,
+ svn_fs_fs__dump_index_func_t callback_func,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_fs__revision_file_t *rev_file;
+ int i;
+ apr_off_t offset, max_offset;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Check the FS format. */
+ if (! svn_fs_fs__use_log_addressing(fs))
+ return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, NULL);
+
+ /* Revision & index file access object. */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision,
+ scratch_pool, iterpool));
+
+ /* Offset range to cover. */
+ SVN_ERR(svn_fs_fs__p2l_get_max_offset(&max_offset, fs, rev_file, revision,
+ scratch_pool));
+
+ /* Walk through all P2L index entries in offset order. */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+
+ /* Read entries for the next block. There will be no overlaps since
+ * we start at the first offset not covered. */
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, fs, rev_file, revision,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+
+ /* Print entries for this block, one line per entry. */
+ for (i = 0; i < entries->nelts && offset < max_offset; ++i)
+ {
+ const svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t);
+ offset = entry->offset + entry->size;
+
+ /* Cancellation support */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Invoke processing callback. */
+ SVN_ERR(callback_func(entry, callback_baton, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c
index d0ba734..1978798 100644
--- a/subversion/libsvn_fs_fs/fs.c
+++ b/subversion/libsvn_fs_fs/fs.c
@@ -37,11 +37,17 @@
#include "fs_fs.h"
#include "tree.h"
#include "lock.h"
+#include "hotcopy.h"
#include "id.h"
+#include "pack.h"
+#include "recovery.h"
#include "rep-cache.h"
+#include "revprops.h"
+#include "transaction.h"
+#include "util.h"
+#include "verify.h"
#include "svn_private_config.h"
#include "private/svn_fs_util.h"
-#include "private/svn_subr_private.h"
#include "../libsvn_fs/fs-loader.h"
@@ -51,6 +57,9 @@
+/* Initialize the part of FS that requires global serialization across all
+ instances. The caller is responsible of ensuring that serialization.
+ Use COMMON_POOL for process-wide and POOL for temporary allocations. */
static svn_error_t *
fs_serialized_init(svn_fs_t *fs, apr_pool_t *common_pool, apr_pool_t *pool)
{
@@ -64,20 +73,29 @@ fs_serialized_init(svn_fs_t *fs, apr_pool_t *common_pool, apr_pool_t *pool)
each separate repository opened during the lifetime of the
svn_fs_initialize pool. It's unlikely that anyone will notice
the modest expenditure; the alternative is to allocate each structure
- in a subpool, add a reference-count, and add a serialized deconstructor
+ in a subpool, add a reference-count, and add a serialized destructor
to the FS vtable. That's more machinery than it's worth.
- Using the uuid to obtain the lock creates a corner case if a
- caller uses svn_fs_set_uuid on the repository in a process where
- other threads might be using the same repository through another
- FS object. The only real-world consumer of svn_fs_set_uuid is
- "svnadmin load", so this is a low-priority problem, and we don't
- know of a better way of associating such data with the
- repository. */
+ Picking an appropriate key for the shared data is tricky, because,
+ unfortunately, a filesystem UUID is not really unique. It is implicitly
+ shared between hotcopied (1), dump / loaded (2) or naively copied (3)
+ filesystems. We tackle this problem by using a combination of the UUID
+ and an instance ID as the key. This allows us to avoid key clashing
+ in (1) and (2) for formats >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT, which
+ do support instance IDs. For old formats the shared data (locks, shared
+ transaction data, ...) will still clash.
+
+ Speaking of (3), there is not so much we can do about it, except maybe
+ provide a convenient way of fixing things. Naively copied filesystems
+ have identical filesystem UUIDs *and* instance IDs. With the key being
+ a combination of these two, clashes can be fixed by changing either of
+ them (or both), e.g. with svn_fs_set_uuid(). */
SVN_ERR_ASSERT(fs->uuid);
- key = apr_pstrcat(pool, SVN_FSFS_SHARED_USERDATA_PREFIX, fs->uuid,
- (char *) NULL);
+ SVN_ERR_ASSERT(ffd->instance_id);
+
+ key = apr_pstrcat(pool, SVN_FSFS_SHARED_USERDATA_PREFIX,
+ fs->uuid, ":", ffd->instance_id, SVN_VA_NULL);
status = apr_pool_userdata_get(&val, key, common_pool);
if (status)
return svn_error_wrap_apr(status, _("Can't fetch FSFS shared data"));
@@ -94,15 +112,17 @@ fs_serialized_init(svn_fs_t *fs, apr_pool_t *common_pool, apr_pool_t *pool)
SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
+ /* ... the pack lock ... */
+ SVN_ERR(svn_mutex__init(&ffsd->fs_pack_lock,
+ SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
+
/* ... not to mention locking the txn-current file. */
SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
/* We also need a mutex for synchronizing access to the active
- transaction list and free transaction pointer. This one is
- enabled unconditionally. */
- SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock,
- TRUE, common_pool));
+ transaction list and free transaction pointer. */
+ SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock, TRUE, common_pool));
key = apr_pstrdup(common_pool, key);
status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
@@ -143,9 +163,21 @@ fs_freeze_body(void *baton,
SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, b->fs, pool));
if (exists)
- SVN_ERR(svn_fs_fs__lock_rep_cache(b->fs, pool));
+ SVN_ERR(svn_fs_fs__with_rep_cache_lock(b->fs,
+ b->freeze_func, b->freeze_baton,
+ pool));
+ else
+ SVN_ERR(b->freeze_func(b->freeze_baton, pool));
+
+ return SVN_NO_ERROR;
+}
- SVN_ERR(b->freeze_func(b->freeze_baton, pool));
+static svn_error_t *
+fs_freeze_body2(void *baton,
+ apr_pool_t *pool)
+{
+ struct fs_freeze_baton_t *b = baton;
+ SVN_ERR(svn_fs_fs__with_write_lock(b->fs, fs_freeze_body, baton, pool));
return SVN_NO_ERROR;
}
@@ -156,6 +188,7 @@ fs_freeze(svn_fs_t *fs,
void *freeze_baton,
apr_pool_t *pool)
{
+ fs_fs_data_t *ffd = fs->fsap_data;
struct fs_freeze_baton_t b;
b.fs = fs;
@@ -163,20 +196,52 @@ fs_freeze(svn_fs_t *fs,
b.freeze_baton = freeze_baton;
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- SVN_ERR(svn_fs_fs__with_write_lock(fs, fs_freeze_body, &b, pool));
+
+ if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
+ SVN_ERR(svn_fs_fs__with_pack_lock(fs, fs_freeze_body2, &b, pool));
+ else
+ SVN_ERR(fs_freeze_body2(&b, pool));
return SVN_NO_ERROR;
}
+static svn_error_t *
+fs_info(const void **fsfs_info,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_fsfs_info_t *info = apr_palloc(result_pool, sizeof(*info));
+ info->fs_type = SVN_FS_TYPE_FSFS;
+ info->shard_size = ffd->max_files_per_dir;
+ info->min_unpacked_rev = ffd->min_unpacked_rev;
+ info->log_addressing = ffd->use_log_addressing;
+ *fsfs_info = info;
+ return SVN_NO_ERROR;
+}
+
+/* Wrapper around svn_fs_fs__set_uuid() adapting between function
+ signatures. */
+static svn_error_t *
+fs_set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool)
+{
+ /* Whenever we set a new UUID, imply that FS will also be a different
+ * instance (on formats that support this). */
+ return svn_error_trace(svn_fs_fs__set_uuid(fs, uuid, NULL, pool));
+}
+
/* The vtable associated with a specific open filesystem. */
static fs_vtable_t fs_vtable = {
svn_fs_fs__youngest_rev,
svn_fs_fs__revision_prop,
- svn_fs_fs__revision_proplist,
+ svn_fs_fs__get_revision_proplist,
svn_fs_fs__change_rev_prop,
- svn_fs_fs__set_uuid,
+ fs_set_uuid,
svn_fs_fs__revision_root,
svn_fs_fs__begin_txn,
svn_fs_fs__open_txn,
@@ -188,6 +253,9 @@ static fs_vtable_t fs_vtable = {
svn_fs_fs__unlock,
svn_fs_fs__get_lock,
svn_fs_fs__get_locks,
+ svn_fs_fs__info_format,
+ svn_fs_fs__info_config_files,
+ fs_info,
svn_fs_fs__verify_root,
fs_freeze,
fs_set_errcall
@@ -201,17 +269,31 @@ static svn_error_t *
initialize_fs_struct(svn_fs_t *fs)
{
fs_fs_data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
+ ffd->use_log_addressing = FALSE;
+
fs->vtable = &fs_vtable;
fs->fsap_data = ffd;
return SVN_NO_ERROR;
}
+/* Reset vtable and fsap_data fields in FS such that the FS is basically
+ * closed now. Note that FS must not hold locks when you call this. */
+static void
+uninitialize_fs_struct(svn_fs_t *fs)
+{
+ fs->vtable = NULL;
+ fs->fsap_data = NULL;
+}
+
/* This implements the fs_library_vtable_t.create() API. Create a new
fsfs-backed Subversion filesystem at path PATH and link it into
*FS. Perform temporary allocations in POOL, and fs-global allocations
- in COMMON_POOL. */
+ in COMMON_POOL. The latter must be serialized using COMMON_POOL_LOCK. */
static svn_error_t *
-fs_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+fs_create(svn_fs_t *fs,
+ const char *path,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
apr_pool_t *common_pool)
{
SVN_ERR(svn_fs__check_fs(fs, FALSE));
@@ -221,7 +303,10 @@ fs_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
SVN_ERR(svn_fs_fs__create(fs, path, pool));
SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- return fs_serialized_init(fs, common_pool, pool);
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ fs_serialized_init(fs, common_pool, pool));
+
+ return SVN_NO_ERROR;
}
@@ -231,17 +316,30 @@ fs_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
/* This implements the fs_library_vtable_t.open() API. Open an FSFS
Subversion filesystem located at PATH, set *FS to point to the
correct vtable for the filesystem. Use POOL for any temporary
- allocations, and COMMON_POOL for fs-global allocations. */
+ allocations, and COMMON_POOL for fs-global allocations.
+ The latter must be serialized using COMMON_POOL_LOCK. */
static svn_error_t *
-fs_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+fs_open(svn_fs_t *fs,
+ const char *path,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
apr_pool_t *common_pool)
{
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
SVN_ERR(initialize_fs_struct(fs));
- SVN_ERR(svn_fs_fs__open(fs, path, pool));
+ SVN_ERR(svn_fs_fs__open(fs, path, subpool));
- SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- return fs_serialized_init(fs, common_pool, pool);
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, subpool));
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ fs_serialized_init(fs, common_pool, subpool));
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
}
@@ -250,40 +348,79 @@ fs_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
static svn_error_t *
fs_open_for_recovery(svn_fs_t *fs,
const char *path,
- apr_pool_t *pool, apr_pool_t *common_pool)
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
{
+ svn_error_t * err;
+ svn_revnum_t youngest_rev;
+ apr_pool_t * subpool = svn_pool_create(pool);
+
/* Recovery for FSFS is currently limited to recreating the 'current'
file from the latest revision. */
/* The only thing we have to watch out for is that the 'current' file
- might not exist. So we'll try to create it here unconditionally,
- and just ignore any errors that might indicate that it's already
- present. (We'll need it to exist later anyway as a source for the
- new file's permissions). */
+ might not exist or contain garbage. So we'll try to read it here
+ and provide or replace the existing file if we couldn't read it.
+ (We'll also need it to exist later anyway as a source for the new
+ file's permissions). */
- /* Use a partly-filled fs pointer first to create 'current'. This will fail
- if 'current' already exists, but we don't care about that. */
+ /* Use a partly-filled fs pointer first to create 'current'. */
fs->path = apr_pstrdup(fs->pool, path);
- svn_error_clear(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
- "0 1 1\n", pool));
+
+ SVN_ERR(initialize_fs_struct(fs));
+
+ /* Figure out the repo format and check that we can even handle it. */
+ SVN_ERR(svn_fs_fs__read_format_file(fs, subpool));
+
+ /* Now, read 'current' and try to patch it if necessary. */
+ err = svn_fs_fs__youngest_rev(&youngest_rev, fs, subpool);
+ if (err)
+ {
+ const char *file_path;
+
+ /* 'current' file is missing or contains garbage. Since we are trying
+ * to recover from whatever problem there is, being picky about the
+ * error code here won't do us much good. If there is a persistent
+ * problem that we can't fix, it will show up when we try rewrite the
+ * file a few lines further below and we will report the failure back
+ * to the caller.
+ *
+ * Start recovery with HEAD = 0. */
+ svn_error_clear(err);
+ file_path = svn_fs_fs__path_current(fs, subpool);
+
+ /* Best effort to ensure the file exists and is valid.
+ * This may fail for r/o filesystems etc. */
+ SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
+ SVN_ERR(svn_io_file_create_empty(file_path, subpool));
+ SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, subpool));
+ }
+
+ uninitialize_fs_struct(fs);
+ svn_pool_destroy(subpool);
/* Now open the filesystem properly by calling the vtable method directly. */
- return fs_open(fs, path, pool, common_pool);
+ return fs_open(fs, path, common_pool_lock, pool, common_pool);
}
/* This implements the fs_library_vtable_t.upgrade_fs() API. */
static svn_error_t *
-fs_upgrade(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+fs_upgrade(svn_fs_t *fs,
+ const char *path,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
apr_pool_t *common_pool)
{
- SVN_ERR(svn_fs__check_fs(fs, FALSE));
- SVN_ERR(initialize_fs_struct(fs));
- SVN_ERR(svn_fs_fs__open(fs, path, pool));
- SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- SVN_ERR(fs_serialized_init(fs, common_pool, pool));
- return svn_fs_fs__upgrade(fs, pool);
+ SVN_ERR(fs_open(fs, path, common_pool_lock, pool, common_pool));
+ return svn_fs_fs__upgrade(fs, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
}
static svn_error_t *
@@ -294,14 +431,11 @@ fs_verify(svn_fs_t *fs, const char *path,
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
apr_pool_t *pool,
apr_pool_t *common_pool)
{
- SVN_ERR(svn_fs__check_fs(fs, FALSE));
- SVN_ERR(initialize_fs_struct(fs));
- SVN_ERR(svn_fs_fs__open(fs, path, pool));
- SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- SVN_ERR(fs_serialized_init(fs, common_pool, pool));
+ SVN_ERR(fs_open(fs, path, common_pool_lock, pool, common_pool));
return svn_fs_fs__verify(fs, start, end, notify_func, notify_baton,
cancel_func, cancel_baton, pool);
}
@@ -313,15 +447,12 @@ fs_pack(svn_fs_t *fs,
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
apr_pool_t *pool,
apr_pool_t *common_pool)
{
- SVN_ERR(svn_fs__check_fs(fs, FALSE));
- SVN_ERR(initialize_fs_struct(fs));
- SVN_ERR(svn_fs_fs__open(fs, path, pool));
- SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- SVN_ERR(fs_serialized_init(fs, common_pool, pool));
- return svn_fs_fs__pack(fs, notify_func, notify_baton,
+ SVN_ERR(fs_open(fs, path, common_pool_lock, pool, common_pool));
+ return svn_fs_fs__pack(fs, 0, notify_func, notify_baton,
cancel_func, cancel_baton, pool);
}
@@ -333,7 +464,8 @@ fs_pack(svn_fs_t *fs,
DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
re-copy data which already exists in DST_FS.
The CLEAN_LOGS argument is ignored and included for Subversion
- 1.0.x compatibility. Perform all temporary allocations in POOL. */
+ 1.0.x compatibility. Indicate progress via the optional NOTIFY_FUNC
+ callback using NOTIFY_BATON. Perform all temporary allocations in POOL. */
static svn_error_t *
fs_hotcopy(svn_fs_t *src_fs,
svn_fs_t *dst_fs,
@@ -341,24 +473,36 @@ fs_hotcopy(svn_fs_t *src_fs,
const char *dst_path,
svn_boolean_t clean_logs,
svn_boolean_t incremental,
+ svn_fs_hotcopy_notify_t notify_func,
+ void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
- apr_pool_t *pool)
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
{
- SVN_ERR(svn_fs__check_fs(src_fs, FALSE));
- SVN_ERR(initialize_fs_struct(src_fs));
- SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
- SVN_ERR(svn_fs_fs__initialize_caches(src_fs, pool));
- SVN_ERR(fs_serialized_init(src_fs, pool, pool));
-
- SVN_ERR(svn_fs__check_fs(dst_fs, FALSE));
+ /* Open the source repo as usual. */
+ SVN_ERR(fs_open(src_fs, src_path, common_pool_lock, pool, common_pool));
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Test target repo when in INCREMENTAL mode, initialize it when not.
+ * For this, we need our FS internal data structures to be temporarily
+ * available. */
SVN_ERR(initialize_fs_struct(dst_fs));
- /* In INCREMENTAL mode, svn_fs_fs__hotcopy() will open DST_FS.
- Otherwise, it's not an FS yet --- possibly just an empty dir --- so
- can't be opened.
- */
- return svn_fs_fs__hotcopy(src_fs, dst_fs, src_path, dst_path,
- incremental, cancel_func, cancel_baton, pool);
+ SVN_ERR(svn_fs_fs__hotcopy_prepare_target(src_fs, dst_fs, dst_path,
+ incremental, pool));
+ uninitialize_fs_struct(dst_fs);
+
+ /* Now, the destination repo should open just fine. */
+ SVN_ERR(fs_open(dst_fs, dst_path, common_pool_lock, pool, common_pool));
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Now, we may copy data as needed ... */
+ return svn_fs_fs__hotcopy(src_fs, dst_fs, incremental,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
}
@@ -389,7 +533,7 @@ fs_delete_fs(const char *path,
apr_pool_t *pool)
{
/* Remove everything. */
- return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+ return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool));
}
static const svn_version_t *
@@ -409,6 +553,7 @@ fs_set_svn_fs_open(svn_fs_t *fs,
svn_error_t *(*svn_fs_open_)(svn_fs_t **,
const char *,
apr_hash_t *,
+ apr_pool_t *,
apr_pool_t *))
{
fs_fs_data_t *ffd = fs->fsap_data;
@@ -416,6 +561,15 @@ fs_set_svn_fs_open(svn_fs_t *fs,
return SVN_NO_ERROR;
}
+static void *
+fs_info_dup(const void *fsfs_info_void,
+ apr_pool_t *result_pool)
+{
+ /* All fields are either ints or static strings. */
+ const svn_fs_fsfs_info_t *fsfs_info = fsfs_info_void;
+ return apr_pmemdup(result_pool, fsfs_info, sizeof(*fsfs_info));
+}
+
/* Base FS library vtable, used by the FS loader library. */
@@ -433,7 +587,8 @@ static fs_library_vtable_t library_vtable = {
fs_pack,
fs_logfiles,
NULL /* parse_id */,
- fs_set_svn_fs_open
+ fs_set_svn_fs_open,
+ fs_info_dup
};
svn_error_t *
@@ -444,6 +599,7 @@ svn_fs_fs__init(const svn_version_t *loader_version,
{
{ "svn_subr", svn_subr_version },
{ "svn_delta", svn_delta_version },
+ { "svn_fs_util", svn_fs_util__version },
{ NULL, NULL }
};
diff --git a/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h
index 5cdc270..c75eafb 100644
--- a/subversion/libsvn_fs_fs/fs.h
+++ b/subversion/libsvn_fs_fs/fs.h
@@ -26,6 +26,8 @@
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_network_io.h>
+#include <apr_md5.h>
+#include <apr_sha1.h>
#include "svn_fs.h"
#include "svn_config.h"
@@ -34,7 +36,8 @@
#include "private/svn_fs_private.h"
#include "private/svn_sqlite.h"
#include "private/svn_mutex.h"
-#include "private/svn_named_atomic.h"
+
+#include "id.h"
#ifdef __cplusplus
extern "C" {
@@ -51,9 +54,11 @@ extern "C" {
#define PATH_UUID "uuid" /* Contains UUID */
#define PATH_CURRENT "current" /* Youngest revision */
#define PATH_LOCK_FILE "write-lock" /* Revision lock file */
+#define PATH_PACK_LOCK_FILE "pack-lock" /* Pack lock file */
#define PATH_REVS_DIR "revs" /* Directory of revisions */
#define PATH_REVPROPS_DIR "revprops" /* Directory of revprops */
-#define PATH_TXNS_DIR "transactions" /* Directory of transactions */
+#define PATH_TXNS_DIR "transactions" /* Directory of transactions in
+ repos w/o log addressing */
#define PATH_NODE_ORIGINS_DIR "node-origins" /* Lazy node-origin cache */
#define PATH_TXN_PROTOS_DIR "txn-protorevs" /* Directory of proto-revs */
#define PATH_TXN_CURRENT "txn-current" /* File with next txn key */
@@ -67,12 +72,18 @@ extern "C" {
#define PATH_PACKED "pack" /* Packed revision data file */
#define PATH_EXT_PACKED_SHARD ".pack" /* Extension for packed
shards */
+#define PATH_EXT_L2P_INDEX ".l2p" /* extension of the log-
+ to-phys index */
+#define PATH_EXT_P2L_INDEX ".p2l" /* extension of the phys-
+ to-log index */
/* If you change this, look at tests/svn_test_fs.c(maybe_install_fsfs_conf) */
#define PATH_CONFIG "fsfs.conf" /* Configuration */
/* Names of special files and file extensions for transactions */
#define PATH_CHANGES "changes" /* Records changes made so far */
#define PATH_TXN_PROPS "props" /* Transaction properties */
+#define PATH_TXN_PROPS_FINAL "props-final" /* Final transaction properties
+ before moving to revprops */
#define PATH_NEXT_IDS "next-ids" /* Next temporary ID assignments */
#define PATH_PREFIX_NODE "node." /* Prefix for node filename */
#define PATH_EXT_TXN ".txn" /* Extension of txn dir */
@@ -80,6 +91,10 @@ extern "C" {
#define PATH_EXT_PROPS ".props" /* Extension for node props */
#define PATH_EXT_REV ".rev" /* Extension of protorev file */
#define PATH_EXT_REV_LOCK ".rev-lock" /* Extension of protorev lock file */
+#define PATH_TXN_ITEM_INDEX "itemidx" /* File containing the current item
+ index number */
+#define PATH_INDEX "index" /* name of index files w/o ext */
+
/* Names of files in legacy FS formats */
#define PATH_REV "rev" /* Proto rev file */
#define PATH_REV_LOCK "rev-lock" /* Proto rev (write) lock file */
@@ -94,14 +109,25 @@ extern "C" {
#define CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION "enable-props-deltification"
#define CONFIG_OPTION_MAX_DELTIFICATION_WALK "max-deltification-walk"
#define CONFIG_OPTION_MAX_LINEAR_DELTIFICATION "max-linear-deltification"
+#define CONFIG_OPTION_COMPRESSION_LEVEL "compression-level"
#define CONFIG_SECTION_PACKED_REVPROPS "packed-revprops"
#define CONFIG_OPTION_REVPROP_PACK_SIZE "revprop-pack-size"
#define CONFIG_OPTION_COMPRESS_PACKED_REVPROPS "compress-packed-revprops"
+#define CONFIG_SECTION_IO "io"
+#define CONFIG_OPTION_BLOCK_SIZE "block-size"
+#define CONFIG_OPTION_L2P_PAGE_SIZE "l2p-page-size"
+#define CONFIG_OPTION_P2L_PAGE_SIZE "p2l-page-size"
+#define CONFIG_SECTION_DEBUG "debug"
+#define CONFIG_OPTION_PACK_AFTER_COMMIT "pack-after-commit"
/* The format number of this filesystem.
This is independent of the repository format number, and
- independent of any other FS back ends. */
-#define SVN_FS_FS__FORMAT_NUMBER 6
+ independent of any other FS back ends.
+
+ Note: If you bump this, please update the switch statement in
+ svn_fs_fs__create() as well.
+ */
+#define SVN_FS_FS__FORMAT_NUMBER 7
/* The minimum format number that supports svndiff version 1. */
#define SVN_FS_FS__MIN_SVNDIFF1_FORMAT 2
@@ -144,9 +170,31 @@ extern "C" {
/* The minimum format number that supports packed revprops. */
#define SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 6
+/* The minimum format number that supports packed revprops. */
+#define SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT 7
+
+/* Minimum format number that providing a separate lock file for pack ops */
+#define SVN_FS_FS__MIN_PACK_LOCK_FORMAT 7
+
+/* Minimum format number that stores mergeinfo-mode flag in changed paths */
+#define SVN_FS_FS__MIN_MERGEINFO_IN_CHANGED_FORMAT 7
+
+/* Minimum format number that supports per-instance filesystem IDs. */
+#define SVN_FS_FS__MIN_INSTANCE_ID_FORMAT 7
+
/* The minimum format number that supports a configuration file (fsfs.conf) */
#define SVN_FS_FS__MIN_CONFIG_FILE 4
+/* On most operating systems apr implements file locks per process, not
+ per file. On Windows apr implements the locking as per file handle
+ locks, so we don't have to add our own mutex for just in-process
+ synchronization. */
+#if APR_HAS_THREADS && !defined(WIN32)
+#define SVN_FS_FS__USE_LOCK_MUTEX 1
+#else
+#define SVN_FS_FS__USE_LOCK_MUTEX 0
+#endif
+
/* Private FSFS-specific data shared between all svn_txn_t objects that
relate to a particular transaction in a filesystem (as identified
by transaction id and filesystem UUID). Objects of this type are
@@ -157,13 +205,8 @@ typedef struct fs_fs_shared_txn_data_t
transaction. */
struct fs_fs_shared_txn_data_t *next;
- /* This transaction's ID. For repositories whose format is less
- than SVN_FS_FS__MIN_TXN_CURRENT_FORMAT, the ID is in the form
- <rev>-<uniqueifier>, where <uniqueifier> runs from 0-99999 (see
- create_txn_dir_pre_1_5() in fs_fs.c). For newer repositories,
- the form is <rev>-<200 digit base 36 number> (see
- create_txn_dir() in fs_fs.c). */
- char txn_id[SVN_FS__TXN_MAX_LEN+1];
+ /* ID of this transaction. */
+ svn_fs_fs__id_part_t txn_id;
/* Whether the transaction's prototype revision file is locked for
writing by any thread in this process (including the current
@@ -176,17 +219,6 @@ typedef struct fs_fs_shared_txn_data_t
apr_pool_t *pool;
} fs_fs_shared_txn_data_t;
-/* On most operating systems apr implements file locks per process, not
- per file. On Windows apr implements the locking as per file handle
- locks, so we don't have to add our own mutex for just in-process
- synchronization. */
-/* Compare ../libsvn_subr/named_atomic.c:USE_THREAD_MUTEX */
-#if APR_HAS_THREADS && !defined(WIN32)
-#define SVN_FS_FS__USE_LOCK_MUTEX 1
-#else
-#define SVN_FS_FS__USE_LOCK_MUTEX 0
-#endif
-
/* Private FSFS-specific data shared between all svn_fs_t objects that
relate to a particular filesystem, as identified by filesystem UUID.
Objects of this type are allocated in the common pool. */
@@ -202,6 +234,12 @@ typedef struct fs_fs_shared_data_t
Access to this object is synchronised under TXN_LIST_LOCK. */
fs_fs_shared_txn_data_t *free_txn;
+ /* The following lock must be taken out in reverse order of their
+ declaration here. Any subset may be acquired and held at any given
+ time but their relative acquisition order must not change.
+
+ (lock 'txn-current' before 'pack' before 'write' before 'txn-list') */
+
/* A lock for intra-process synchronization when accessing the TXNS list. */
svn_mutex__t *txn_list_lock;
@@ -209,6 +247,10 @@ typedef struct fs_fs_shared_data_t
repository write lock. */
svn_mutex__t *fs_write_lock;
+ /* A lock for intra-process synchronization when grabbing the
+ repository pack operation lock. */
+ svn_mutex__t *fs_pack_lock;
+
/* A lock for intra-process synchronization when locking the
txn-current file. */
svn_mutex__t *txn_current_lock;
@@ -221,34 +263,76 @@ typedef struct fs_fs_shared_data_t
/* Data structure for the 1st level DAG node cache. */
typedef struct fs_fs_dag_cache_t fs_fs_dag_cache_t;
-/* Key type for all caches that use revision + offset / counter as key. */
+/* Key type for all caches that use revision + offset / counter as key.
+
+ Note: Cache keys should be 16 bytes for best performance and there
+ should be no padding. */
typedef struct pair_cache_key_t
{
- svn_revnum_t revision;
+ /* The object's revision. Use the 64 data type to prevent padding. */
+ apr_int64_t revision;
+ /* Sub-address: item index, revprop generation, packed flag, etc. */
apr_int64_t second;
} pair_cache_key_t;
+/* Key type that identifies a txdelta window.
+
+ Note: Cache keys should require no padding. */
+typedef struct window_cache_key_t
+{
+ /* The object's revision. Use the 64 data type to prevent padding. */
+ apr_int64_t revision;
+
+ /* Window number within that representation. */
+ apr_int64_t chunk_index;
+
+ /* Item index of the representation */
+ apr_uint64_t item_index;
+} window_cache_key_t;
+
/* Private (non-shared) FSFS-specific data for each svn_fs_t object.
Any caches in here may be NULL. */
typedef struct fs_fs_data_t
{
/* The format number of this FS. */
int format;
+
/* The maximum number of files to store per directory (for sharded
layouts) or zero (for linear layouts). */
int max_files_per_dir;
+ /* If set, this FS is using logical addressing. Otherwise, it is using
+ physical addressing. */
+ svn_boolean_t use_log_addressing;
+
+ /* Rev / pack file read granularity in bytes. */
+ apr_int64_t block_size;
+
+ /* Capacity in entries of log-to-phys index pages */
+ apr_int64_t l2p_page_size;
+
+ /* Rev / pack file granularity (in bytes) covered by a single phys-to-log
+ * index page. */
+ apr_int64_t p2l_page_size;
+
+ /* If set, parse and cache *all* data of each block that we read
+ * (not just the one bit that we need, atm). */
+ svn_boolean_t use_block_read;
+
/* The revision that was youngest, last time we checked. */
svn_revnum_t youngest_rev_cache;
- /* The fsfs.conf file, parsed. Allocated in FS->pool. */
- svn_config_t *config;
-
- /* Caches of immutable data. (Note that if these are created with
- svn_cache__create_memcache, the data can be shared between
+ /* Caches of immutable data. (Note that these may be shared between
multiple svn_fs_t's for the same filesystem.) */
+ /* Access to the configured memcached instances. May be NULL. */
+ svn_memcache_t *memcache;
+
+ /* If TRUE, don't ignore any cache-related errors. If FALSE, errors from
+ e.g. memcached may be ignored as caching is an optional feature. */
+ svn_boolean_t fail_stop;
+
/* A cache of revision root IDs, mapping from (svn_revnum_t *) to
(svn_fs_id_t *). (Not threadsafe.) */
svn_cache__t *rev_root_id_cache;
@@ -269,21 +353,6 @@ typedef struct fs_fs_data_t
rep key (revision/offset) to svn_stringbuf_t. */
svn_cache__t *fulltext_cache;
- /* Access object to the atomics namespace used by revprop caching.
- Will be NULL until the first access. */
- svn_atomic_namespace__t *revprop_namespace;
-
- /* Access object to the revprop "generation". Will be NULL until
- the first access. */
- svn_named_atomic__t *revprop_generation;
-
- /* Access object to the revprop update timeout. Will be NULL until
- the first access. */
- svn_named_atomic__t *revprop_timeout;
-
- /* Revision property cache. Maps from (rev,generation) to apr_hash_t. */
- svn_cache__t *revprop_cache;
-
/* Node properties cache. Maps from rep key to apr_hash_t. */
svn_cache__t *properties_cache;
@@ -293,20 +362,28 @@ typedef struct fs_fs_data_t
respective pack file. */
svn_cache__t *packed_offset_cache;
- /* Cache for txdelta_window_t objects; the key is (revFilePath, offset) */
+ /* Cache for svn_fs_fs__raw_cached_window_t objects; the key is
+ window_cache_key_t. */
+ svn_cache__t *raw_window_cache;
+
+ /* Cache for txdelta_window_t objects; the key is window_cache_key_t */
svn_cache__t *txdelta_window_cache;
/* Cache for combined windows as svn_stringbuf_t objects;
- the key is (revFilePath, offset) */
+ the key is window_cache_key_t */
svn_cache__t *combined_window_cache;
- /* Cache for node_revision_t objects; the key is (revision, id offset) */
+ /* Cache for node_revision_t objects; the key is (revision, item_index) */
svn_cache__t *node_revision_cache;
/* Cache for change lists as APR arrays of change_t * objects; the key
is the revision */
svn_cache__t *changes_cache;
+ /* Cache for svn_fs_fs__rep_header_t objects; the key is a
+ (revision, item index) pair */
+ svn_cache__t *rep_header_cache;
+
/* Cache for svn_mergeinfo_t objects; the key is a combination of
revision, inheritance flags and path. */
svn_cache__t *mergeinfo_cache;
@@ -316,6 +393,23 @@ typedef struct fs_fs_data_t
if the node has mergeinfo, "0" if it doesn't. */
svn_cache__t *mergeinfo_existence_cache;
+ /* Cache for l2p_header_t objects; the key is (revision, is-packed).
+ Will be NULL for pre-format7 repos */
+ svn_cache__t *l2p_header_cache;
+
+ /* Cache for l2p_page_t objects; the key is svn_fs_fs__page_cache_key_t.
+ Will be NULL for pre-format7 repos */
+ svn_cache__t *l2p_page_cache;
+
+ /* Cache for p2l_header_t objects; the key is (revision, is-packed).
+ Will be NULL for pre-format7 repos */
+ svn_cache__t *p2l_header_cache;
+
+ /* Cache for apr_array_header_t objects containing svn_fs_fs__p2l_entry_t
+ elements; the key is svn_fs_fs__page_cache_key_t.
+ Will be NULL for pre-format7 repos */
+ svn_cache__t *p2l_page_cache;
+
/* TRUE while the we hold a lock on the write lock file. */
svn_boolean_t has_write_lock;
@@ -363,19 +457,27 @@ typedef struct fs_fs_data_t
* deltification history after which skip deltas will be used. */
apr_int64_t max_linear_deltification;
+ /* Compression level to use with txdelta storage format in new revs. */
+ int delta_compression_level;
+
+ /* Pack after every commit. */
+ svn_boolean_t pack_after_commit;
+
+ /* Per-instance filesystem ID, which provides an additional level of
+ uniqueness for filesystems that share the same UUID, but should
+ still be distinguishable (e.g. backups produced by svn_fs_hotcopy()
+ or dump / load cycles). */
+ const char *instance_id;
+
/* Pointer to svn_fs_open. */
svn_error_t *(*svn_fs_open_)(svn_fs_t **, const char *, apr_hash_t *,
- apr_pool_t *);
+ apr_pool_t *, apr_pool_t *);
} fs_fs_data_t;
/*** Filesystem Transaction ***/
typedef struct transaction_t
{
- /* property list (const char * name, svn_string_t * value).
- may be NULL if there are no properties. */
- apr_hash_t *proplist;
-
/* node revision id of the root node. */
const svn_fs_id_t *root_id;
@@ -395,26 +497,26 @@ typedef struct transaction_t
* svn_fs_fs__rep_copy. */
typedef struct representation_t
{
- /* Checksums for the contents produced by this representation.
+ /* Checksums digests for the contents produced by this representation.
This checksum is for the contents the rep shows to consumers,
regardless of how the rep stores the data under the hood. It is
independent of the storage (fulltext, delta, whatever).
- If checksum is NULL, then for compatibility behave as though this
+ If has_sha1 is FALSE, then for compatibility behave as though this
checksum matches the expected checksum.
The md5 checksum is always filled, unless this is rep which was
retrieved from the rep-cache. The sha1 checksum is only computed on
- a write, for use with rep-sharing; it may be read from an existing
- representation, but otherwise it is NULL. */
- svn_checksum_t *md5_checksum;
- svn_checksum_t *sha1_checksum;
+ a write, for use with rep-sharing. */
+ svn_boolean_t has_sha1;
+ unsigned char sha1_digest[APR_SHA1_DIGESTSIZE];
+ unsigned char md5_digest[APR_MD5_DIGESTSIZE];
/* Revision where this representation is located. */
svn_revnum_t revision;
- /* Offset into the revision file where it is located. */
- apr_off_t offset;
+ /* Item index with the the revision. */
+ apr_uint64_t item_index;
/* The size of the representation in bytes as seen in the revision
file. */
@@ -424,17 +526,21 @@ typedef struct representation_t
* the fulltext size is equal to representation size in the rev file, */
svn_filesize_t expanded_size;
- /* Is this representation a transaction? */
- const char *txn_id;
+ /* Is this a representation (still) within a transaction? */
+ svn_fs_fs__id_part_t txn_id;
/* For rep-sharing, we need a way of uniquifying node-revs which share the
same representation (see svn_fs_fs__noderev_same_rep_key() ). So, we
store the original txn of the node rev (not the rep!), along with some
- intra-node uniqification content.
-
- May be NULL, in which case, it is considered to match other NULL
- values.*/
- const char *uniquifier;
+ intra-node uniqification content. */
+ struct
+ {
+ /* unique context, i.e. txn ID, in which the noderev (!) got created */
+ svn_fs_fs__id_part_t noderev_txn_id;
+
+ /* unique value within that txn */
+ apr_uint64_t number;
+ } uniquifier;
} representation_t;
@@ -494,25 +600,10 @@ typedef struct node_revision_t
typedef struct change_t
{
/* Path of the change. */
- const char *path;
-
- /* Node revision ID of the change. */
- const svn_fs_id_t *noderev_id;
-
- /* The kind of change. */
- svn_fs_path_change_kind_t kind;
-
- /* Text or property mods? */
- svn_boolean_t text_mod;
- svn_boolean_t prop_mod;
-
- /* Node kind (possibly svn_node_unknown). */
- svn_node_kind_t node_kind;
-
- /* Copyfrom revision and path. */
- svn_revnum_t copyfrom_rev;
- const char * copyfrom_path;
+ svn_string_t path;
+ /* API compatible change description */
+ svn_fs_path_change2_t info;
} change_t;
diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c
index 8fb3a36..103458d 100644
--- a/subversion/libsvn_fs_fs/fs_fs.c
+++ b/subversion/libsvn_fs_fs/fs_fs.c
@@ -20,59 +20,35 @@
* ====================================================================
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <apr_general.h>
-#include <apr_pools.h>
-#include <apr_file_io.h>
+#include "fs_fs.h"
+
#include <apr_uuid.h>
-#include <apr_lib.h>
-#include <apr_md5.h>
-#include <apr_sha1.h>
-#include <apr_strings.h>
-#include <apr_thread_mutex.h>
-
-#include "svn_pools.h"
-#include "svn_fs.h"
-#include "svn_dirent_uri.h"
-#include "svn_path.h"
+
+#include "svn_private_config.h"
+
+#include "svn_checksum.h"
#include "svn_hash.h"
#include "svn_props.h"
-#include "svn_sorts.h"
-#include "svn_string.h"
#include "svn_time.h"
-#include "svn_mergeinfo.h"
-#include "svn_config.h"
-#include "svn_ctype.h"
+#include "svn_dirent_uri.h"
+#include "svn_sorts.h"
#include "svn_version.h"
-#include "fs.h"
-#include "tree.h"
-#include "lock.h"
-#include "key-gen.h"
-#include "fs_fs.h"
+#include "cached_data.h"
#include "id.h"
+#include "index.h"
#include "rep-cache.h"
-#include "temp_serializer.h"
+#include "revprops.h"
+#include "transaction.h"
+#include "tree.h"
+#include "util.h"
-#include "private/svn_string_private.h"
#include "private/svn_fs_util.h"
+#include "private/svn_io_private.h"
+#include "private/svn_string_private.h"
#include "private/svn_subr_private.h"
-#include "private/svn_delta_private.h"
#include "../libsvn_fs/fs-loader.h"
-#include "svn_private_config.h"
-#include "temp_serializer.h"
-
-/* An arbitrary maximum path length, so clients can't run us out of memory
- * by giving us arbitrarily large paths. */
-#define FSFS_MAX_PATH_LEN 4096
-
/* The default maximum number of files per directory to store in the
rev and revprops directory. The number below is somewhat arbitrary,
and can be overridden by defining the macro while compiling; the
@@ -96,53 +72,6 @@
Values < 1 disable deltification. */
#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
-/* Give writing processes 10 seconds to replace an existing revprop
- file with a new one. After that time, we assume that the writing
- process got aborted and that we have re-read revprops. */
-#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
-
-/* The following are names of atomics that will be used to communicate
- * revprop updates across all processes on this machine. */
-#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
-#define ATOMIC_REVPROP_TIMEOUT "rev-prop-timeout"
-#define ATOMIC_REVPROP_NAMESPACE "rev-prop-atomics"
-
-/* Following are defines that specify the textual elements of the
- native filesystem directories and revision files. */
-
-/* Headers used to describe node-revision in the revision file. */
-#define HEADER_ID "id"
-#define HEADER_TYPE "type"
-#define HEADER_COUNT "count"
-#define HEADER_PROPS "props"
-#define HEADER_TEXT "text"
-#define HEADER_CPATH "cpath"
-#define HEADER_PRED "pred"
-#define HEADER_COPYFROM "copyfrom"
-#define HEADER_COPYROOT "copyroot"
-#define HEADER_FRESHTXNRT "is-fresh-txn-root"
-#define HEADER_MINFO_HERE "minfo-here"
-#define HEADER_MINFO_CNT "minfo-cnt"
-
-/* Kinds that a change can be. */
-#define ACTION_MODIFY "modify"
-#define ACTION_ADD "add"
-#define ACTION_DELETE "delete"
-#define ACTION_REPLACE "replace"
-#define ACTION_RESET "reset"
-
-/* True and False flags. */
-#define FLAG_TRUE "true"
-#define FLAG_FALSE "false"
-
-/* Kinds that a node-rev can be. */
-#define KIND_FILE "file"
-#define KIND_DIR "dir"
-
-/* Kinds of representation. */
-#define REP_PLAIN "PLAIN"
-#define REP_DELTA "DELTA"
-
/* Notes:
To avoid opening and closing the rev-files all the time, it would
@@ -155,59 +84,13 @@ are likely some errors because of that.
*/
-/* The vtable associated with an open transaction object. */
-static txn_vtable_t txn_vtable = {
- svn_fs_fs__commit_txn,
- svn_fs_fs__abort_txn,
- svn_fs_fs__txn_prop,
- svn_fs_fs__txn_proplist,
- svn_fs_fs__change_txn_prop,
- svn_fs_fs__txn_root,
- svn_fs_fs__change_txn_props
-};
-
/* Declarations. */
static svn_error_t *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
- const char *path,
- apr_pool_t *pool);
-
-static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
-
-static svn_error_t *
-get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
-
-static svn_error_t *
-verify_walker(representation_t *rep,
- void *baton,
- svn_fs_t *fs,
- apr_pool_t *scratch_pool);
+get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
/* Pathname helper functions */
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- return (rev < ffd->min_unpacked_rev);
-}
-
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* rev 0 will not be packed */
- return (rev < ffd->min_unpacked_rev)
- && (rev != 0)
- && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
-}
-
static const char *
path_format(svn_fs_t *fs, apr_pool_t *pool)
{
@@ -226,442 +109,250 @@ svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
return svn_dirent_join(fs->path, PATH_CURRENT, pool);
}
-static APR_INLINE const char *
-path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
-}
-static APR_INLINE const char *
-path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
-}
-
-static APR_INLINE const char *
-path_lock(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
-}
-
-static const char *
-path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
+
+/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
+static svn_error_t *
+get_lock_on_filesystem(const char *lock_filename,
+ apr_pool_t *pool)
{
- return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
+ return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool));
}
-static const char *
-path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
- apr_pool_t *pool)
+/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
+ When registered with the pool holding the lock on the lock file,
+ this makes sure the flag gets reset just before we release the lock. */
+static apr_status_t
+reset_lock_flag(void *baton_void)
{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- assert(ffd->max_files_per_dir);
- assert(is_packed_rev(fs, rev));
-
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool,
- "%ld" PATH_EXT_PACKED_SHARD,
- rev / ffd->max_files_per_dir),
- kind, NULL);
+ fs_fs_data_t *ffd = baton_void;
+ ffd->has_write_lock = FALSE;
+ return APR_SUCCESS;
}
-static const char *
-path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+/* Structure defining a file system lock to be acquired and the function
+ to be executed while the lock is held.
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool, "%ld",
- rev / ffd->max_files_per_dir),
- NULL);
-}
+ Instances of this structure may be nested to allow for multiple locks to
+ be taken out before executing the user-provided body. In that case, BODY
+ and BATON of the outer instances will be with_lock and a with_lock_baton_t
+ instance (transparently, no special treatment is required.). It is
+ illegal to attempt to acquire the same lock twice within the same lock
+ chain or via nesting calls using separate lock chains.
-static const char *
-path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+ All instances along the chain share the same LOCK_POOL such that only one
+ pool needs to be created and cleared for all locks. We also allocate as
+ much data from that lock pool as possible to minimize memory usage in
+ caller pools. */
+typedef struct with_lock_baton_t
{
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* The filesystem we operate on. Same for all instances along the chain. */
+ svn_fs_t *fs;
- assert(! is_packed_rev(fs, rev));
+ /* Mutex to complement the lock file in an APR threaded process.
+ No-op object for non-threaded processes but never NULL. */
+ svn_mutex__t *mutex;
- if (ffd->max_files_per_dir)
- {
- return svn_dirent_join(path_rev_shard(fs, rev, pool),
- apr_psprintf(pool, "%ld", rev),
- pool);
- }
+ /* Path to the file to lock. */
+ const char *lock_path;
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool, "%ld", rev), NULL);
-}
+ /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
+ svn_boolean_t is_global_lock;
-svn_error_t *
-svn_fs_fs__path_rev_absolute(const char **path,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* Function body to execute after we acquired the lock.
+ This may be user-provided or a nested call to with_lock(). */
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool);
- if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
- || ! is_packed_rev(fs, rev))
- {
- *path = path_rev(fs, rev, pool);
- }
- else
- {
- *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
- }
+ /* Baton to pass to BODY; possibly NULL.
+ This may be user-provided or a nested lock baton instance. */
+ void *baton;
- return SVN_NO_ERROR;
-}
-
-static const char *
-path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* Pool for all allocations along the lock chain and BODY. Will hold the
+ file locks and gets destroyed after the outermost BODY returned,
+ releasing all file locks.
+ Same for all instances along the chain. */
+ apr_pool_t *lock_pool;
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld",
- rev / ffd->max_files_per_dir),
- NULL);
-}
+ /* TRUE, iff BODY is the user-provided body. */
+ svn_boolean_t is_inner_most_lock;
-static const char *
-path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* TRUE, iff this is not a nested lock.
+ Then responsible for destroying LOCK_POOL. */
+ svn_boolean_t is_outer_most_lock;
+} with_lock_baton_t;
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
- rev / ffd->max_files_per_dir),
- NULL);
-}
-
-static const char *
-path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
+ with BATON->BATON. If this is the outermost lock call, release all file
+ locks after the body returned. If BATON->IS_GLOBAL_LOCK is set, set the
+ HAS_WRITE_LOCK flag while we keep the write lock. */
+static svn_error_t *
+with_some_lock_file(with_lock_baton_t *baton)
{
- fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *pool = baton->lock_pool;
+ svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
- if (ffd->max_files_per_dir)
+ if (!err)
{
- return svn_dirent_join(path_revprops_shard(fs, rev, pool),
- apr_psprintf(pool, "%ld", rev),
- pool);
- }
-
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld", rev), NULL);
-}
-
-static APR_INLINE const char *
-path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
- return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
- (char *)NULL),
- NULL);
-}
-
-/* Return the name of the sha1->rep mapping file in transaction TXN_ID
- * within FS for the given SHA1 checksum. Use POOL for allocations.
- */
-static APR_INLINE const char *
-path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
- apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
- svn_checksum_to_cstring(sha1, pool),
- pool);
-}
-
-static APR_INLINE const char *
-path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
-}
-
-static APR_INLINE const char *
-path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
-}
-
-static APR_INLINE const char *
-path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
-}
-
-static APR_INLINE const char *
-path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
-}
-
-
-static APR_INLINE const char *
-path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_REV,
- (char *)NULL),
- NULL);
- else
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
-}
+ svn_fs_t *fs = baton->fs;
+ fs_fs_data_t *ffd = fs->fsap_data;
-static APR_INLINE const char *
-path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
- (char *)NULL),
- NULL);
- else
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
- pool);
-}
+ if (baton->is_global_lock)
+ {
+ /* set the "got the lock" flag and register reset function */
+ apr_pool_cleanup_register(pool,
+ ffd,
+ reset_lock_flag,
+ apr_pool_cleanup_null);
+ ffd->has_write_lock = TRUE;
+ }
-static const char *
-path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- const char *txn_id = svn_fs_fs__id_txn_id(id);
- const char *node_id = svn_fs_fs__id_node_id(id);
- const char *copy_id = svn_fs_fs__id_copy_id(id);
- const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
- node_id, copy_id);
+ /* nobody else will modify the repo state
+ => read HEAD & pack info once */
+ if (baton->is_inner_most_lock)
+ {
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ err = svn_fs_fs__update_min_unpacked_rev(fs, pool);
+ if (!err)
+ err = get_youngest(&ffd->youngest_rev_cache, fs, pool);
+ }
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
-}
+ if (!err)
+ err = baton->body(baton->baton, pool);
+ }
-static APR_INLINE const char *
-path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
- (char *)NULL);
-}
+ if (baton->is_outer_most_lock)
+ svn_pool_destroy(pool);
-static APR_INLINE const char *
-path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
- PATH_EXT_CHILDREN, (char *)NULL);
+ return svn_error_trace(err);
}
-static APR_INLINE const char *
-path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
-{
- size_t len = strlen(node_id);
- const char *node_id_minus_last_char =
- (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
- return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
- node_id_minus_last_char, NULL);
-}
+/* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
-static APR_INLINE const char *
-path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
+ POOL is unused here and only provided for signature compatibility with
+ WITH_LOCK_BATON_T.BODY. */
+static svn_error_t *
+with_lock(void *baton,
+ apr_pool_t *pool)
{
- const char *path;
- apr_off_t offset = 0;
+ with_lock_baton_t *lock_baton = baton;
+ SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
- if (apr_file_name_get(&path, file) != APR_SUCCESS)
- path = "(unknown)";
-
- if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
- offset = -1;
-
- return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
+ return SVN_NO_ERROR;
}
-
-
-/* Functions for working with shared transaction data. */
-
-/* Return the transaction object for transaction TXN_ID from the
- transaction list of filesystem FS (which must already be locked via the
- txn_list_lock mutex). If the transaction does not exist in the list,
- then create a new transaction object and return it (if CREATE_NEW is
- true) or return NULL (otherwise). */
-static fs_fs_shared_txn_data_t *
-get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
+/* Enum identifying a filesystem lock. */
+typedef enum lock_id_t
{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
- fs_fs_shared_txn_data_t *txn;
-
- for (txn = ffsd->txns; txn; txn = txn->next)
- if (strcmp(txn->txn_id, txn_id) == 0)
- break;
-
- if (txn || !create_new)
- return txn;
-
- /* Use the transaction object from the (single-object) freelist,
- if one is available, or otherwise create a new object. */
- if (ffsd->free_txn)
- {
- txn = ffsd->free_txn;
- ffsd->free_txn = NULL;
- }
- else
- {
- apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
- txn = apr_palloc(subpool, sizeof(*txn));
- txn->pool = subpool;
- }
-
- assert(strlen(txn_id) < sizeof(txn->txn_id));
- apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
- txn->being_written = FALSE;
-
- /* Link this transaction into the head of the list. We will typically
- be dealing with only one active transaction at a time, so it makes
- sense for searches through the transaction list to look at the
- newest transactions first. */
- txn->next = ffsd->txns;
- ffsd->txns = txn;
-
- return txn;
-}
+ write_lock,
+ txn_lock,
+ pack_lock
+} lock_id_t;
-/* Free the transaction object for transaction TXN_ID, and remove it
- from the transaction list of filesystem FS (which must already be
- locked via the txn_list_lock mutex). Do nothing if the transaction
- does not exist. */
+/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
+ according to the LOCK_ID. All other members of BATON must already be
+ valid. */
static void
-free_shared_txn(svn_fs_t *fs, const char *txn_id)
+init_lock_baton(with_lock_baton_t *baton,
+ lock_id_t lock_id)
{
- fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_data_t *ffd = baton->fs->fsap_data;
fs_fs_shared_data_t *ffsd = ffd->shared;
- fs_fs_shared_txn_data_t *txn, *prev = NULL;
- for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
- if (strcmp(txn->txn_id, txn_id) == 0)
+ switch (lock_id)
+ {
+ case write_lock:
+ baton->mutex = ffsd->fs_write_lock;
+ baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool);
+ baton->is_global_lock = TRUE;
break;
- if (!txn)
- return;
-
- if (prev)
- prev->next = txn->next;
- else
- ffsd->txns = txn->next;
+ case txn_lock:
+ baton->mutex = ffsd->txn_current_lock;
+ baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs,
+ baton->lock_pool);
+ baton->is_global_lock = FALSE;
+ break;
- /* As we typically will be dealing with one transaction after another,
- we will maintain a single-object free list so that we can hopefully
- keep reusing the same transaction object. */
- if (!ffsd->free_txn)
- ffsd->free_txn = txn;
- else
- svn_pool_destroy(txn->pool);
+ case pack_lock:
+ baton->mutex = ffsd->fs_pack_lock;
+ baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs,
+ baton->lock_pool);
+ baton->is_global_lock = FALSE;
+ break;
+ }
}
-
-/* Obtain a lock on the transaction list of filesystem FS, call BODY
- with FS, BATON, and POOL, and then unlock the transaction list.
- Return what BODY returned. */
-static svn_error_t *
-with_txnlist_lock(svn_fs_t *fs,
- svn_error_t *(*body)(svn_fs_t *fs,
- const void *baton,
+/* Return the baton for the innermost lock of a (potential) lock chain.
+ The baton shall take out LOCK_ID from FS and execute BODY with BATON
+ while the lock is being held. Allocate the result in a sub-pool of POOL.
+ */
+static with_lock_baton_t *
+create_lock_baton(svn_fs_t *fs,
+ lock_id_t lock_id,
+ svn_error_t *(*body)(void *baton,
apr_pool_t *pool),
- const void *baton,
+ void *baton,
apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
+ /* Allocate everything along the lock chain into a single sub-pool.
+ This minimizes memory usage and cleanup overhead. */
+ apr_pool_t *lock_pool = svn_pool_create(pool);
+ with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
- SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
- body(fs, baton, pool));
+ /* Store parameters. */
+ result->fs = fs;
+ result->body = body;
+ result->baton = baton;
- return SVN_NO_ERROR;
-}
+ /* File locks etc. will use this pool as well for easy cleanup. */
+ result->lock_pool = lock_pool;
+ /* Right now, we are the first, (only, ) and last struct in the chain. */
+ result->is_inner_most_lock = TRUE;
+ result->is_outer_most_lock = TRUE;
-/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
-static svn_error_t *
-get_lock_on_filesystem(const char *lock_filename,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
-
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- /* No lock file? No big deal; these are just empty files
- anyway. Create it and try again. */
- svn_error_clear(err);
- err = NULL;
-
- SVN_ERR(svn_io_file_create(lock_filename, "", pool));
- SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
- }
+ /* Select mutex and lock file path depending on LOCK_ID.
+ Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
+ init_lock_baton(result, lock_id);
- return svn_error_trace(err);
+ return result;
}
-/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
- When registered with the pool holding the lock on the lock file,
- this makes sure the flag gets reset just before we release the lock. */
-static apr_status_t
-reset_lock_flag(void *baton_void)
+/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
+ *
+ * That means, when you create a lock chain, start with the last / innermost
+ * lock to take out and add the first / outermost lock last.
+ */
+static with_lock_baton_t *
+chain_lock_baton(lock_id_t lock_id,
+ with_lock_baton_t *nested)
{
- fs_fs_data_t *ffd = baton_void;
- ffd->has_write_lock = FALSE;
- return APR_SUCCESS;
-}
+ /* Use the same pool for batons along the lock chain. */
+ apr_pool_t *lock_pool = nested->lock_pool;
+ with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
-/* Obtain a write lock on the file LOCK_FILENAME (protecting with
- LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
- BATON and that subpool, destroy the subpool (releasing the write
- lock) and return what BODY returned. If IS_GLOBAL_LOCK is set,
- set the HAS_WRITE_LOCK flag while we keep the write lock. */
-static svn_error_t *
-with_some_lock_file(svn_fs_t *fs,
- svn_error_t *(*body)(void *baton,
- apr_pool_t *pool),
- void *baton,
- const char *lock_filename,
- svn_boolean_t is_global_lock,
- apr_pool_t *pool)
-{
- apr_pool_t *subpool = svn_pool_create(pool);
- svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
+ /* All locks along the chain operate on the same FS. */
+ result->fs = nested->fs;
- if (!err)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* Execution of this baton means acquiring the nested lock and its
+ execution. */
+ result->body = with_lock;
+ result->baton = nested;
- if (is_global_lock)
- {
- /* set the "got the lock" flag and register reset function */
- apr_pool_cleanup_register(subpool,
- ffd,
- reset_lock_flag,
- apr_pool_cleanup_null);
- ffd->has_write_lock = TRUE;
- }
+ /* Shared among all locks along the chain. */
+ result->lock_pool = lock_pool;
- /* nobody else will modify the repo state
- => read HEAD & pack info once */
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(update_min_unpacked_rev(fs, pool));
- SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
- pool));
- err = body(baton, subpool);
- }
+ /* We are the new outermost lock but surely not the innermost lock. */
+ result->is_inner_most_lock = FALSE;
+ result->is_outer_most_lock = TRUE;
+ nested->is_outer_most_lock = FALSE;
- svn_pool_destroy(subpool);
+ /* Select mutex and lock file path depending on LOCK_ID.
+ Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
+ init_lock_baton(result, lock_id);
- return svn_error_trace(err);
+ return result;
}
svn_error_t *
@@ -671,306 +362,63 @@ svn_fs_fs__with_write_lock(svn_fs_t *fs,
void *baton,
apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
-
- SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
- with_some_lock_file(fs, body, baton,
- path_lock(fs, pool),
- TRUE,
- pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Run BODY (with BATON and POOL) while the txn-current file
- of FS is locked. */
-static svn_error_t *
-with_txn_current_lock(svn_fs_t *fs,
- svn_error_t *(*body)(void *baton,
- apr_pool_t *pool),
- void *baton,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
-
- SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
- with_some_lock_file(fs, body, baton,
- path_txn_current_lock(fs, pool),
- FALSE,
- pool));
-
- return SVN_NO_ERROR;
-}
-
-/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
- which see. */
-struct unlock_proto_rev_baton
-{
- const char *txn_id;
- void *lockcookie;
-};
-
-/* Callback used in the implementation of unlock_proto_rev(). */
-static svn_error_t *
-unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
- const struct unlock_proto_rev_baton *b = baton;
- const char *txn_id = b->txn_id;
- apr_file_t *lockfile = b->lockcookie;
- fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
- apr_status_t apr_err;
-
- if (!txn)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't unlock unknown transaction '%s'"),
- txn_id);
- if (!txn->being_written)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't unlock nonlocked transaction '%s'"),
- txn_id);
-
- apr_err = apr_file_unlock(lockfile);
- if (apr_err)
- return svn_error_wrap_apr
- (apr_err,
- _("Can't unlock prototype revision lockfile for transaction '%s'"),
- txn_id);
- apr_err = apr_file_close(lockfile);
- if (apr_err)
- return svn_error_wrap_apr
- (apr_err,
- _("Can't close prototype revision lockfile for transaction '%s'"),
- txn_id);
-
- txn->being_written = FALSE;
-
- return SVN_NO_ERROR;
+ return svn_error_trace(
+ with_lock(create_lock_baton(fs, write_lock, body, baton, pool),
+ pool));
}
-/* Unlock the prototype revision file for transaction TXN_ID in filesystem
- FS using cookie LOCKCOOKIE. The original prototype revision file must
- have been closed _before_ calling this function.
-
- Perform temporary allocations in POOL. */
-static svn_error_t *
-unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
- apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__with_pack_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
{
- struct unlock_proto_rev_baton b;
-
- b.txn_id = txn_id;
- b.lockcookie = lockcookie;
- return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
+ return svn_error_trace(
+ with_lock(create_lock_baton(fs, pack_lock, body, baton, pool),
+ pool));
}
-/* Same as unlock_proto_rev(), but requires that the transaction list
- lock is already held. */
-static svn_error_t *
-unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
- void *lockcookie,
- apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
{
- struct unlock_proto_rev_baton b;
-
- b.txn_id = txn_id;
- b.lockcookie = lockcookie;
- return unlock_proto_rev_body(fs, &b, pool);
+ return svn_error_trace(
+ with_lock(create_lock_baton(fs, txn_lock, body, baton, pool),
+ pool));
}
-/* A structure used by get_writable_proto_rev() and
- get_writable_proto_rev_body(), which see. */
-struct get_writable_proto_rev_baton
-{
- apr_file_t **file;
- void **lockcookie;
- const char *txn_id;
-};
-
-/* Callback used in the implementation of get_writable_proto_rev(). */
-static svn_error_t *
-get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__with_all_locks(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
{
- const struct get_writable_proto_rev_baton *b = baton;
- apr_file_t **file = b->file;
- void **lockcookie = b->lockcookie;
- const char *txn_id = b->txn_id;
- svn_error_t *err;
- fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
-
- /* First, ensure that no thread in this process (including this one)
- is currently writing to this transaction's proto-rev file. */
- if (txn->being_written)
- return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
- _("Cannot write to the prototype revision file "
- "of transaction '%s' because a previous "
- "representation is currently being written by "
- "this process"),
- txn_id);
-
-
- /* We know that no thread in this process is writing to the proto-rev
- file, and by extension, that no thread in this process is holding a
- lock on the prototype revision lock file. It is therefore safe
- for us to attempt to lock this file, to see if any other process
- is holding a lock. */
-
- {
- apr_file_t *lockfile;
- apr_status_t apr_err;
- const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
-
- /* Open the proto-rev lockfile, creating it if necessary, as it may
- not exist if the transaction dates from before the lockfiles were
- introduced.
-
- ### We'd also like to use something like svn_io_file_lock2(), but
- that forces us to create a subpool just to be able to unlock
- the file, which seems a waste. */
- SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
- APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
-
- apr_err = apr_file_lock(lockfile,
- APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
- if (apr_err)
- {
- svn_error_clear(svn_io_file_close(lockfile, pool));
-
- if (APR_STATUS_IS_EAGAIN(apr_err))
- return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
- _("Cannot write to the prototype revision "
- "file of transaction '%s' because a "
- "previous representation is currently "
- "being written by another process"),
- txn_id);
-
- return svn_error_wrap_apr(apr_err,
- _("Can't get exclusive lock on file '%s'"),
- svn_dirent_local_style(lockfile_path, pool));
- }
-
- *lockcookie = lockfile;
- }
-
- /* We've successfully locked the transaction; mark it as such. */
- txn->being_written = TRUE;
-
-
- /* Now open the prototype revision file and seek to the end. */
- err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
- APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
-
- /* You might expect that we could dispense with the following seek
- and achieve the same thing by opening the file using APR_APPEND.
- Unfortunately, APR's buffered file implementation unconditionally
- places its initial file pointer at the start of the file (even for
- files opened with APR_APPEND), so we need this seek to reconcile
- the APR file pointer to the OS file pointer (since we need to be
- able to read the current file position later). */
- if (!err)
- {
- apr_off_t offset = 0;
- err = svn_io_file_seek(*file, APR_END, &offset, pool);
- }
-
- if (err)
- {
- err = svn_error_compose_create(
- err,
- unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
-
- *lockcookie = NULL;
- }
-
- return svn_error_trace(err);
-}
-
-/* Get a handle to the prototype revision file for transaction TXN_ID in
- filesystem FS, and lock it for writing. Return FILE, a file handle
- positioned at the end of the file, and LOCKCOOKIE, a cookie that
- should be passed to unlock_proto_rev() to unlock the file once FILE
- has been closed.
+ fs_fs_data_t *ffd = fs->fsap_data;
- If the prototype revision file is already locked, return error
- SVN_ERR_FS_REP_BEING_WRITTEN.
+ /* Be sure to use the correct lock ordering as documented in
+ fs_fs_shared_data_t. The lock chain is being created in
+ innermost (last to acquire) -> outermost (first to acquire) order. */
+ with_lock_baton_t *lock_baton
+ = create_lock_baton(fs, write_lock, body, baton, pool);
- Perform all allocations in POOL. */
-static svn_error_t *
-get_writable_proto_rev(apr_file_t **file,
- void **lockcookie,
- svn_fs_t *fs, const char *txn_id,
- apr_pool_t *pool)
-{
- struct get_writable_proto_rev_baton b;
+ if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
+ lock_baton = chain_lock_baton(pack_lock, lock_baton);
- b.file = file;
- b.lockcookie = lockcookie;
- b.txn_id = txn_id;
+ if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ lock_baton = chain_lock_baton(txn_lock, lock_baton);
- return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
+ return svn_error_trace(with_lock(lock_baton, pool));
}
-/* Callback used in the implementation of purge_shared_txn(). */
-static svn_error_t *
-purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
- const char *txn_id = baton;
- free_shared_txn(fs, txn_id);
- svn_fs_fs__reset_txn_caches(fs);
-
- return SVN_NO_ERROR;
-}
-
-/* Purge the shared data for transaction TXN_ID in filesystem FS.
- Perform all allocations in POOL. */
-static svn_error_t *
-purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
-}
-/* Fetch the current offset of FILE into *OFFSET_P. */
-static svn_error_t *
-get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
-{
- apr_off_t offset;
-
- /* Note that, for buffered files, one (possibly surprising) side-effect
- of this call is to flush any unwritten data to disk. */
- offset = 0;
- SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
- *offset_p = offset;
-
- return SVN_NO_ERROR;
-}
-
-
-/* Check that BUF, a nul-terminated buffer of text from file PATH,
- contains only digits at OFFSET and beyond, raising an error if not.
- TITLE contains a user-visible description of the file, usually the
- short file name.
-
- Uses POOL for temporary allocation. */
-static svn_error_t *
-check_file_buffer_numeric(const char *buf, apr_off_t offset,
- const char *path, const char *title,
- apr_pool_t *pool)
-{
- const char *p;
-
- for (p = buf + offset; *p; p++)
- if (!svn_ctype_isdigit(*p))
- return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
- _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
- title, svn_dirent_local_style(path, pool), *p, buf);
-
- return SVN_NO_ERROR;
-}
-
/* Check that BUF, a nul-terminated buffer of text from format file PATH,
contains only digits at OFFSET and beyond, raising an error if not.
@@ -979,7 +427,8 @@ static svn_error_t *
check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
const char *path, apr_pool_t *pool)
{
- return check_file_buffer_numeric(buf, offset, path, "Format", pool);
+ return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
+ pool);
}
/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
@@ -1008,16 +457,21 @@ check_format(int format)
}
/* Read the format number and maximum number of files per directory
- from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
- respectively.
+ from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and
+ USE_LOG_ADDRESSIONG respectively.
*MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
will be set to zero if a linear scheme should be used.
+ *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option,
+ and will be set to FALSE for physical addressing.
Use POOL for temporary allocation. */
static svn_error_t *
-read_format(int *pformat, int *max_files_per_dir,
- const char *path, apr_pool_t *pool)
+read_format(int *pformat,
+ int *max_files_per_dir,
+ svn_boolean_t *use_log_addressing,
+ const char *path,
+ apr_pool_t *pool)
{
svn_error_t *err;
svn_stream_t *stream;
@@ -1038,6 +492,7 @@ read_format(int *pformat, int *max_files_per_dir,
svn_error_clear(err);
*pformat = 1;
*max_files_per_dir = 0;
+ *use_log_addressing = FALSE;
return SVN_NO_ERROR;
}
@@ -1062,6 +517,7 @@ read_format(int *pformat, int *max_files_per_dir,
/* Set the default values for anything that can be set via an option. */
*max_files_per_dir = 0;
+ *use_log_addressing = FALSE;
/* Read any options. */
while (!eos)
@@ -1088,38 +544,75 @@ read_format(int *pformat, int *max_files_per_dir,
}
}
+ if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT &&
+ strncmp(buf->data, "addressing ", 11) == 0)
+ {
+ if (strcmp(buf->data + 11, "physical") == 0)
+ {
+ *use_log_addressing = FALSE;
+ continue;
+ }
+
+ if (strcmp(buf->data + 11, "logical") == 0)
+ {
+ *use_log_addressing = TRUE;
+ continue;
+ }
+ }
+
return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
_("'%s' contains invalid filesystem format option '%s'"),
svn_dirent_local_style(path, pool), buf->data);
}
+ /* Non-sharded repositories never use logical addressing.
+ * If the format file is inconsistent in that respect, something
+ * probably went wrong.
+ */
+ if (*use_log_addressing && !*max_files_per_dir)
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("'%s' specifies logical addressing for a non-sharded repository"),
+ svn_dirent_local_style(path, pool));
+
return SVN_NO_ERROR;
}
-/* Write the format number and maximum number of files per directory
- to a new format file in PATH, possibly expecting to overwrite a
- previously existing file.
+/* Write the format number, maximum number of files per directory and
+ the addressing scheme to a new format file in PATH, possibly expecting
+ to overwrite a previously existing file.
Use POOL for temporary allocation. */
-static svn_error_t *
-write_format(const char *path, int format, int max_files_per_dir,
- svn_boolean_t overwrite, apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__write_format(svn_fs_t *fs,
+ svn_boolean_t overwrite,
+ apr_pool_t *pool)
{
svn_stringbuf_t *sb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *path = path_format(fs, pool);
- SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
+ SVN_ERR_ASSERT(1 <= ffd->format
+ && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
- sb = svn_stringbuf_createf(pool, "%d\n", format);
+ sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
- if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+ if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
{
- if (max_files_per_dir)
+ if (ffd->max_files_per_dir)
svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
- max_files_per_dir));
+ ffd->max_files_per_dir));
else
svn_stringbuf_appendcstr(sb, "layout linear\n");
}
+ if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
+ {
+ if (ffd->use_log_addressing)
+ svn_stringbuf_appendcstr(sb, "addressing logical\n");
+ else
+ svn_stringbuf_appendcstr(sb, "addressing physical\n");
+ }
+
/* svn_io_write_version_file() does a load of magic to allow it to
replace version files that already exist. We only need to do
that when we're allowed to overwrite an existing file. */
@@ -1130,15 +623,8 @@ write_format(const char *path, int format, int max_files_per_dir,
}
else
{
- const char *path_tmp;
-
- SVN_ERR(svn_io_write_unique(&path_tmp,
- svn_dirent_dirname(path, pool),
- sb->data, sb->len,
- svn_io_file_del_none, pool));
-
- /* rename the temp file as the real destination */
- SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
+ SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
+ NULL /* copy_perms_path */, pool));
}
/* And set the perms to make it read only */
@@ -1152,21 +638,68 @@ svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
}
+/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
+ * the range of what the current system may address in RAM and it is a
+ * power of 2. Assume that the element size within the block is ITEM_SIZE.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+verify_block_size(apr_int64_t block_size,
+ apr_size_t item_size,
+ const char *name,
+ apr_pool_t *scratch_pool
+ )
+{
+ /* Limit range. */
+ if (block_size <= 0)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is too small for fsfs.conf setting '%s'."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is too large for fsfs.conf setting '%s'."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ /* Ensure it is a power of two.
+ * For positive X, X & (X-1) will reset the lowest bit set.
+ * If the result is 0, at most one bit has been set. */
+ if (0 != (block_size & (block_size - 1)))
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is invalid for fsfs.conf setting '%s' "
+ "because it is not a power of 2."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ return SVN_NO_ERROR;
+}
+
/* Read the configuration information of the file system at FS_PATH
- * and set the respective values in FFD. Use POOL for allocations.
+ * and set the respective values in FFD. Use pools as usual.
*/
static svn_error_t *
read_config(fs_fs_data_t *ffd,
const char *fs_path,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- SVN_ERR(svn_config_read3(&ffd->config,
- svn_dirent_join(fs_path, PATH_CONFIG, pool),
- FALSE, FALSE, FALSE, pool));
+ svn_config_t *config;
+
+ SVN_ERR(svn_config_read3(&config,
+ svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
+ FALSE, FALSE, FALSE, scratch_pool));
/* Initialize ffd->rep_sharing_allowed. */
if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
+ SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
CONFIG_SECTION_REP_SHARING,
CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
else
@@ -1175,22 +708,32 @@ read_config(fs_fs_data_t *ffd,
/* Initialize deltification settings in ffd. */
if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
{
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
+ apr_int64_t compression_level;
+
+ SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
- FALSE));
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
+ TRUE));
+ SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
- FALSE));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
+ TRUE));
+ SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_MAX_DELTIFICATION_WALK,
SVN_FS_FS_MAX_DELTIFICATION_WALK));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
+ SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
+
+ SVN_ERR(svn_config_get_int64(config, &compression_level,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_COMPRESSION_LEVEL,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
+ ffd->delta_compression_level
+ = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
+ SVN_DELTA_COMPRESSION_LEVEL_MAX);
}
else
{
@@ -1198,21 +741,22 @@ read_config(fs_fs_data_t *ffd,
ffd->deltify_properties = FALSE;
ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
+ ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
}
/* Initialize revprop packing settings in ffd. */
if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
{
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
+ SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
CONFIG_SECTION_PACKED_REVPROPS,
CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
FALSE));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
+ SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
CONFIG_SECTION_PACKED_REVPROPS,
CONFIG_OPTION_REVPROP_PACK_SIZE,
ffd->compress_packed_revprops
- ? 0x100
- : 0x40));
+ ? 0x10
+ : 0x4));
ffd->revprop_pack_size *= 1024;
}
@@ -1222,6 +766,64 @@ read_config(fs_fs_data_t *ffd,
ffd->compress_packed_revprops = FALSE;
}
+ if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
+ {
+ SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_BLOCK_SIZE,
+ 64));
+ SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_L2P_PAGE_SIZE,
+ 0x2000));
+ SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_P2L_PAGE_SIZE,
+ 0x400));
+
+ /* Don't accept unreasonable or illegal values.
+ * Block size and P2L page size are in kbytes;
+ * L2P blocks are arrays of apr_off_t. */
+ SVN_ERR(verify_block_size(ffd->block_size, 0x400,
+ CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
+ SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
+ CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
+ SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
+ CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
+
+ /* convert kBytes to bytes */
+ ffd->block_size *= 0x400;
+ ffd->p2l_page_size *= 0x400;
+ /* L2P pages are in entries - not in (k)Bytes */
+ }
+ else
+ {
+ /* should be irrelevant but we initialize them anyway */
+ ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
+ ffd->l2p_page_size = 0x2000; /* Matches above default. */
+ ffd->p2l_page_size = 0x100000; /* Matches above default in bytes. */
+ }
+
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
+ CONFIG_SECTION_DEBUG,
+ CONFIG_OPTION_PACK_AFTER_COMMIT,
+ FALSE));
+ }
+ else
+ {
+ ffd->pack_after_commit = FALSE;
+ }
+
+ /* memcached configuration */
+ SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
+ CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
+ FALSE));
+
return SVN_NO_ERROR;
}
@@ -1286,20 +888,20 @@ write_config(svn_fs_t *fs,
"###" NL
"### The following parameter enables deltification for directories. It can" NL
"### be switched on and off at will, but for best space-saving results" NL
-"### should be enabled consistently over the life of the repository." NL
+"### should be enabled consistently over the lifetime of the repository." NL
"### Repositories containing large directories will benefit greatly." NL
-"### In rarely read repositories, the I/O overhead may be significant as" NL
-"### cache hit rates will most likely be low" NL
-"### directory deltification is disabled by default." NL
-"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false" NL
+"### In rarely accessed repositories, the I/O overhead may be significant" NL
+"### as caches will most likely be low." NL
+"### directory deltification is enabled by default." NL
+"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true" NL
"###" NL
"### The following parameter enables deltification for properties on files" NL
"### and directories. Overall, this is a minor tuning option but can save" NL
"### some disk space if you merge frequently or frequently change node" NL
"### properties. You should not activate this if rep-sharing has been" NL
"### disabled because this may result in a net increase in repository size." NL
-"### property deltification is disabled by default." NL
-"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false" NL
+"### property deltification is enabled by default." NL
+"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true" NL
"###" NL
"### During commit, the server may need to walk the whole change history of" NL
"### of a given node to find a suitable deltification base. This linear" NL
@@ -1331,6 +933,24 @@ write_config(svn_fs_t *fs,
"### exclusive use of skip-deltas (as in pre-1.8)." NL
"### For 1.8, the default value is 16; earlier versions use 1." NL
"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
+"###" NL
+"### After deltification, we compress the data through zlib to minimize on-" NL
+"### disk size. That can be an expensive and ineffective process. This" NL
+"### setting controls the usage of zlib in future revisions." NL
+"### Revisions with highly compressible data in them may shrink in size" NL
+"### if the setting is increased but may take much longer to commit. The" NL
+"### time taken to uncompress that data again is widely independent of the" NL
+"### compression level." NL
+"### Compression will be ineffective if the incoming content is already" NL
+"### highly compressed. In that case, disabling the compression entirely" NL
+"### will speed up commits as well as reading the data. Repositories with" NL
+"### many small compressible files (source code) but also a high percentage" NL
+"### of large incompressible ones (artwork) may benefit from compression" NL
+"### levels lowered to e.g. 1." NL
+"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
+"### and 0 disabling it altogether." NL
+"### The default value is 5." NL
+"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5" NL
"" NL
"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
"### This parameter controls the size (in kBytes) of packed revprop files." NL
@@ -1340,96 +960,174 @@ write_config(svn_fs_t *fs,
"### much larger than the limit set here. The threshold will be applied" NL
"### before optional compression takes place." NL
"### Large values will reduce disk space usage at the expense of increased" NL
-"### latency and CPU usage reading and changing individual revprops. They" NL
-"### become an advantage when revprop caching has been enabled because a" NL
-"### lot of data can be read in one go. Values smaller than 4 kByte will" NL
-"### not improve latency any further and quickly render revprop packing" NL
-"### ineffective." NL
-"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL
-"### pack files and 256 kBytes when compression has been enabled." NL
-"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL
+"### latency and CPU usage reading and changing individual revprops." NL
+"### Values smaller than 4 kByte will not improve latency any further and " NL
+"### quickly render revprop packing ineffective." NL
+"### revprop-pack-size is 4 kBytes by default for non-compressed revprop" NL
+"### pack files and 16 kBytes when compression has been enabled." NL
+"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4" NL
"###" NL
"### To save disk space, packed revprop files may be compressed. Standard" NL
"### revprops tend to allow for very effective compression. Reading and" NL
-"### even more so writing, become significantly more CPU intensive. With" NL
-"### revprop caching enabled, the overhead can be offset by reduced I/O" NL
-"### unless you often modify revprops after packing." NL
+"### even more so writing, become significantly more CPU intensive." NL
"### Compressing packed revprops is disabled by default." NL
"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL
+"" NL
+"[" CONFIG_SECTION_IO "]" NL
+"### Parameters in this section control the data access granularity in" NL
+"### format 7 repositories and later. The defaults should translate into" NL
+"### decent performance over a wide range of setups." NL
+"###" NL
+"### When a specific piece of information needs to be read from disk, a" NL
+"### data block is being read at once and its contents are being cached." NL
+"### If the repository is being stored on a RAID, the block size should be" NL
+"### either 50% or 100% of RAID block size / granularity. Also, your file" NL
+"### system blocks/clusters should be properly aligned and sized. In that" NL
+"### setup, each access will hit only one disk (minimizes I/O load) but" NL
+"### uses all the data provided by the disk in a single access." NL
+"### For SSD-based storage systems, slightly lower values around 16 kB" NL
+"### may improve latency while still maximizing throughput. If block-read" NL
+"### has not been enabled, this will be capped to 4 kBytes." NL
+"### Can be changed at any time but must be a power of 2." NL
+"### block-size is given in kBytes and with a default of 64 kBytes." NL
+"# " CONFIG_OPTION_BLOCK_SIZE " = 64" NL
+"###" NL
+"### The log-to-phys index maps data item numbers to offsets within the" NL
+"### rev or pack file. This index is organized in pages of a fixed maximum" NL
+"### capacity. To access an item, the page table and the respective page" NL
+"### must be read." NL
+"### This parameter only affects revisions with thousands of changed paths." NL
+"### If you have several extremely large revisions (~1 mio changes), think" NL
+"### about increasing this setting. Reducing the value will rarely result" NL
+"### in a net speedup." NL
+"### This is an expert setting. Must be a power of 2." NL
+"### l2p-page-size is 8192 entries by default." NL
+"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192" NL
+"###" NL
+"### The phys-to-log index maps positions within the rev or pack file to" NL
+"### to data items, i.e. describes what piece of information is being" NL
+"### stored at any particular offset. The index describes the rev file" NL
+"### in chunks (pages) and keeps a global list of all those pages. Large" NL
+"### pages mean a shorter page table but a larger per-page description of" NL
+"### data items in it. The latency sweetspot depends on the change size" NL
+"### distribution but covers a relatively wide range." NL
+"### If the repository contains very large files, i.e. individual changes" NL
+"### of tens of MB each, increasing the page size will shorten the index" NL
+"### file at the expense of a slightly increased latency in sections with" NL
+"### smaller changes." NL
+"### For source code repositories, this should be about 16x the block-size." NL
+"### Must be a power of 2." NL
+"### p2l-page-size is given in kBytes and with a default of 1024 kBytes." NL
+"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024" NL
;
#undef NL
return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
fsfs_conf_contents, pool);
}
+/* Read / Evaluate the global configuration in FS->CONFIG to set up
+ * parameters in FS. */
static svn_error_t *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
- const char *path,
- apr_pool_t *pool)
+read_global_config(svn_fs_t *fs)
{
- char buf[80];
- apr_file_t *file;
- apr_size_t len;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Providing a config hash is optional. */
+ if (fs->config)
+ ffd->use_block_read = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_BLOCK_READ,
+ FALSE);
+ else
+ ffd->use_block_read = FALSE;
- SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
- APR_OS_DEFAULT, pool));
- len = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
- SVN_ERR(svn_io_file_close(file, pool));
+ /* Ignore the user-specified larger block size if we don't use block-read.
+ Defaulting to 4k gives us the same access granularity in format 7 as in
+ older formats. */
+ if (!ffd->use_block_read)
+ ffd->block_size = MIN(0x1000, ffd->block_size);
- *min_unpacked_rev = SVN_STR_TO_REV(buf);
return SVN_NO_ERROR;
}
+/* Read FS's UUID file and store the data in the FS struct. */
static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
+read_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *uuid_file;
+ char buf[APR_UUID_FORMATTED_LENGTH + 2];
+ apr_size_t limit;
+
+ /* Read the repository uuid. */
+ SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ scratch_pool));
+
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
+ fs->uuid = apr_pstrdup(fs->pool, buf);
- SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
+ /* Read the instance ID. */
+ if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
+ {
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
+ scratch_pool));
+ ffd->instance_id = apr_pstrdup(fs->pool, buf);
+ }
+ else
+ {
+ ffd->instance_id = fs->uuid;
+ }
- return read_min_unpacked_rev(&ffd->min_unpacked_rev,
- path_min_unpacked_rev(fs, pool),
- pool);
+ SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
+
+ return SVN_NO_ERROR;
}
svn_error_t *
-svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
+svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
- apr_file_t *uuid_file;
int format, max_files_per_dir;
- char buf[APR_UUID_FORMATTED_LENGTH + 2];
- apr_size_t limit;
+ svn_boolean_t use_log_addressing;
- fs->path = apr_pstrdup(fs->pool, path);
+ /* Read info from format file. */
+ SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
+ path_format(fs, scratch_pool), scratch_pool));
- /* Read the FS format number. */
- SVN_ERR(read_format(&format, &max_files_per_dir,
- path_format(fs, pool), pool));
-
- /* Now we've got a format number no matter what. */
+ /* Now that we've got *all* info, store / update values in FFD. */
ffd->format = format;
ffd->max_files_per_dir = max_files_per_dir;
+ ffd->use_log_addressing = use_log_addressing;
- /* Read in and cache the repository uuid. */
- SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+ return SVN_NO_ERROR;
+}
- limit = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
- fs->uuid = apr_pstrdup(fs->pool, buf);
+svn_error_t *
+svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ /* Read the FS format file. */
+ SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
- SVN_ERR(svn_io_file_close(uuid_file, pool));
+ /* Read in and cache the repository uuid. */
+ SVN_ERR(read_uuid(fs, pool));
/* Read the min unpacked revision. */
if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(update_min_unpacked_rev(fs, pool));
+ SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
/* Read the configuration file. */
- SVN_ERR(read_config(ffd, fs->path, pool));
+ SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
- return get_youngest(&(ffd->youngest_rev_cache), path, pool);
+ /* Global configuration options. */
+ SVN_ERR(read_global_config(fs));
+
+ return get_youngest(&(ffd->youngest_rev_cache), fs, pool);
}
/* Wrapper around svn_io_file_create which ignores EEXIST. */
@@ -1447,125 +1145,32 @@ create_file_ignore_eexist(const char *file,
return svn_error_trace(err);
}
-/* forward declarations */
-
-static svn_error_t *
-pack_revprops_shard(const char *pack_file_dir,
- const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- apr_off_t max_pack_size,
- int compression_level,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool);
-
-static svn_error_t *
-delete_revprops_shard(const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool);
-
-/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
- *
- * NOTE: Keep the old non-packed shards around until after the format bump.
- * Otherwise, re-running upgrade will drop the packed revprop shard but
- * have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after
- * the bump.
- *
- * Use SCRATCH_POOL for temporary allocations.
- */
-static svn_error_t *
-upgrade_pack_revprops(svn_fs_t *fs,
- apr_pool_t *scratch_pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- const char *revprops_shard_path;
- const char *revprops_pack_file_dir;
- apr_int64_t shard;
- apr_int64_t first_unpacked_shard
- = ffd->min_unpacked_rev / ffd->max_files_per_dir;
-
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
- scratch_pool);
- int compression_level = ffd->compress_packed_revprops
- ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
- : SVN_DELTA_COMPRESSION_LEVEL_NONE;
-
- /* first, pack all revprops shards to match the packed revision shards */
- for (shard = 0; shard < first_unpacked_shard; ++shard)
- {
- revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool,
- "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
- shard),
- iterpool);
- revprops_shard_path = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
- iterpool);
-
- SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
- shard, ffd->max_files_per_dir,
- (int)(0.9 * ffd->revprop_pack_size),
- compression_level,
- NULL, NULL, iterpool));
- svn_pool_clear(iterpool);
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* In the filesystem FS, remove all non-packed revprop shards up to
- * min_unpacked_rev. Use SCRATCH_POOL for temporary allocations.
- * See upgrade_pack_revprops for more info.
- */
-static svn_error_t *
-upgrade_cleanup_pack_revprops(svn_fs_t *fs,
- apr_pool_t *scratch_pool)
+/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
+ * parameters over between them. */
+struct upgrade_baton_t
{
- fs_fs_data_t *ffd = fs->fsap_data;
- const char *revprops_shard_path;
- apr_int64_t shard;
- apr_int64_t first_unpacked_shard
- = ffd->min_unpacked_rev / ffd->max_files_per_dir;
-
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
- scratch_pool);
-
- /* delete the non-packed revprops shards afterwards */
- for (shard = 0; shard < first_unpacked_shard; ++shard)
- {
- revprops_shard_path = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
- iterpool);
- SVN_ERR(delete_revprops_shard(revprops_shard_path,
- shard, ffd->max_files_per_dir,
- NULL, NULL, iterpool));
- svn_pool_clear(iterpool);
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
+ svn_fs_t *fs;
+ svn_fs_upgrade_notify_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
static svn_error_t *
upgrade_body(void *baton, apr_pool_t *pool)
{
- svn_fs_t *fs = baton;
+ struct upgrade_baton_t *upgrade_baton = baton;
+ svn_fs_t *fs = upgrade_baton->fs;
+ fs_fs_data_t *ffd = fs->fsap_data;
int format, max_files_per_dir;
+ svn_boolean_t use_log_addressing;
const char *format_path = path_format(fs, pool);
svn_node_kind_t kind;
svn_boolean_t needs_revprop_shard_cleanup = FALSE;
/* Read the FS format number and max-files-per-dir setting. */
- SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
+ SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
+ format_path, pool));
/* If the config file does not exist, create one. */
SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
@@ -1589,29 +1194,30 @@ upgrade_body(void *baton, apr_pool_t *pool)
if (format == SVN_FS_FS__FORMAT_NUMBER)
return SVN_NO_ERROR;
- /* If our filesystem predates the existance of the 'txn-current
+ /* If our filesystem predates the existence of the 'txn-current
file', make that file and its corresponding lock file. */
if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
{
- SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
- pool));
- SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
- pool));
+ SVN_ERR(create_file_ignore_eexist(
+ svn_fs_fs__path_txn_current(fs, pool), "0\n",
+ pool));
+ SVN_ERR(create_file_ignore_eexist(
+ svn_fs_fs__path_txn_current_lock(fs, pool), "",
+ pool));
}
- /* If our filesystem predates the existance of the 'txn-protorevs'
+ /* If our filesystem predates the existence of the 'txn-protorevs'
dir, make that directory. */
if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
{
- /* We don't use path_txn_proto_rev() here because it expects
- we've already bumped our format. */
SVN_ERR(svn_io_make_dir_recursively(
- svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
+ svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
}
/* If our filesystem is new enough, write the min unpacked rev file. */
if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
+ SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
+ "0\n", pool));
/* If the file system supports revision packing but not revprop packing
*and* the FS has been sharded, pack the revprops up to the point that
@@ -1622,16 +1228,46 @@ upgrade_body(void *baton, apr_pool_t *pool)
&& max_files_per_dir > 0)
{
needs_revprop_shard_cleanup = TRUE;
- SVN_ERR(upgrade_pack_revprops(fs, pool));
- }
+ SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
+ upgrade_baton->notify_func,
+ upgrade_baton->notify_baton,
+ upgrade_baton->cancel_func,
+ upgrade_baton->cancel_baton,
+ pool));
+ }
+
+ /* We will need the UUID info shortly ...
+ Read it before the format bump as the UUID file still uses the old
+ format. */
+ SVN_ERR(read_uuid(fs, pool));
+
+ /* Update the format info in the FS struct. Upgrade steps further
+ down will use the format from FS to create missing info. */
+ ffd->format = SVN_FS_FS__FORMAT_NUMBER;
+ ffd->max_files_per_dir = max_files_per_dir;
+ ffd->use_log_addressing = use_log_addressing;
+
+ /* Always add / bump the instance ID such that no form of caching
+ accidentally uses outdated information. Keep the UUID. */
+ SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
/* Bump the format file. */
- SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER,
- max_files_per_dir, TRUE, pool));
+ SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
+
+ if (upgrade_baton->notify_func)
+ SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
+ SVN_FS_FS__FORMAT_NUMBER,
+ svn_fs_upgrade_format_bumped,
+ pool));
/* Now, it is safe to remove the redundant revprop files. */
if (needs_revprop_shard_cleanup)
- SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool));
+ SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
+ upgrade_baton->notify_func,
+ upgrade_baton->notify_baton,
+ upgrade_baton->cancel_func,
+ upgrade_baton->cancel_baton,
+ pool));
/* Done */
return SVN_NO_ERROR;
@@ -1639,121 +1275,21 @@ upgrade_body(void *baton, apr_pool_t *pool)
svn_error_t *
-svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
-}
-
-
-/* Functions for dealing with recoverable errors on mutable files
- *
- * Revprops, current, and txn-current files are mutable; that is, they
- * change as part of normal fsfs operation, in constrat to revs files, or
- * the format file, which are written once at create (or upgrade) time.
- * When more than one host writes to the same repository, we will
- * sometimes see these recoverable errors when accesssing these files.
- *
- * These errors all relate to NFS, and thus we only use this retry code if
- * ESTALE is defined.
- *
- ** ESTALE
- *
- * In NFS v3 and under, the server doesn't track opened files. If you
- * unlink(2) or rename(2) a file held open by another process *on the
- * same host*, that host's kernel typically renames the file to
- * .nfsXXXX and automatically deletes that when it's no longer open,
- * but this behavior is not required.
- *
- * For obvious reasons, this does not work *across hosts*. No one
- * knows about the opened file; not the server, and not the deleting
- * client. So the file vanishes, and the reader gets stale NFS file
- * handle.
- *
- ** EIO, ENOENT
- *
- * Some client implementations (at least the 2.6.18.5 kernel that ships
- * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
- * even EIO errors when trying to read these files that have been renamed
- * over on some other host.
- *
- ** Solution
- *
- * Try open and read of such files in try_stringbuf_from_file(). Call
- * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
- * (though, realistically, the second try will succeed).
- */
-
-#define RECOVERABLE_RETRY_COUNT 10
-
-/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
- * not be modified unless the whole file was read successfully.
- *
- * ESTALE, EIO and ENOENT will not cause this function to return an error
- * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate
- * missing files (ENOENT) there.
- *
- * Use POOL for allocations.
- */
-static svn_error_t *
-try_stringbuf_from_file(svn_stringbuf_t **content,
- svn_boolean_t *missing,
- const char *path,
- svn_boolean_t last_attempt,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
- if (missing)
- *missing = FALSE;
-
- if (err)
- {
- *content = NULL;
-
- if (APR_STATUS_IS_ENOENT(err->apr_err))
- {
- if (!last_attempt)
- {
- svn_error_clear(err);
- if (missing)
- *missing = TRUE;
- return SVN_NO_ERROR;
- }
- }
-#ifdef ESTALE
- else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
- || APR_TO_OS_ERROR(err->apr_err) == EIO)
- {
- if (!last_attempt)
- {
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- }
-#endif
- }
-
- return svn_error_trace(err);
-}
-
-/* Read the 'current' file FNAME and store the contents in *BUF.
- Allocations are performed in POOL. */
-static svn_error_t *
-read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
+svn_fs_fs__upgrade(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
{
- int i;
- *content = NULL;
+ struct upgrade_baton_t baton;
+ baton.fs = fs;
+ baton.notify_func = notify_func;
+ baton.notify_baton = notify_baton;
+ baton.cancel_func = cancel_func;
+ baton.cancel_baton = cancel_baton;
- for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
- SVN_ERR(try_stringbuf_from_file(content, NULL,
- fname, i + 1 < RECOVERABLE_RETRY_COUNT,
- pool));
-
- if (!*content)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't read '%s'"),
- svn_dirent_local_style(fname, pool));
-
- return SVN_NO_ERROR;
+ return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
}
/* Find the youngest revision in a repository at path FS_PATH and
@@ -1761,15 +1297,11 @@ read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
POOL. */
static svn_error_t *
get_youngest(svn_revnum_t *youngest_p,
- const char *fs_path,
+ svn_fs_t *fs,
apr_pool_t *pool)
{
- svn_stringbuf_t *buf;
- SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
- pool));
-
- *youngest_p = SVN_STR_TO_REV(buf->data);
-
+ apr_uint64_t dummy;
+ SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
return SVN_NO_ERROR;
}
@@ -1781,88 +1313,37 @@ svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
{
fs_fs_data_t *ffd = fs->fsap_data;
- SVN_ERR(get_youngest(youngest_p, fs->path, pool));
+ SVN_ERR(get_youngest(youngest_p, fs, pool));
ffd->youngest_rev_cache = *youngest_p;
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)
+int
+svn_fs_fs__shard_size(svn_fs_t *fs)
{
- *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;
+ fs_fs_data_t *ffd = fs->fsap_data;
- 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);
- }
+ return ffd->max_files_per_dir;
+}
- value = header_str->data + i;
+svn_error_t *
+svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
- /* header_str is safely in our pool, so we can use bits of it as
- key and value. */
- svn_hash_sets(*headers, name, value);
- }
+ SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
+ *min_unpacked = ffd->min_unpacked_rev;
return SVN_NO_ERROR;
}
-/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
- than the current youngest revision or is simply not a valid
- revision number, else return success.
-
- FSFS is based around the concept that commits only take effect when
- the number in "current" is bumped. Thus if there happens to be a rev
- or revprops file installed for a revision higher than the one recorded
- in "current" (because a commit failed between installing the rev file
- and bumping "current", or because an administrator rolled back the
- repository by resetting "current" without deleting rev files, etc), it
- ought to be completely ignored. This function provides the check
- by which callers can make that decision. */
-static svn_error_t *
-ensure_revision_exists(svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
@@ -1876,7 +1357,7 @@ ensure_revision_exists(svn_fs_t *fs,
if (rev <= ffd->youngest_rev_cache)
return SVN_NO_ERROR;
- SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
+ SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
/* Check again. */
if (rev <= ffd->youngest_rev_cache)
@@ -1887,3931 +1368,53 @@ ensure_revision_exists(svn_fs_t *fs,
}
svn_error_t *
-svn_fs_fs__revision_exists(svn_revnum_t rev,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- /* Different order of parameters. */
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
- return SVN_NO_ERROR;
-}
-
-/* Open the correct revision file for REV. If the filesystem FS has
- been packed, *FILE will be set to the packed file; otherwise, set *FILE
- to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the
- file doesn't exist.
-
- TODO: Consider returning an indication of whether this is a packed rev
- file, so the caller need not rely on is_packed_rev() which in turn
- relies on the cached FFD->min_unpacked_rev value not having changed
- since the rev file was opened.
-
- Use POOL for allocations. */
-static svn_error_t *
-open_pack_or_rev_file(apr_file_t **file,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_error_t *err;
- const char *path;
- svn_boolean_t retry = FALSE;
-
- do
- {
- err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
-
- /* open the revision file in buffered r/o mode */
- if (! err)
- err = svn_io_file_open(file, path,
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
-
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- {
- /* Could not open the file. This may happen if the
- * file once existed but got packed later. */
- svn_error_clear(err);
-
- /* if that was our 2nd attempt, leave it at that. */
- if (retry)
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such revision %ld"), rev);
-
- /* We failed for the first time. Refresh cache & retry. */
- SVN_ERR(update_min_unpacked_rev(fs, pool));
-
- retry = TRUE;
- }
- else
- {
- svn_error_clear(err);
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such revision %ld"), rev);
- }
- }
- else
- {
- retry = FALSE;
- }
- }
- while (retry);
-
- return svn_error_trace(err);
-}
-
-/* Reads a line from STREAM and converts it to a 64 bit integer to be
- * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
- * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
- * error return.
- * SCRATCH_POOL is used for temporary allocations.
- */
-static svn_error_t *
-read_number_from_stream(apr_int64_t *result,
- svn_boolean_t *hit_eof,
- svn_stream_t *stream,
- apr_pool_t *scratch_pool)
-{
- svn_stringbuf_t *sb;
- svn_boolean_t eof;
- svn_error_t *err;
-
- SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
- if (hit_eof)
- *hit_eof = eof;
- else
- if (eof)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
-
- if (!eof)
- {
- err = svn_cstring_atoi64(result, sb->data);
- if (err)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
- _("Number '%s' invalid or too large"),
- sb->data);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
- Use POOL for temporary allocations. */
-static svn_error_t *
-get_packed_offset(apr_off_t *rev_offset,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_stream_t *manifest_stream;
- svn_boolean_t is_cached;
- svn_revnum_t shard;
- apr_int64_t shard_pos;
- apr_array_header_t *manifest;
- apr_pool_t *iterpool;
-
- shard = rev / ffd->max_files_per_dir;
-
- /* position of the shard within the manifest */
- shard_pos = rev % ffd->max_files_per_dir;
-
- /* fetch exactly that element into *rev_offset, if the manifest is found
- in the cache */
- SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
- ffd->packed_offset_cache, &shard,
- svn_fs_fs__get_sharded_offset, &shard_pos,
- pool));
-
- if (is_cached)
- return SVN_NO_ERROR;
-
- /* Open the manifest file. */
- SVN_ERR(svn_stream_open_readonly(&manifest_stream,
- path_rev_packed(fs, rev, PATH_MANIFEST,
- pool),
- pool, pool));
-
- /* While we're here, let's just read the entire manifest file into an array,
- so we can cache the entire thing. */
- iterpool = svn_pool_create(pool);
- manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
- while (1)
- {
- svn_boolean_t eof;
- apr_int64_t val;
-
- svn_pool_clear(iterpool);
- SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
- if (eof)
- break;
-
- APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
- }
- svn_pool_destroy(iterpool);
-
- *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
- apr_off_t);
-
- /* Close up shop and cache the array. */
- SVN_ERR(svn_stream_close(manifest_stream));
- return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
-}
-
-/* Open the revision file for revision REV in filesystem FS and store
- the newly opened file in FILE. Seek to location OFFSET before
- returning. Perform temporary allocations in POOL. */
-static svn_error_t *
-open_and_seek_revision(apr_file_t **file,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_off_t offset,
- apr_pool_t *pool)
-{
- apr_file_t *rev_file;
-
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
- SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
-
- if (is_packed_rev(fs, rev))
- {
- apr_off_t rev_offset;
-
- SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
- offset += rev_offset;
- }
-
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
- *file = rev_file;
-
- return SVN_NO_ERROR;
-}
-
-/* Open the representation for a node-revision in transaction TXN_ID
- in filesystem FS and store the newly opened file in FILE. Seek to
- location OFFSET before returning. Perform temporary allocations in
- POOL. Only appropriate for file contents, nor props or directory
- contents. */
-static svn_error_t *
-open_and_seek_transaction(apr_file_t **file,
- svn_fs_t *fs,
- const char *txn_id,
- representation_t *rep,
- apr_pool_t *pool)
-{
- apr_file_t *rev_file;
- apr_off_t offset;
-
- SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- offset = rep->offset;
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
- *file = rev_file;
-
- return SVN_NO_ERROR;
-}
-
-/* Given a node-id ID, and a representation REP in filesystem FS, open
- the correct file and seek to the correction location. Store this
- file in *FILE_P. Perform any allocations in POOL. */
-static svn_error_t *
-open_and_seek_representation(apr_file_t **file_p,
- svn_fs_t *fs,
- representation_t *rep,
- apr_pool_t *pool)
-{
- if (! rep->txn_id)
- return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
- pool);
- else
- return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
-}
-
-/* Parse the description of a representation from STRING and store it
- into *REP_P. If the representation is mutable (the revision is
- given as -1), then use TXN_ID for the representation's txn_id
- field. If MUTABLE_REP_TRUNCATED is true, then this representation
- is for property or directory contents, and no information will be
- expected except the "-1" revision number for a mutable
- representation. Allocate *REP_P in POOL. */
-static svn_error_t *
-read_rep_offsets_body(representation_t **rep_p,
- char *string,
- const char *txn_id,
- svn_boolean_t mutable_rep_truncated,
- apr_pool_t *pool)
-{
- representation_t *rep;
- char *str;
- apr_int64_t val;
-
- rep = apr_pcalloc(pool, sizeof(*rep));
- *rep_p = rep;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
-
- rep->revision = SVN_STR_TO_REV(str);
- if (rep->revision == SVN_INVALID_REVNUM)
- {
- rep->txn_id = txn_id;
- if (mutable_rep_truncated)
- return SVN_NO_ERROR;
- }
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->offset = (apr_off_t)val;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->size = (svn_filesize_t)val;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->expanded_size = (svn_filesize_t)val;
-
- /* Read in the MD5 hash. */
- str = svn_cstring_tokenize(" ", &string);
- if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(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 read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
- and adding an error message. */
-static svn_error_t *
-read_rep_offsets(representation_t **rep_p,
- char *string,
- const svn_fs_id_t *noderev_id,
- svn_boolean_t mutable_rep_truncated,
- apr_pool_t *pool)
-{
- svn_error_t *err;
- const char *txn_id;
-
- if (noderev_id)
- txn_id = svn_fs_fs__id_txn_id(noderev_id);
- else
- txn_id = NULL;
-
- err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
- pool);
- if (err)
- {
- const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
- const char *where;
- where = apr_psprintf(pool,
- _("While reading representation offsets "
- "for node-revision '%s':"),
- noderev_id ? id_unparsed->data : "(null)");
-
- return svn_error_quick_wrap(err, where);
- }
- else
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
-{
- svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
- return svn_error_createf
- (SVN_ERR_FS_ID_NOT_FOUND, 0,
- _("Reference to non-existent node '%s' in filesystem '%s'"),
- id_str->data, fs->path);
-}
-
-/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
- * caching has been enabled and the data can be found, IS_CACHED will
- * be set to TRUE. The noderev will be allocated from POOL.
- *
- * Non-permanent ids (e.g. ids within a TXN) will not be cached.
- */
-static svn_error_t *
-get_cached_node_revision_body(node_revision_t **noderev_p,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- svn_boolean_t *is_cached,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
- {
- *is_cached = FALSE;
- }
- else
- {
- pair_cache_key_t key = { 0 };
-
- key.revision = svn_fs_fs__id_rev(id);
- key.second = svn_fs_fs__id_offset(id);
- SVN_ERR(svn_cache__get((void **) noderev_p,
- is_cached,
- ffd->node_revision_cache,
- &key,
- pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* If noderev caching has been enabled, store the NODEREV_P for the given ID
- * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
- *
- * Non-permanent ids (e.g. ids within a TXN) will not be cached.
- */
-static svn_error_t *
-set_cached_node_revision_body(node_revision_t *noderev_p,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *scratch_pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
- {
- pair_cache_key_t key = { 0 };
-
- key.revision = svn_fs_fs__id_rev(id);
- key.second = svn_fs_fs__id_offset(id);
- return svn_cache__set(ffd->node_revision_cache,
- &key,
- noderev_p,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Get the node-revision for the node ID in FS.
- Set *NODEREV_P to the new node-revision structure, allocated in POOL.
- See svn_fs_fs__get_node_revision, which wraps this and adds another
- error. */
-static svn_error_t *
-get_node_revision_body(node_revision_t **noderev_p,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *pool)
-{
- apr_file_t *revision_file;
- svn_error_t *err;
- svn_boolean_t is_cached = FALSE;
-
- /* First, try a cache lookup. If that succeeds, we are done here. */
- SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
- if (is_cached)
- return SVN_NO_ERROR;
-
- if (svn_fs_fs__id_txn_id(id))
- {
- /* This is a transaction node-rev. */
- err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
- }
- else
- {
- /* This is a revision node-rev. */
- err = open_and_seek_revision(&revision_file, fs,
- svn_fs_fs__id_rev(id),
- svn_fs_fs__id_offset(id),
- pool);
- }
-
- if (err)
- {
- if (APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- return svn_error_trace(err_dangling_id(fs, id));
- }
-
- return svn_error_trace(err);
- }
-
- SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
- svn_stream_from_aprfile2(revision_file, FALSE,
- pool),
- pool));
-
- /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
- return set_cached_node_revision_body(*noderev_p, fs, id, pool);
-}
-
-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, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
- /* ### s/kind/type/ */
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Missing kind field in node-rev '%s'"),
- noderev_id);
-
- noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
- : svn_node_dir;
-
- /* Read the 'count' field. */
- value = svn_hash_gets(headers, HEADER_COUNT);
- if (value)
- SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
- else
- noderev->predecessor_count = 0;
-
- /* Get the properties location. */
- value = svn_hash_gets(headers, HEADER_PROPS);
- if (value)
- {
- SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
- noderev->id, TRUE, pool));
- }
-
- /* Get the data location. */
- value = svn_hash_gets(headers, HEADER_TEXT);
- if (value)
- {
- SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
- noderev->id,
- (noderev->kind == svn_node_dir), pool));
- }
-
- /* 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_error_t *
-svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *pool)
-{
- svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
- if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
- {
- svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
- return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
- "Corrupt node-revision '%s'",
- id_string->data);
- }
- return svn_error_trace(err);
-}
-
-
-/* Return a formatted string, compatible with filesystem format FORMAT,
- that represents the location of representation REP. If
- MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
- and only a "-1" revision number will be given for a mutable rep.
- If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
- Perform the allocation from POOL. */
-static const char *
-representation_string(representation_t *rep,
- int format,
- svn_boolean_t mutable_rep_truncated,
- svn_boolean_t may_be_corrupt,
- apr_pool_t *pool)
-{
- if (rep->txn_id && mutable_rep_truncated)
- return "-1";
-
-#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum) \
- ((!may_be_corrupt || (checksum) != NULL) \
- ? svn_checksum_to_cstring_display((checksum), pool) \
- : "(null)")
-
- if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
- return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
- " %" SVN_FILESIZE_T_FMT " %s",
- rep->revision, rep->offset, rep->size,
- rep->expanded_size,
- DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
-
- return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
- " %" SVN_FILESIZE_T_FMT " %s %s %s",
- rep->revision, rep->offset, rep->size,
- rep->expanded_size,
- DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
- DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
- rep->uniquifier);
-
-#undef DISPLAY_MAYBE_NULL_CHECKSUM
-
-}
-
-
-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) ?
- KIND_FILE : KIND_DIR));
-
- if (noderev->predecessor_id)
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
- svn_fs_fs__id_unparse(noderev->predecessor_id,
- pool)->data));
-
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
- noderev->predecessor_count));
-
- if (noderev->data_rep)
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
- representation_string(noderev->data_rep,
- format,
- (noderev->kind
- == svn_node_dir),
- FALSE,
- pool)));
-
- if (noderev->prop_rep)
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
- representation_string(noderev->prop_rep, format,
- TRUE, FALSE, pool)));
-
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
- noderev->created_path));
-
- if (noderev->copyfrom_path)
- SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
- " %s\n",
- noderev->copyfrom_rev,
- 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__put_node_revision(svn_fs_t *fs,
- const svn_fs_id_t *id,
- node_revision_t *noderev,
- svn_boolean_t fresh_txn_root,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_file_t *noderev_file;
- const char *txn_id = svn_fs_fs__id_txn_id(id);
-
- noderev->is_fresh_txn_root = fresh_txn_root;
-
- if (! txn_id)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Attempted to write to non-transaction '%s'"),
- svn_fs_fs__id_unparse(id, pool)->data);
-
- SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
- APR_WRITE | APR_CREATE | APR_TRUNCATE
- | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
- pool),
- noderev, ffd->format,
- svn_fs_fs__fs_supports_mergeinfo(fs),
- pool));
-
- SVN_ERR(svn_io_file_close(noderev_file, pool));
-
- return SVN_NO_ERROR;
-}
-
-/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
- * file in the respective transaction, if rep sharing has been enabled etc.
- * Use POOL for temporary allocations.
- */
-static svn_error_t *
-store_sha1_rep_mapping(svn_fs_t *fs,
+svn_fs_fs__file_length(svn_filesize_t *length,
node_revision_t *noderev,
apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* if rep sharing has been enabled and the noderev has a data rep and
- * its SHA-1 is known, store the rep struct under its SHA1. */
- if ( ffd->rep_sharing_allowed
- && noderev->data_rep
- && noderev->data_rep->sha1_checksum)
+ representation_t *data_rep = noderev->data_rep;
+ if (!data_rep)
{
- apr_file_t *rep_file;
- const char *file_name = path_txn_sha1(fs,
- svn_fs_fs__id_txn_id(noderev->id),
- noderev->data_rep->sha1_checksum,
- pool);
- const char *rep_string = representation_string(noderev->data_rep,
- ffd->format,
- (noderev->kind
- == svn_node_dir),
- FALSE,
- pool);
- SVN_ERR(svn_io_file_open(&rep_file, file_name,
- APR_WRITE | APR_CREATE | APR_TRUNCATE
- | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
- strlen(rep_string), NULL, pool));
-
- SVN_ERR(svn_io_file_close(rep_file, pool));
+ /* Treat "no representation" as "empty file". */
+ *length = 0;
}
-
- return SVN_NO_ERROR;
-}
-
-
-/* This structure is used to hold the information associated with a
- REP line. */
-struct rep_args
-{
- svn_boolean_t is_delta;
- svn_boolean_t is_delta_vs_empty;
-
- svn_revnum_t base_revision;
- apr_off_t base_offset;
- svn_filesize_t base_length;
-};
-
-/* 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. */
-static svn_error_t *
-read_rep_line(struct rep_args **rep_args_p,
- apr_file_t *file,
- apr_pool_t *pool)
-{
- char buffer[160];
- apr_size_t limit;
- struct rep_args *rep_args;
- char *str, *last_str = buffer;
- apr_int64_t val;
-
- limit = sizeof(buffer);
- SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
-
- rep_args = apr_pcalloc(pool, sizeof(*rep_args));
- rep_args->is_delta = FALSE;
-
- if (strcmp(buffer, REP_PLAIN) == 0)
- {
- *rep_args_p = rep_args;
- return SVN_NO_ERROR;
- }
-
- if (strcmp(buffer, REP_DELTA) == 0)
- {
- /* This is a delta against the empty stream. */
- rep_args->is_delta = TRUE;
- rep_args->is_delta_vs_empty = TRUE;
- *rep_args_p = rep_args;
- return SVN_NO_ERROR;
- }
-
- rep_args->is_delta = TRUE;
- rep_args->is_delta_vs_empty = FALSE;
-
- /* We have hopefully a DELTA vs. a non-empty base revision. */
- 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;
- rep_args->base_revision = SVN_STR_TO_REV(str);
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (! str)
- goto error;
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep_args->base_offset = (apr_off_t)val;
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (! str)
- goto error;
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep_args->base_length = (svn_filesize_t)val;
-
- *rep_args_p = rep_args;
- return SVN_NO_ERROR;
-
- error:
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed representation header at %s"),
- path_and_offset_of(file, pool));
-}
-
-/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
- of the header located at OFFSET and store it in *ID_P. Allocate
- temporary variables from POOL. */
-static svn_error_t *
-get_fs_id_at_offset(svn_fs_id_t **id_p,
- apr_file_t *rev_file,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_off_t offset,
- apr_pool_t *pool)
-{
- svn_fs_id_t *id;
- apr_hash_t *headers;
- const char *node_id_str;
-
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
- SVN_ERR(read_header_block(&headers,
- svn_stream_from_aprfile2(rev_file, TRUE, pool),
- pool));
-
- /* In error messages, the offset is relative to the pack file,
- not to the rev file. */
-
- node_id_str = svn_hash_gets(headers, HEADER_ID);
-
- if (node_id_str == NULL)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Missing node-id in node-rev at r%ld "
- "(offset %s)"),
- rev,
- apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
-
- id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
-
- if (id == NULL)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Corrupt node-id '%s' in node-rev at r%ld "
- "(offset %s)"),
- node_id_str, rev,
- apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
-
- *id_p = id;
-
- /* ### assert that the txn_id is REV/OFFSET ? */
-
- return SVN_NO_ERROR;
-}
-
-
-/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
- specifies the offset to the root node-id and to the changed path
- information. Store the root node offset in *ROOT_OFFSET and the
- changed path offset in *CHANGES_OFFSET. If either of these
- pointers is NULL, do nothing with it.
-
- If PACKED is true, REV_FILE should be a packed shard file.
- ### There is currently no such parameter. This function assumes that
- is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
- file. Therefore FS->fsap_data->min_unpacked_rev must not have been
- refreshed since REV_FILE was opened if there is a possibility that
- revision REV may have become packed since then.
- TODO: Take an IS_PACKED parameter instead, in order to remove this
- requirement.
-
- Allocate temporary variables from POOL. */
-static svn_error_t *
-get_root_changes_offset(apr_off_t *root_offset,
- apr_off_t *changes_offset,
- apr_file_t *rev_file,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_off_t offset;
- apr_off_t rev_offset;
- char buf[64];
- int i, num_bytes;
- const char *str;
- apr_size_t len;
- apr_seek_where_t seek_relative;
-
- /* Determine where to seek to in the file.
-
- If we've got a pack file, we want to seek to the end of the desired
- revision. But we don't track that, so we seek to the beginning of the
- next revision.
-
- Unless the next revision is in a different file, in which case, we can
- just seek to the end of the pack file -- just like we do in the
- non-packed case. */
- if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
- {
- SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
- seek_relative = APR_SET;
- }
- else
- {
- seek_relative = APR_END;
- offset = 0;
- }
-
- /* Offset of the revision from the start of the pack file, if applicable. */
- if (is_packed_rev(fs, rev))
- SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
- else
- rev_offset = 0;
-
- /* We will assume that the last line containing the two offsets
- will never be longer than 64 characters. */
- SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
-
- offset -= sizeof(buf);
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-
- /* Read in this last block, from which we will identify the last line. */
- len = sizeof(buf);
- SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
-
- /* This cast should be safe since the maximum amount read, 64, will
- never be bigger than the size of an int. */
- num_bytes = (int) len;
-
- /* The last byte should be a newline. */
- if (buf[num_bytes - 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 (buf[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 = &buf[i];
-
- /* find the next space */
- for ( ; i < (num_bytes - 2) ; i++)
- if (buf[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;
-
- buf[i] = '\0';
- SVN_ERR(svn_cstring_atoi64(&val, str));
- *root_offset = rev_offset + (apr_off_t)val;
- }
-
- i++;
- str = &buf[i];
-
- /* find the next newline */
- for ( ; i < num_bytes; i++)
- if (buf[i] == '\n')
- break;
-
- if (changes_offset)
- {
- apr_int64_t val;
-
- buf[i] = '\0';
- SVN_ERR(svn_cstring_atoi64(&val, str));
- *changes_offset = rev_offset + (apr_off_t)val;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Move a file into place from OLD_FILENAME in the transactions
- directory to its final location NEW_FILENAME in the repository. On
- Unix, match the permissions of the new file to the permissions of
- PERMS_REFERENCE. Temporary allocations are from POOL.
-
- This function almost duplicates svn_io_file_move(), but it tries to
- guarantee a flush. */
-static svn_error_t *
-move_into_place(const char *old_filename,
- const char *new_filename,
- const char *perms_reference,
- apr_pool_t *pool)
-{
- svn_error_t *err;
-
- SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
-
- /* Move the file into place. */
- err = svn_io_file_rename(old_filename, new_filename, pool);
- if (err && APR_STATUS_IS_EXDEV(err->apr_err))
- {
- apr_file_t *file;
-
- /* Can't rename across devices; fall back to copying. */
- svn_error_clear(err);
- err = SVN_NO_ERROR;
- SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
-
- /* Flush the target of the copy to disk. */
- SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
- APR_OS_DEFAULT, pool));
- /* ### BH: Does this really guarantee a flush of the data written
- ### via a completely different handle on all operating systems?
- ###
- ### Maybe we should perform the copy ourselves instead of making
- ### apr do that and flush the real handle? */
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
- if (err)
- return svn_error_trace(err);
-
-#ifdef __linux__
- {
- /* Linux has the unusual feature that fsync() on a file is not
- enough to ensure that a file's directory entries have been
- flushed to disk; you have to fsync the directory as well.
- On other operating systems, we'd only be asking for trouble
- by trying to open and fsync a directory. */
- const char *dirname;
- apr_file_t *file;
-
- dirname = svn_dirent_dirname(new_filename, pool);
- SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
- pool));
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
-#endif
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_file_t *revision_file;
- apr_off_t root_offset;
- svn_fs_id_t *root_id = NULL;
- svn_boolean_t is_cached;
-
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
- SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
- ffd->rev_root_id_cache, &rev, pool));
- if (is_cached)
- return SVN_NO_ERROR;
-
- SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
- SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
- pool));
-
- SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
- root_offset, pool));
-
- SVN_ERR(svn_io_file_close(revision_file, pool));
-
- SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
-
- *root_id_p = root_id;
-
- return SVN_NO_ERROR;
-}
-
-/* Revprop caching management.
- *
- * Mechanism:
- * ----------
- *
- * Revprop caching needs to be activated and will be deactivated for the
- * respective FS instance if the necessary infrastructure could not be
- * initialized. In deactivated mode, there is almost no runtime overhead
- * associated with revprop caching. As long as no revprops are being read
- * or changed, revprop caching imposes no overhead.
- *
- * When activated, we cache revprops using (revision, generation) pairs
- * as keys with the generation being incremented upon every revprop change.
- * Since the cache is process-local, the generation needs to be tracked
- * for at least as long as the process lives but may be reset afterwards.
- *
- * To track the revprop generation, we use two-layer approach. On the lower
- * level, we use named atomics to have a system-wide consistent value for
- * the current revprop generation. However, those named atomics will only
- * remain valid for as long as at least one process / thread in the system
- * accesses revprops in the respective repository. The underlying shared
- * memory gets cleaned up afterwards.
- *
- * On the second level, we will use a persistent file to track the latest
- * revprop generation. It will be written upon each revprop change but
- * only be read if we are the first process to initialize the named atomics
- * with that value.
- *
- * The overhead for the second and following accesses to revprops is
- * almost zero on most systems.
- *
- *
- * Tech aspects:
- * -------------
- *
- * A problem is that we need to provide a globally available file name to
- * back the SHM implementation on OSes that need it. We can only assume
- * write access to some file within the respective repositories. Because
- * a given server process may access thousands of repositories during its
- * lifetime, keeping the SHM data alive for all of them is also not an
- * option.
- *
- * So, we store the new revprop generation on disk as part of each
- * setrevprop call, i.e. this write will be serialized and the write order
- * be guaranteed by the repository write lock.
- *
- * The only racy situation occurs when the data is being read again by two
- * processes concurrently but in that situation, the first process to
- * finish that procedure is guaranteed to be the only one that initializes
- * the SHM data. Since even writers will first go through that
- * initialization phase, they will never operate on stale data.
- */
-
-/* Read revprop generation as stored on disk for repository FS. The result
- * is returned in *CURRENT. Default to 2 if no such file is available.
- */
-static svn_error_t *
-read_revprop_generation_file(apr_int64_t *current,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- svn_error_t *err;
- apr_file_t *file;
- char buf[80];
- apr_size_t len;
- const char *path = path_revprop_generation(fs, pool);
-
- err = svn_io_file_open(&file, path,
- APR_READ | APR_BUFFERED,
- APR_OS_DEFAULT, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- *current = 2;
-
- return SVN_NO_ERROR;
- }
- SVN_ERR(err);
-
- len = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
-
- /* Check that the first line contains only digits. */
- SVN_ERR(check_file_buffer_numeric(buf, 0, path,
- "Revprop Generation", pool));
- SVN_ERR(svn_cstring_atoi64(current, buf));
-
- return svn_io_file_close(file, pool);
-}
-
-/* Write the CURRENT revprop generation to disk for repository FS.
- */
-static svn_error_t *
-write_revprop_generation_file(svn_fs_t *fs,
- apr_int64_t current,
- apr_pool_t *pool)
-{
- apr_file_t *file;
- const char *tmp_path;
-
- char buf[SVN_INT64_BUFFER_SIZE];
- apr_size_t len = svn__i64toa(buf, current);
- buf[len] = '\n';
-
- SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
- svn_io_file_del_none, pool, pool));
- SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
- SVN_ERR(svn_io_file_close(file, pool));
-
- return move_into_place(tmp_path, path_revprop_generation(fs, pool),
- tmp_path, pool);
-}
-
-/* Make sure the revprop_namespace member in FS is set. */
-static svn_error_t *
-ensure_revprop_namespace(svn_fs_t *fs)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- return ffd->revprop_namespace == NULL
- ? svn_atomic_namespace__create(&ffd->revprop_namespace,
- svn_dirent_join(fs->path,
- ATOMIC_REVPROP_NAMESPACE,
- fs->pool),
- fs->pool)
- : SVN_NO_ERROR;
-}
-
-/* Make sure the revprop_namespace member in FS is set. */
-static svn_error_t *
-cleanup_revprop_namespace(svn_fs_t *fs)
-{
- const char *name = svn_dirent_join(fs->path,
- ATOMIC_REVPROP_NAMESPACE,
- fs->pool);
- return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
-}
-
-/* Make sure the revprop_generation member in FS is set and, if necessary,
- * initialized with the latest value stored on disk.
- */
-static svn_error_t *
-ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- SVN_ERR(ensure_revprop_namespace(fs));
- if (ffd->revprop_generation == NULL)
- {
- apr_int64_t current = 0;
-
- SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
- ffd->revprop_namespace,
- ATOMIC_REVPROP_GENERATION,
- TRUE));
-
- /* If the generation is at 0, we just created a new namespace
- * (it would be at least 2 otherwise). Read the latest generation
- * from disk and if we are the first one to initialize the atomic
- * (i.e. is still 0), set it to the value just gotten.
- */
- SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
- if (current == 0)
- {
- SVN_ERR(read_revprop_generation_file(&current, fs, pool));
- SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
- ffd->revprop_generation));
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Make sure the revprop_timeout member in FS is set. */
-static svn_error_t *
-ensure_revprop_timeout(svn_fs_t *fs)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- SVN_ERR(ensure_revprop_namespace(fs));
- return ffd->revprop_timeout == NULL
- ? svn_named_atomic__get(&ffd->revprop_timeout,
- ffd->revprop_namespace,
- ATOMIC_REVPROP_TIMEOUT,
- TRUE)
- : SVN_NO_ERROR;
-}
-
-/* Create an error object with the given MESSAGE and pass it to the
- WARNING member of FS. */
-static void
-log_revprop_cache_init_warning(svn_fs_t *fs,
- svn_error_t *underlying_err,
- const char *message)
-{
- svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
- underlying_err,
- message, fs->path);
-
- if (fs->warning)
- (fs->warning)(fs->warning_baton, err);
-
- svn_error_clear(err);
-}
-
-/* Test whether revprop cache and necessary infrastructure are
- available in FS. */
-static svn_boolean_t
-has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_error_t *error;
-
- /* is the cache (still) enabled? */
- if (ffd->revprop_cache == NULL)
- return FALSE;
-
- /* is it efficient? */
- if (!svn_named_atomic__is_efficient())
- {
- /* access to it would be quite slow
- * -> disable the revprop cache for good
- */
- ffd->revprop_cache = NULL;
- log_revprop_cache_init_warning(fs, NULL,
- "Revprop caching for '%s' disabled"
- " because it would be inefficient.");
-
- return FALSE;
- }
-
- /* try to access our SHM-backed infrastructure */
- error = ensure_revprop_generation(fs, pool);
- if (error)
- {
- /* failure -> disable revprop cache for good */
-
- ffd->revprop_cache = NULL;
- log_revprop_cache_init_warning(fs, error,
- "Revprop caching for '%s' disabled "
- "because SHM infrastructure for revprop "
- "caching failed to initialize.");
-
- return FALSE;
- }
-
- return TRUE;
-}
-
-/* Baton structure for revprop_generation_fixup. */
-typedef struct revprop_generation_fixup_t
-{
- /* revprop generation to read */
- apr_int64_t *generation;
-
- /* containing the revprop_generation member to query */
- fs_fs_data_t *ffd;
-} revprop_generation_upgrade_t;
-
-/* If the revprop generation has an odd value, it means the original writer
- of the revprop got killed. We don't know whether that process as able
- to change the revprop data but we assume that it was. Therefore, we
- increase the generation in that case to basically invalidate everyones
- cache content.
- Execute this onlx while holding the write lock to the repo in baton->FFD.
- */
-static svn_error_t *
-revprop_generation_fixup(void *void_baton,
- apr_pool_t *pool)
-{
- revprop_generation_upgrade_t *baton = void_baton;
- assert(baton->ffd->has_write_lock);
-
- /* Maybe, either the original revprop writer or some other reader has
- already corrected / bumped the revprop generation. Thus, we need
- to read it again. */
- SVN_ERR(svn_named_atomic__read(baton->generation,
- baton->ffd->revprop_generation));
-
- /* Cause everyone to re-read revprops upon their next access, if the
- last revprop write did not complete properly. */
- while (*baton->generation % 2)
- SVN_ERR(svn_named_atomic__add(baton->generation,
- 1,
- baton->ffd->revprop_generation));
-
- return SVN_NO_ERROR;
-}
-
-/* Read the current revprop generation and return it in *GENERATION.
- Also, detect aborted / crashed writers and recover from that.
- Use the access object in FS to set the shared mem values. */
-static svn_error_t *
-read_revprop_generation(apr_int64_t *generation,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- apr_int64_t current = 0;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* read the current revprop generation number */
- SVN_ERR(ensure_revprop_generation(fs, pool));
- SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
-
- /* is an unfinished revprop write under the way? */
- if (current % 2)
- {
- apr_int64_t timeout = 0;
-
- /* read timeout for the write operation */
- SVN_ERR(ensure_revprop_timeout(fs));
- SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
-
- /* has the writer process been aborted,
- * i.e. has the timeout been reached?
- */
- if (apr_time_now() > timeout)
- {
- revprop_generation_upgrade_t baton;
- baton.generation = &current;
- baton.ffd = ffd;
-
- /* Ensure that the original writer process no longer exists by
- * acquiring the write lock to this repository. Then, fix up
- * the revprop generation.
- */
- if (ffd->has_write_lock)
- SVN_ERR(revprop_generation_fixup(&baton, pool));
- else
- SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
- &baton, pool));
- }
- }
-
- /* return the value we just got */
- *generation = current;
- return SVN_NO_ERROR;
-}
-
-/* Set the revprop generation to the next odd number to indicate that
- there is a revprop write process under way. If that times out,
- readers shall recover from that state & re-read revprops.
- Use the access object in FS to set the shared mem value. */
-static svn_error_t *
-begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
-{
- apr_int64_t current;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* set the timeout for the write operation */
- SVN_ERR(ensure_revprop_timeout(fs));
- SVN_ERR(svn_named_atomic__write(NULL,
- apr_time_now() + REVPROP_CHANGE_TIMEOUT,
- ffd->revprop_timeout));
-
- /* set the revprop generation to an odd value to indicate
- * that a write is in progress
- */
- SVN_ERR(ensure_revprop_generation(fs, pool));
- do
- {
- SVN_ERR(svn_named_atomic__add(&current,
- 1,
- ffd->revprop_generation));
- }
- while (current % 2 == 0);
-
- return SVN_NO_ERROR;
-}
-
-/* Set the revprop generation to the next even number to indicate that
- a) readers shall re-read revprops, and
- b) the write process has been completed (no recovery required)
- Use the access object in FS to set the shared mem value. */
-static svn_error_t *
-end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
-{
- apr_int64_t current = 1;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* set the revprop generation to an even value to indicate
- * that a write has been completed
- */
- SVN_ERR(ensure_revprop_generation(fs, pool));
- do
- {
- SVN_ERR(svn_named_atomic__add(&current,
- 1,
- ffd->revprop_generation));
- }
- while (current % 2);
-
- /* Save the latest generation to disk. FS is currently in a "locked"
- * state such that we can be sure the be the only ones to write that
- * file.
- */
- return write_revprop_generation_file(fs, current, pool);
-}
-
-/* Container for all data required to access the packed revprop file
- * for a given REVISION. This structure will be filled incrementally
- * by read_pack_revprops() its sub-routines.
- */
-typedef struct packed_revprops_t
-{
- /* revision number to read (not necessarily the first in the pack) */
- svn_revnum_t revision;
-
- /* current revprop generation. Used when populating the revprop cache */
- apr_int64_t generation;
-
- /* the actual revision properties */
- apr_hash_t *properties;
-
- /* their size when serialized to a single string
- * (as found in PACKED_REVPROPS) */
- apr_size_t serialized_size;
-
-
- /* name of the pack file (without folder path) */
- const char *filename;
-
- /* packed shard folder path */
- const char *folder;
-
- /* sum of values in SIZES */
- apr_size_t total_size;
-
- /* first revision in the pack (>= MANIFEST_START) */
- svn_revnum_t start_revision;
-
- /* size of the revprops in PACKED_REVPROPS */
- apr_array_header_t *sizes;
-
- /* offset of the revprops in PACKED_REVPROPS */
- apr_array_header_t *offsets;
-
-
- /* concatenation of the serialized representation of all revprops
- * in the pack, i.e. the pack content without header and compression */
- svn_stringbuf_t *packed_revprops;
-
- /* First revision covered by MANIFEST.
- * Will equal the shard start revision or 1, for the 1st shard. */
- svn_revnum_t manifest_start;
-
- /* content of the manifest.
- * Maps long(rev - MANIFEST_START) to const char* pack file name */
- apr_array_header_t *manifest;
-} packed_revprops_t;
-
-/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
- * Also, put them into the revprop cache, if activated, for future use.
- * Three more parameters are being used to update the revprop cache: FS is
- * our file system, the revprops belong to REVISION and the global revprop
- * GENERATION is used as well.
- *
- * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
- * for temporary allocations.
- */
-static svn_error_t *
-parse_revprop(apr_hash_t **properties,
- svn_fs_t *fs,
- svn_revnum_t revision,
- apr_int64_t generation,
- svn_string_t *content,
- apr_pool_t *pool,
- apr_pool_t *scratch_pool)
-{
- svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
- *properties = apr_hash_make(pool);
-
- SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
- if (has_revprop_cache(fs, pool))
- {
- fs_fs_data_t *ffd = fs->fsap_data;
- pair_cache_key_t key = { 0 };
-
- key.revision = revision;
- key.second = generation;
- SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Read the non-packed revprops for revision REV in FS, put them into the
- * revprop cache if activated and return them in *PROPERTIES. GENERATION
- * is the current revprop generation.
- *
- * If the data could not be read due to an otherwise recoverable error,
- * leave *PROPERTIES unchanged. No error will be returned in that case.
- *
- * Allocations will be done in POOL.
- */
-static svn_error_t *
-read_non_packed_revprop(apr_hash_t **properties,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_int64_t generation,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *content = NULL;
- apr_pool_t *iterpool = svn_pool_create(pool);
- svn_boolean_t missing = FALSE;
- int i;
-
- for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
- {
- svn_pool_clear(iterpool);
- SVN_ERR(try_stringbuf_from_file(&content,
- &missing,
- path_revprops(fs, rev, iterpool),
- i + 1 < RECOVERABLE_RETRY_COUNT,
- iterpool));
- }
-
- if (content)
- SVN_ERR(parse_revprop(properties, fs, rev, generation,
- svn_stringbuf__morph_into_string(content),
- pool, iterpool));
-
- svn_pool_clear(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
- * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
- */
-static svn_error_t *
-get_revprop_packname(svn_fs_t *fs,
- packed_revprops_t *revprops,
- apr_pool_t *pool,
- apr_pool_t *scratch_pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_stringbuf_t *content = NULL;
- const char *manifest_file_path;
- int idx;
-
- /* read content of the manifest file */
- revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
- manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
-
- SVN_ERR(read_content(&content, manifest_file_path, pool));
-
- /* parse the manifest. Every line is a file name */
- revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
- sizeof(const char*));
-
- /* Read all lines. Since the last line ends with a newline, we will
- end up with a valid but empty string after the last entry. */
- while (content->data && *content->data)
- {
- APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
- content->data = strchr(content->data, '\n');
- if (content->data)
- {
- *content->data = 0;
- content->data++;
- }
- }
-
- /* Index for our revision. Rev 0 is excluded from the first shard. */
- revprops->manifest_start = revprops->revision
- - (revprops->revision % ffd->max_files_per_dir);
- if (revprops->manifest_start == 0)
- ++revprops->manifest_start;
- idx = (int)(revprops->revision - revprops->manifest_start);
-
- if (revprops->manifest->nelts <= idx)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Packed revprop manifest for r%ld too "
- "small"), revprops->revision);
-
- /* Now get the file name */
- revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
-
- return SVN_NO_ERROR;
-}
-
-/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
- */
-static svn_boolean_t
-same_shard(svn_fs_t *fs,
- svn_revnum_t r1,
- svn_revnum_t r2)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
-}
-
-/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
- * fill the START_REVISION, SIZES, OFFSETS members. Also, make
- * PACKED_REVPROPS point to the first serialized revprop.
- *
- * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
- * well as the SERIALIZED_SIZE member. If revprop caching has been
- * enabled, parse all revprops in the pack and cache them.
- */
-static svn_error_t *
-parse_packed_revprops(svn_fs_t *fs,
- packed_revprops_t *revprops,
- apr_pool_t *pool,
- apr_pool_t *scratch_pool)
-{
- svn_stream_t *stream;
- apr_int64_t first_rev, count, i;
- apr_off_t offset;
- const char *header_end;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-
- /* decompress (even if the data is only "stored", there is still a
- * length header to remove) */
- svn_string_t *compressed
- = svn_stringbuf__morph_into_string(revprops->packed_revprops);
- svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
- SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
-
- /* read first revision number and number of revisions in the pack */
- stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
- SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
- SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
-
- /* Check revision range for validity. */
- if ( !same_shard(fs, revprops->revision, first_rev)
- || !same_shard(fs, revprops->revision, first_rev + count - 1)
- || count < 1)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Revprop pack for revision r%ld"
- " contains revprops for r%ld .. r%ld"),
- revprops->revision,
- (svn_revnum_t)first_rev,
- (svn_revnum_t)(first_rev + count -1));
-
- /* Since start & end are in the same shard, it is enough to just test
- * the FIRST_REV for being actually packed. That will also cover the
- * special case of rev 0 never being packed. */
- if (!is_packed_revprop(fs, first_rev))
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Revprop pack for revision r%ld"
- " starts at non-packed revisions r%ld"),
- revprops->revision, (svn_revnum_t)first_rev);
-
- /* make PACKED_REVPROPS point to the first char after the header.
- * This is where the serialized revprops are. */
- header_end = strstr(uncompressed->data, "\n\n");
- if (header_end == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Header end not found"));
-
- offset = header_end - uncompressed->data + 2;
-
- revprops->packed_revprops = svn_stringbuf_create_empty(pool);
- revprops->packed_revprops->data = uncompressed->data + offset;
- revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
- revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
-
- /* STREAM still points to the first entry in the sizes list.
- * Init / construct REVPROPS members. */
- revprops->start_revision = (svn_revnum_t)first_rev;
- revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
- revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
-
- /* Now parse, revision by revision, the size and content of each
- * revisions' revprops. */
- for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
- {
- apr_int64_t size;
- svn_string_t serialized;
- apr_hash_t *properties;
- svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
-
- /* read & check the serialized size */
- SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
- if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Packed revprop size exceeds pack file size"));
-
- /* Parse this revprops list, if necessary */
- serialized.data = revprops->packed_revprops->data + offset;
- serialized.len = (apr_size_t)size;
-
- if (revision == revprops->revision)
- {
- SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
- revprops->generation, &serialized,
- pool, iterpool));
- revprops->serialized_size = serialized.len;
- }
- else
- {
- /* If revprop caching is enabled, parse any revprops.
- * They will get cached as a side-effect of this. */
- if (has_revprop_cache(fs, pool))
- SVN_ERR(parse_revprop(&properties, fs, revision,
- revprops->generation, &serialized,
- iterpool, iterpool));
- }
-
- /* fill REVPROPS data structures */
- APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
- APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
- revprops->total_size += serialized.len;
-
- offset += serialized.len;
-
- svn_pool_clear(iterpool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* In filesystem FS, read the packed revprops for revision REV into
- * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled.
- * Allocate data in POOL.
- */
-static svn_error_t *
-read_pack_revprop(packed_revprops_t **revprops,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_int64_t generation,
- apr_pool_t *pool)
-{
- apr_pool_t *iterpool = svn_pool_create(pool);
- svn_boolean_t missing = FALSE;
- svn_error_t *err;
- packed_revprops_t *result;
- int i;
-
- /* someone insisted that REV is packed. Double-check if necessary */
- if (!is_packed_revprop(fs, rev))
- SVN_ERR(update_min_unpacked_rev(fs, iterpool));
-
- if (!is_packed_revprop(fs, rev))
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such packed revision %ld"), rev);
-
- /* initialize the result data structure */
- result = apr_pcalloc(pool, sizeof(*result));
- result->revision = rev;
- result->generation = generation;
-
- /* try to read the packed revprops. This may require retries if we have
- * concurrent writers. */
- for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
- {
- const char *file_path;
-
- /* there might have been concurrent writes.
- * Re-read the manifest and the pack file.
- */
- SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
- file_path = svn_dirent_join(result->folder,
- result->filename,
- iterpool);
- SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
- &missing,
- file_path,
- i + 1 < RECOVERABLE_RETRY_COUNT,
- pool));
-
- /* If we could not find the file, there was a write.
- * So, we should refresh our revprop generation info as well such
- * that others may find data we will put into the cache. They would
- * consider it outdated, otherwise.
- */
- if (missing && has_revprop_cache(fs, pool))
- SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
-
- svn_pool_clear(iterpool);
- }
-
- /* the file content should be available now */
- if (!result->packed_revprops)
- return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
- _("Failed to read revprop pack file for r%ld"), rev);
-
- /* parse it. RESULT will be complete afterwards. */
- err = parse_packed_revprops(fs, result, pool, iterpool);
- svn_pool_destroy(iterpool);
- if (err)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
- _("Revprop pack file for r%ld is corrupt"), rev);
-
- *revprops = result;
-
- return SVN_NO_ERROR;
-}
-
-/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
- *
- * Allocations will be done in POOL.
- */
-static svn_error_t *
-get_revision_proplist(apr_hash_t **proplist_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_int64_t generation = 0;
-
- /* not found, yet */
- *proplist_p = NULL;
-
- /* should they be available at all? */
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
- /* Try cache lookup first. */
- if (has_revprop_cache(fs, pool))
- {
- svn_boolean_t is_cached;
- pair_cache_key_t key = { 0 };
-
- SVN_ERR(read_revprop_generation(&generation, fs, pool));
-
- key.revision = rev;
- key.second = generation;
- SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
- ffd->revprop_cache, &key, pool));
- if (is_cached)
- return SVN_NO_ERROR;
- }
-
- /* if REV had not been packed when we began, try reading it from the
- * non-packed shard. If that fails, we will fall through to packed
- * shard reads. */
- if (!is_packed_revprop(fs, rev))
- {
- svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
- generation, pool);
- if (err)
- {
- if (!APR_STATUS_IS_ENOENT(err->apr_err)
- || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- return svn_error_trace(err);
-
- svn_error_clear(err);
- *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
- }
- }
-
- /* if revprop packing is available and we have not read the revprops, yet,
- * try reading them from a packed shard. If that fails, REV is most
- * likely invalid (or its revprops highly contested). */
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
- {
- packed_revprops_t *packed_revprops;
- SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
- *proplist_p = packed_revprops->properties;
- }
-
- /* The revprops should have been there. Did we get them? */
- if (!*proplist_p)
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("Could not read revprops for revision %ld"),
- rev);
-
- return SVN_NO_ERROR;
-}
-
-/* Serialize the revision property list PROPLIST of revision REV in
- * filesystem FS to a non-packed file. Return the name of that temporary
- * file in *TMP_PATH and the file path that it must be moved to in
- * *FINAL_PATH.
- *
- * Use POOL for allocations.
- */
-static svn_error_t *
-write_non_packed_revprop(const char **final_path,
- const char **tmp_path,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *proplist,
- apr_pool_t *pool)
-{
- svn_stream_t *stream;
- *final_path = path_revprops(fs, rev, pool);
-
- /* ### do we have a directory sitting around already? we really shouldn't
- ### have to get the dirname here. */
- SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
- svn_dirent_dirname(*final_path, pool),
- svn_io_file_del_none, pool, pool));
- SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
-
- return SVN_NO_ERROR;
-}
-
-/* After writing the new revprop file(s), call this function to move the
- * file at TMP_PATH to FINAL_PATH and give it the permissions from
- * PERMS_REFERENCE.
- *
- * If indicated in BUMP_GENERATION, increase FS' revprop generation.
- * Finally, delete all the temporary files given in FILES_TO_DELETE.
- * The latter may be NULL.
- *
- * Use POOL for temporary allocations.
- */
-static svn_error_t *
-switch_to_new_revprop(svn_fs_t *fs,
- const char *final_path,
- const char *tmp_path,
- const char *perms_reference,
- apr_array_header_t *files_to_delete,
- svn_boolean_t bump_generation,
- apr_pool_t *pool)
-{
- /* Now, we may actually be replacing revprops. Make sure that all other
- threads and processes will know about this. */
- if (bump_generation)
- SVN_ERR(begin_revprop_change(fs, pool));
-
- SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
-
- /* Indicate that the update (if relevant) has been completed. */
- if (bump_generation)
- SVN_ERR(end_revprop_change(fs, pool));
-
- /* Clean up temporary files, if necessary. */
- if (files_to_delete)
- {
- apr_pool_t *iterpool = svn_pool_create(pool);
- int i;
-
- for (i = 0; i < files_to_delete->nelts; ++i)
- {
- const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
- SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
- svn_pool_clear(iterpool);
- }
-
- svn_pool_destroy(iterpool);
- }
- return SVN_NO_ERROR;
-}
-
-/* Write a pack file header to STREAM that starts at revision START_REVISION
- * and contains the indexes [START,END) of SIZES.
- */
-static svn_error_t *
-serialize_revprops_header(svn_stream_t *stream,
- svn_revnum_t start_revision,
- apr_array_header_t *sizes,
- int start,
- int end,
- apr_pool_t *pool)
-{
- apr_pool_t *iterpool = svn_pool_create(pool);
- int i;
-
- SVN_ERR_ASSERT(start < end);
-
- /* start revision and entry count */
- SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
- SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
-
- /* the sizes array */
- for (i = start; i < end; ++i)
+ else if (data_rep->expanded_size)
{
- apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
- SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
- size));
- }
-
- /* the double newline char indicates the end of the header */
- SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
-
- svn_pool_clear(iterpool);
- return SVN_NO_ERROR;
-}
-
-/* Writes the a pack file to FILE_STREAM. It copies the serialized data
- * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
- *
- * The data for the latter is taken from NEW_SERIALIZED. Note, that
- * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
- * taken in that case but only a subset of the old data will be copied.
- *
- * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
- * POOL is used for temporary allocations.
- */
-static svn_error_t *
-repack_revprops(svn_fs_t *fs,
- packed_revprops_t *revprops,
- int start,
- int end,
- int changed_index,
- svn_stringbuf_t *new_serialized,
- apr_off_t new_total_size,
- svn_stream_t *file_stream,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_stream_t *stream;
- int i;
-
- /* create data empty buffers and the stream object */
- svn_stringbuf_t *uncompressed
- = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
- svn_stringbuf_t *compressed
- = svn_stringbuf_create_empty(pool);
- stream = svn_stream_from_stringbuf(uncompressed, pool);
-
- /* write the header*/
- SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
- revprops->sizes, start, end, pool));
-
- /* append the serialized revprops */
- for (i = start; i < end; ++i)
- if (i == changed_index)
- {
- SVN_ERR(svn_stream_write(stream,
- new_serialized->data,
- &new_serialized->len));
- }
- else
- {
- apr_size_t size
- = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
- apr_size_t offset
- = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
-
- SVN_ERR(svn_stream_write(stream,
- revprops->packed_revprops->data + offset,
- &size));
- }
-
- /* flush the stream buffer (if any) to our underlying data buffer */
- SVN_ERR(svn_stream_close(stream));
-
- /* compress / store the data */
- SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
- compressed,
- ffd->compress_packed_revprops
- ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
- : SVN_DELTA_COMPRESSION_LEVEL_NONE));
-
- /* finally, write the content to the target stream and close it */
- SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
- SVN_ERR(svn_stream_close(file_stream));
-
- return SVN_NO_ERROR;
-}
-
-/* Allocate a new pack file name for revisions
- * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
- * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE,
- * auto-create that array if necessary. Return an open file stream to
- * the new file in *STREAM allocated in POOL.
- */
-static svn_error_t *
-repack_stream_open(svn_stream_t **stream,
- svn_fs_t *fs,
- packed_revprops_t *revprops,
- int start,
- int end,
- apr_array_header_t **files_to_delete,
- apr_pool_t *pool)
-{
- apr_int64_t tag;
- const char *tag_string;
- svn_string_t *new_filename;
- int i;
- apr_file_t *file;
- int manifest_offset
- = (int)(revprops->start_revision - revprops->manifest_start);
-
- /* get the old (= current) file name and enlist it for later deletion */
- const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
- start + manifest_offset,
- const char*);
-
- if (*files_to_delete == NULL)
- *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
-
- APR_ARRAY_PUSH(*files_to_delete, const char*)
- = svn_dirent_join(revprops->folder, old_filename, pool);
-
- /* increase the tag part, i.e. the counter after the dot */
- tag_string = strchr(old_filename, '.');
- if (tag_string == NULL)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Packed file '%s' misses a tag"),
- old_filename);
-
- SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
- new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
- revprops->start_revision + start,
- ++tag);
-
- /* update the manifest to point to the new file */
- for (i = start; i < end; ++i)
- APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
- = new_filename->data;
-
- /* create a file stream for the new file */
- SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
- new_filename->data,
- pool),
- APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
- *stream = svn_stream_from_aprfile2(file, FALSE, pool);
-
- return SVN_NO_ERROR;
-}
-
-/* For revision REV in filesystem FS, set the revision properties to
- * PROPLIST. Return a new file in *TMP_PATH that the caller shall move
- * to *FINAL_PATH to make the change visible. Files to be deleted will
- * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
- * Use POOL for allocations.
- */
-static svn_error_t *
-write_packed_revprop(const char **final_path,
- const char **tmp_path,
- apr_array_header_t **files_to_delete,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *proplist,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- packed_revprops_t *revprops;
- apr_int64_t generation = 0;
- svn_stream_t *stream;
- svn_stringbuf_t *serialized;
- apr_off_t new_total_size;
- int changed_index;
-
- /* read the current revprop generation. This value will not change
- * while we hold the global write lock to this FS. */
- if (has_revprop_cache(fs, pool))
- SVN_ERR(read_revprop_generation(&generation, fs, pool));
-
- /* read contents of the current pack file */
- SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
-
- /* serialize the new revprops */
- serialized = svn_stringbuf_create_empty(pool);
- stream = svn_stream_from_stringbuf(serialized, pool);
- SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
-
- /* calculate the size of the new data */
- changed_index = (int)(rev - revprops->start_revision);
- new_total_size = revprops->total_size - revprops->serialized_size
- + serialized->len
- + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
-
- APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
-
- /* can we put the new data into the same pack as the before? */
- if ( new_total_size < ffd->revprop_pack_size
- || revprops->sizes->nelts == 1)
- {
- /* simply replace the old pack file with new content as we do it
- * in the non-packed case */
-
- *final_path = svn_dirent_join(revprops->folder, revprops->filename,
- pool);
- SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
- svn_io_file_del_none, pool, pool));
- SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
- changed_index, serialized, new_total_size,
- stream, pool));
+ /* Standard case: a non-empty file. */
+ *length = data_rep->expanded_size;
}
else
{
- /* split the pack file into two of roughly equal size */
- int right_count, left_count, i;
-
- int left = 0;
- int right = revprops->sizes->nelts - 1;
- apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
- apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
-
- /* let left and right side grow such that their size difference
- * is minimal after each step. */
- while (left <= right)
- if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
- < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
- {
- left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
- + SVN_INT64_BUFFER_SIZE;
- ++left;
- }
- else
- {
- right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
- + SVN_INT64_BUFFER_SIZE;
- --right;
- }
-
- /* since the items need much less than SVN_INT64_BUFFER_SIZE
- * bytes to represent their length, the split may not be optimal */
- left_count = left;
- right_count = revprops->sizes->nelts - left;
-
- /* if new_size is large, one side may exceed the pack size limit.
- * In that case, split before and after the modified revprop.*/
- if ( left_size > ffd->revprop_pack_size
- || right_size > ffd->revprop_pack_size)
- {
- left_count = changed_index;
- right_count = revprops->sizes->nelts - left_count - 1;
- }
-
- /* write the new, split files */
- if (left_count)
- {
- SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
- left_count, files_to_delete, pool));
- SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
- changed_index, serialized, new_total_size,
- stream, pool));
- }
-
- if (left_count + right_count < revprops->sizes->nelts)
- {
- SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
- changed_index + 1, files_to_delete,
- pool));
- SVN_ERR(repack_revprops(fs, revprops, changed_index,
- changed_index + 1,
- changed_index, serialized, new_total_size,
- stream, pool));
- }
-
- if (right_count)
- {
- SVN_ERR(repack_stream_open(&stream, fs, revprops,
- revprops->sizes->nelts - right_count,
- revprops->sizes->nelts,
- files_to_delete, pool));
- SVN_ERR(repack_revprops(fs, revprops,
- revprops->sizes->nelts - right_count,
- revprops->sizes->nelts, changed_index,
- serialized, new_total_size, stream,
- pool));
- }
-
- /* write the new manifest */
- *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
- SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
- svn_io_file_del_none, pool, pool));
-
- for (i = 0; i < revprops->manifest->nelts; ++i)
- {
- const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
- const char*);
- SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
- }
-
- SVN_ERR(svn_stream_close(stream));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Set the revision property list of revision REV in filesystem FS to
- PROPLIST. Use POOL for temporary allocations. */
-static svn_error_t *
-set_revision_proplist(svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *proplist,
- apr_pool_t *pool)
-{
- svn_boolean_t is_packed;
- svn_boolean_t bump_generation = FALSE;
- const char *final_path;
- const char *tmp_path;
- const char *perms_reference;
- apr_array_header_t *files_to_delete = NULL;
-
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
- /* this info will not change while we hold the global FS write lock */
- is_packed = is_packed_revprop(fs, rev);
-
- /* Test whether revprops already exist for this revision.
- * Only then will we need to bump the revprop generation. */
- if (has_revprop_cache(fs, pool))
- {
- if (is_packed)
- {
- bump_generation = TRUE;
- }
- else
- {
- svn_node_kind_t kind;
- SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
- pool));
- bump_generation = kind != svn_node_none;
- }
- }
-
- /* Serialize the new revprop data */
- if (is_packed)
- SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
- fs, rev, proplist, pool));
- else
- SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
- fs, rev, proplist, pool));
-
- /* We use the rev file of this revision as the perms reference,
- * because when setting revprops for the first time, the revprop
- * file won't exist and therefore can't serve as its own reference.
- * (Whereas the rev file should already exist at this point.)
- */
- SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
-
- /* Now, switch to the new revprop data. */
- SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
- files_to_delete, bump_generation, pool));
+ /* Work around a FSFS format quirk (see issue #4554).
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
+ A plain representation may specify its EXPANDED LENGTH as "0"
+ in which case, the SIZE value is what we want.
- return SVN_NO_ERROR;
-}
-
-/* Represents where in the current svndiff data block each
- representation is. */
-struct rep_state
-{
- apr_file_t *file;
- /* The txdelta window cache to use or NULL. */
- svn_cache__t *window_cache;
- /* Caches un-deltified windows. May be NULL. */
- svn_cache__t *combined_cache;
- apr_off_t start; /* The starting offset for the raw
- svndiff/plaintext data minus header. */
- apr_off_t off; /* The current offset into the file. */
- apr_off_t end; /* The end offset of the raw data. */
- int ver; /* If a delta, what svndiff version? */
- int chunk_index;
-};
-
-/* See create_rep_state, which wraps this and adds another error. */
-static svn_error_t *
-create_rep_state_body(struct rep_state **rep_state,
- struct rep_args **rep_args,
- apr_file_t **file_hint,
- svn_revnum_t *rev_hint,
- representation_t *rep,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
- struct rep_args *ra;
- unsigned char buf[4];
-
- /* If the hint is
- * - given,
- * - refers to a valid revision,
- * - refers to a packed revision,
- * - as does the rep we want to read, and
- * - refers to the same pack file as the rep
- * ...
- */
- if ( file_hint && rev_hint && *file_hint
- && SVN_IS_VALID_REVNUM(*rev_hint)
- && *rev_hint < ffd->min_unpacked_rev
- && rep->revision < ffd->min_unpacked_rev
- && ( (*rev_hint / ffd->max_files_per_dir)
- == (rep->revision / ffd->max_files_per_dir)))
- {
- /* ... we can re-use the same, already open file object
+ Because EXPANDED_LENGTH will also be 0 for empty files, while
+ SIZE is non-null, we need to check wether the content is
+ actually empty. We simply compare with the MD5 checksum of
+ empty content (sha-1 is not always available).
*/
- apr_off_t offset;
- SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
-
- offset += rep->offset;
- SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
-
- rs->file = *file_hint;
- }
- else
- {
- /* otherwise, create a new file object
- */
- SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
- }
-
- /* remember the current file, if suggested by the caller */
- if (file_hint)
- *file_hint = rs->file;
- if (rev_hint)
- *rev_hint = rep->revision;
-
- /* continue constructing RS and RA */
- rs->window_cache = ffd->txdelta_window_cache;
- rs->combined_cache = ffd->combined_window_cache;
-
- SVN_ERR(read_rep_line(&ra, rs->file, pool));
- SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
- rs->off = rs->start;
- rs->end = rs->start + rep->size;
- *rep_state = rs;
- *rep_args = ra;
-
- if (!ra->is_delta)
- /* This is a plaintext, so just return the current rep_state. */
- return SVN_NO_ERROR;
-
- /* We are dealing with a delta, find out what version. */
- SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
- NULL, NULL, pool));
- /* ### Layering violation */
- if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
- return svn_error_create
- (SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed svndiff data in representation"));
- rs->ver = buf[3];
- rs->chunk_index = 0;
- rs->off += 4;
-
- return SVN_NO_ERROR;
-}
-
-/* Read the rep args for REP in filesystem FS and create a rep_state
- for reading the representation. Return the rep_state in *REP_STATE
- and the rep args in *REP_ARGS, both allocated in POOL.
-
- When reading multiple reps, i.e. a skip delta chain, you may provide
- non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first
- call it should be a pointer to NULL.) The function will use these variables
- to store the previous call results and tries to re-use them. This may
- result in significant savings in I/O for packed files.
- */
-static svn_error_t *
-create_rep_state(struct rep_state **rep_state,
- struct rep_args **rep_args,
- apr_file_t **file_hint,
- svn_revnum_t *rev_hint,
- representation_t *rep,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- svn_error_t *err = create_rep_state_body(rep_state, rep_args,
- file_hint, rev_hint,
- rep, fs, pool);
- if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* ### This always returns "-1" for transaction reps, because
- ### this particular bit of code doesn't know if the rep is
- ### stored in the protorev or in the mutable area (for props
- ### or dir contents). It is pretty rare for FSFS to *read*
- ### from the protorev file, though, so this is probably OK.
- ### And anyone going to debug corruption errors is probably
- ### going to jump straight to this comment anyway! */
- return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
- "Corrupt representation '%s'",
- rep
- ? representation_string(rep, ffd->format, TRUE,
- TRUE, pool)
- : "(null)");
- }
- /* ### Call representation_string() ? */
- return svn_error_trace(err);
-}
-
-struct rep_read_baton
-{
- /* The FS from which we're reading. */
- svn_fs_t *fs;
-
- /* If not NULL, this is the base for the first delta window in rs_list */
- svn_stringbuf_t *base_window;
-
- /* The state of all prior delta representations. */
- apr_array_header_t *rs_list;
-
- /* The plaintext state, if there is a plaintext. */
- struct rep_state *src_state;
-
- /* The index of the current delta chunk, if we are reading a delta. */
- int chunk_index;
-
- /* The buffer where we store undeltified data. */
- char *buf;
- apr_size_t buf_pos;
- apr_size_t buf_len;
-
- /* A checksum context for summing the data read in order to verify it.
- Note: we don't need to use the sha1 checksum because we're only doing
- data verification, for which md5 is perfectly safe. */
- svn_checksum_ctx_t *md5_checksum_ctx;
-
- svn_boolean_t checksum_finalized;
-
- /* The stored checksum of the representation we are reading, its
- length, and the amount we've read so far. Some of this
- information is redundant with rs_list and src_state, but it's
- convenient for the checksumming code to have it here. */
- svn_checksum_t *md5_checksum;
-
- svn_filesize_t len;
- svn_filesize_t off;
-
- /* The key for the fulltext cache for this rep, if there is a
- fulltext cache. */
- pair_cache_key_t fulltext_cache_key;
- /* The text we've been reading, if we're going to cache it. */
- svn_stringbuf_t *current_fulltext;
-
- /* Used for temporary allocations during the read. */
- apr_pool_t *pool;
-
- /* Pool used to store file handles and other data that is persistant
- for the entire stream read. */
- apr_pool_t *filehandle_pool;
-};
-
-/* Combine the name of the rev file in RS with the given OFFSET to form
- * a cache lookup key. Allocations will be made from POOL. May return
- * NULL if the key cannot be constructed. */
-static const char*
-get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
-{
- const char *name;
- const char *last_part;
- const char *name_last;
-
- /* the rev file name containing the txdelta window.
- * If this fails we are in serious trouble anyways.
- * And if nobody else detects the problems, the file content checksum
- * comparison _will_ find them.
- */
- if (apr_file_name_get(&name, rs->file))
- return NULL;
-
- /* Handle packed files as well by scanning backwards until we find the
- * revision or pack number. */
- name_last = name + strlen(name) - 1;
- while (! svn_ctype_isdigit(*name_last))
- --name_last;
-
- last_part = name_last;
- while (svn_ctype_isdigit(*last_part))
- --last_part;
-
- /* We must differentiate between packed files (as of today, the number
- * is being followed by a dot) and non-packed files (followed by \0).
- * Otherwise, there might be overlaps in the numbering range if the
- * repo gets packed after caching the txdeltas of non-packed revs.
- * => add the first non-digit char to the packed number. */
- if (name_last[1] != '\0')
- ++name_last;
-
- /* copy one char MORE than the actual number to mark packed files,
- * i.e. packed revision file content uses different key space then
- * non-packed ones: keys for packed rev file content ends with a dot
- * for non-packed rev files they end with a digit. */
- name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
- return svn_fs_fs__combine_number_and_string(offset, name, pool);
-}
-
-/* Read the WINDOW_P for the rep state RS from the current FSFS session's
- * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
- * cache has been given. If a cache is available IS_CACHED will inform
- * the caller about the success of the lookup. Allocations (of the window
- * in particualar) will be made from POOL.
- *
- * If the information could be found, put RS and the position within the
- * rev file into the same state as if the data had just been read from it.
- */
-static svn_error_t *
-get_cached_window(svn_txdelta_window_t **window_p,
- struct rep_state *rs,
- svn_boolean_t *is_cached,
- apr_pool_t *pool)
-{
- if (! rs->window_cache)
- {
- /* txdelta window has not been enabled */
- *is_cached = FALSE;
- }
- else
- {
- /* ask the cache for the desired txdelta window */
- svn_fs_fs__txdelta_cached_window_t *cached_window;
- SVN_ERR(svn_cache__get((void **) &cached_window,
- is_cached,
- rs->window_cache,
- get_window_key(rs, rs->off, pool),
- pool));
-
- if (*is_cached)
- {
- /* found it. Pass it back to the caller. */
- *window_p = cached_window->window;
-
- /* manipulate the RS as if we just read the data */
- rs->chunk_index++;
- rs->off = cached_window->end_offset;
-
- /* manipulate the rev file as if we just read from it */
- SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Store the WINDOW read at OFFSET for the rep state RS in the current
- * FSFS session's cache. This will be a no-op if no cache has been given.
- * Temporary allocations will be made from SCRATCH_POOL. */
-static svn_error_t *
-set_cached_window(svn_txdelta_window_t *window,
- struct rep_state *rs,
- apr_off_t offset,
- apr_pool_t *scratch_pool)
-{
- if (rs->window_cache)
- {
- /* store the window and the first offset _past_ it */
- svn_fs_fs__txdelta_cached_window_t cached_window;
-
- cached_window.window = window;
- cached_window.end_offset = rs->off;
-
- /* but key it with the start offset because that is the known state
- * when we will look it up */
- return svn_cache__set(rs->window_cache,
- get_window_key(rs, offset, scratch_pool),
- &cached_window,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Read the WINDOW_P for the rep state RS from the current FSFS session's
- * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
- * cache has been given. If a cache is available IS_CACHED will inform
- * the caller about the success of the lookup. Allocations (of the window
- * in particualar) will be made from POOL.
- */
-static svn_error_t *
-get_cached_combined_window(svn_stringbuf_t **window_p,
- struct rep_state *rs,
- svn_boolean_t *is_cached,
- apr_pool_t *pool)
-{
- if (! rs->combined_cache)
- {
- /* txdelta window has not been enabled */
- *is_cached = FALSE;
- }
- else
- {
- /* ask the cache for the desired txdelta window */
- return svn_cache__get((void **)window_p,
- is_cached,
- rs->combined_cache,
- get_window_key(rs, rs->start, pool),
- pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Store the WINDOW read at OFFSET for the rep state RS in the current
- * FSFS session's cache. This will be a no-op if no cache has been given.
- * Temporary allocations will be made from SCRATCH_POOL. */
-static svn_error_t *
-set_cached_combined_window(svn_stringbuf_t *window,
- struct rep_state *rs,
- apr_off_t offset,
- apr_pool_t *scratch_pool)
-{
- if (rs->combined_cache)
- {
- /* but key it with the start offset because that is the known state
- * when we will look it up */
- return svn_cache__set(rs->combined_cache,
- get_window_key(rs, offset, scratch_pool),
- window,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Build an array of rep_state structures in *LIST giving the delta
- reps from first_rep to a plain-text or self-compressed rep. Set
- *SRC_STATE to the plain-text rep we find at the end of the chain,
- or to NULL if the final delta representation is self-compressed.
- The representation to start from is designated by filesystem FS, id
- ID, and representation REP.
- Also, set *WINDOW_P to the base window content for *LIST, if it
- could be found in cache. Otherwise, *LIST will contain the base
- representation for the whole delta chain.
- Finally, return the expanded size of the representation in
- *EXPANDED_SIZE. It will take care of cases where only the on-disk
- size is known. */
-static svn_error_t *
-build_rep_list(apr_array_header_t **list,
- svn_stringbuf_t **window_p,
- struct rep_state **src_state,
- svn_filesize_t *expanded_size,
- svn_fs_t *fs,
- representation_t *first_rep,
- apr_pool_t *pool)
-{
- representation_t rep;
- struct rep_state *rs = NULL;
- struct rep_args *rep_args;
- svn_boolean_t is_cached = FALSE;
- apr_file_t *last_file = NULL;
- svn_revnum_t last_revision;
-
- *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
- rep = *first_rep;
-
- /* The value as stored in the data struct.
- 0 is either for unknown length or actually zero length. */
- *expanded_size = first_rep->expanded_size;
-
- /* for the top-level rep, we need the rep_args */
- SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
- &last_revision, &rep, fs, pool));
-
- /* Unknown size or empty representation?
- That implies the this being the first iteration.
- Usually size equals on-disk size, except for empty,
- compressed representations (delta, size = 4).
- Please note that for all non-empty deltas have
- a 4-byte header _plus_ some data. */
- if (*expanded_size == 0)
- if (! rep_args->is_delta || first_rep->size != 4)
- *expanded_size = first_rep->size;
-
- while (1)
- {
- /* fetch state, if that has not been done already */
- if (!rs)
- SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
- &last_revision, &rep, fs, pool));
-
- SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
- if (is_cached)
- {
- /* We already have a reconstructed window in our cache.
- Write a pseudo rep_state with the full length. */
- rs->off = rs->start;
- rs->end = rs->start + (*window_p)->len;
- *src_state = rs;
- return SVN_NO_ERROR;
- }
-
- if (!rep_args->is_delta)
- {
- /* This is a plaintext, so just return the current rep_state. */
- *src_state = rs;
- return SVN_NO_ERROR;
- }
-
- /* Push this rep onto the list. If it's self-compressed, we're done. */
- APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
- if (rep_args->is_delta_vs_empty)
- {
- *src_state = NULL;
- return SVN_NO_ERROR;
- }
-
- rep.revision = rep_args->base_revision;
- rep.offset = rep_args->base_offset;
- rep.size = rep_args->base_length;
- rep.txn_id = NULL;
-
- rs = NULL;
- }
-}
-
-
-/* Create a rep_read_baton structure for node revision NODEREV in
- filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not
- NULL, it is the rep's key in the fulltext cache, and a stringbuf
- must be allocated to store the text. Perform all allocations in
- POOL. If rep is mutable, it must be for file contents. */
-static svn_error_t *
-rep_read_get_baton(struct rep_read_baton **rb_p,
- svn_fs_t *fs,
- representation_t *rep,
- pair_cache_key_t fulltext_cache_key,
- apr_pool_t *pool)
-{
- struct rep_read_baton *b;
-
- b = apr_pcalloc(pool, sizeof(*b));
- b->fs = fs;
- b->base_window = NULL;
- b->chunk_index = 0;
- b->buf = NULL;
- b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
- b->checksum_finalized = FALSE;
- b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
- b->len = rep->expanded_size;
- b->off = 0;
- b->fulltext_cache_key = fulltext_cache_key;
- b->pool = svn_pool_create(pool);
- b->filehandle_pool = svn_pool_create(pool);
-
- SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
- &b->src_state, &b->len, fs, rep,
- b->filehandle_pool));
-
- if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
- b->current_fulltext = svn_stringbuf_create_ensure
- ((apr_size_t)b->len,
- b->filehandle_pool);
- else
- b->current_fulltext = NULL;
-
- /* Save our output baton. */
- *rb_p = b;
-
- return SVN_NO_ERROR;
-}
-
-/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
- window into *NWIN. */
-static svn_error_t *
-read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
- struct rep_state *rs, apr_pool_t *pool)
-{
- svn_stream_t *stream;
- svn_boolean_t is_cached;
- apr_off_t old_offset;
-
- SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
-
- /* RS->FILE may be shared between RS instances -> make sure we point
- * to the right data. */
- SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-
- /* Skip windows to reach the current chunk if we aren't there yet. */
- while (rs->chunk_index < this_chunk)
- {
- SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
- rs->chunk_index++;
- SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
- if (rs->off >= rs->end)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Reading one svndiff window read "
- "beyond the end of the "
- "representation"));
- }
-
- /* Read the next window. But first, try to find it in the cache. */
- SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
- if (is_cached)
- return SVN_NO_ERROR;
-
- /* Actually read the next window. */
- old_offset = rs->off;
- stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
- SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
- rs->chunk_index++;
- SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
-
- if (rs->off > rs->end)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Reading one svndiff window read beyond "
- "the end of the representation"));
-
- /* the window has not been cached before, thus cache it now
- * (if caching is used for them at all) */
- return set_cached_window(*nwin, rs, old_offset, pool);
-}
-
-/* Read SIZE bytes from the representation RS and return it in *NWIN. */
-static svn_error_t *
-read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
- apr_size_t size, apr_pool_t *pool)
-{
- /* RS->FILE may be shared between RS instances -> make sure we point
- * to the right data. */
- SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-
- /* Read the plain data. */
- *nwin = svn_stringbuf_create_ensure(size, pool);
- SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
- pool));
- (*nwin)->data[size] = 0;
-
- /* Update RS. */
- rs->off += (apr_off_t)size;
-
- return SVN_NO_ERROR;
-}
-
-/* Get the undeltified window that is a result of combining all deltas
- from the current desired representation identified in *RB with its
- base representation. Store the window in *RESULT. */
-static svn_error_t *
-get_combined_window(svn_stringbuf_t **result,
- struct rep_read_baton *rb)
-{
- apr_pool_t *pool, *new_pool, *window_pool;
- int i;
- svn_txdelta_window_t *window;
- apr_array_header_t *windows;
- svn_stringbuf_t *source, *buf = rb->base_window;
- struct rep_state *rs;
-
- /* Read all windows that we need to combine. This is fine because
- the size of each window is relatively small (100kB) and skip-
- delta limits the number of deltas in a chain to well under 100.
- Stop early if one of them does not depend on its predecessors. */
- window_pool = svn_pool_create(rb->pool);
- windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
- for (i = 0; i < rb->rs_list->nelts; ++i)
- {
- rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
- SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
-
- APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
- if (window->src_ops == 0)
- {
- ++i;
- break;
- }
- }
-
- /* Combine in the windows from the other delta reps. */
- pool = svn_pool_create(rb->pool);
- for (--i; i >= 0; --i)
- {
-
- rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
- window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
-
- /* Maybe, we've got a PLAIN start representation. If we do, read
- as much data from it as the needed for the txdelta window's source
- view.
- Note that BUF / SOURCE may only be NULL in the first iteration. */
- source = buf;
- if (source == NULL && rb->src_state != NULL)
- SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
- pool));
-
- /* Combine this window with the current one. */
- new_pool = svn_pool_create(rb->pool);
- buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
- buf->len = window->tview_len;
-
- svn_txdelta_apply_instructions(window, source ? source->data : NULL,
- buf->data, &buf->len);
- if (buf->len != window->tview_len)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("svndiff window length is "
- "corrupt"));
-
- /* Cache windows only if the whole rep content could be read as a
- single chunk. Only then will no other chunk need a deeper RS
- list than the cached chunk. */
- if ((rb->chunk_index == 0) && (rs->off == rs->end))
- SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
-
- /* Cycle pools so that we only need to hold three windows at a time. */
- svn_pool_destroy(pool);
- pool = new_pool;
- }
-
- svn_pool_destroy(window_pool);
-
- *result = buf;
- return SVN_NO_ERROR;
-}
-
-/* Returns whether or not the expanded fulltext of the file is cachable
- * based on its size SIZE. The decision depends on the cache used by RB.
- */
-static svn_boolean_t
-fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
-{
- return (size < APR_SIZE_MAX)
- && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
-}
-
-/* Close method used on streams returned by read_representation().
- */
-static svn_error_t *
-rep_read_contents_close(void *baton)
-{
- struct rep_read_baton *rb = baton;
-
- svn_pool_destroy(rb->pool);
- svn_pool_destroy(rb->filehandle_pool);
-
- return SVN_NO_ERROR;
-}
-
-/* Return the next *LEN bytes of the rep and store them in *BUF. */
-static svn_error_t *
-get_contents(struct rep_read_baton *rb,
- char *buf,
- apr_size_t *len)
-{
- apr_size_t copy_len, remaining = *len;
- char *cur = buf;
- struct rep_state *rs;
-
- /* Special case for when there are no delta reps, only a plain
- text. */
- if (rb->rs_list->nelts == 0)
- {
- copy_len = remaining;
- rs = rb->src_state;
-
- if (rb->base_window != NULL)
- {
- /* We got the desired rep directly from the cache.
- This is where we need the pseudo rep_state created
- by build_rep_list(). */
- apr_size_t offset = (apr_size_t)(rs->off - rs->start);
- if (copy_len + offset > rb->base_window->len)
- copy_len = offset < rb->base_window->len
- ? rb->base_window->len - offset
- : 0ul;
-
- memcpy (cur, rb->base_window->data + offset, copy_len);
- }
- else
- {
- if (((apr_off_t) copy_len) > rs->end - rs->off)
- copy_len = (apr_size_t) (rs->end - rs->off);
- SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
- NULL, rb->pool));
- }
-
- rs->off += copy_len;
- *len = copy_len;
- return SVN_NO_ERROR;
- }
-
- while (remaining > 0)
- {
- /* If we have buffered data from a previous chunk, use that. */
- if (rb->buf)
- {
- /* Determine how much to copy from the buffer. */
- copy_len = rb->buf_len - rb->buf_pos;
- if (copy_len > remaining)
- copy_len = remaining;
-
- /* Actually copy the data. */
- memcpy(cur, rb->buf + rb->buf_pos, copy_len);
- rb->buf_pos += copy_len;
- cur += copy_len;
- remaining -= copy_len;
-
- /* If the buffer is all used up, clear it and empty the
- local pool. */
- if (rb->buf_pos == rb->buf_len)
- {
- svn_pool_clear(rb->pool);
- rb->buf = NULL;
- }
- }
- else
- {
- svn_stringbuf_t *sbuf = NULL;
-
- rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
- if (rs->off == rs->end)
- break;
-
- /* Get more buffered data by evaluating a chunk. */
- SVN_ERR(get_combined_window(&sbuf, rb));
-
- rb->chunk_index++;
- rb->buf_len = sbuf->len;
- rb->buf = sbuf->data;
- rb->buf_pos = 0;
- }
- }
-
- *len = cur - buf;
-
- return SVN_NO_ERROR;
-}
-
-/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
- representation and store them in *BUF. Sum as we read and verify
- the MD5 sum at the end. */
-static svn_error_t *
-rep_read_contents(void *baton,
- char *buf,
- apr_size_t *len)
-{
- struct rep_read_baton *rb = baton;
-
- /* Get the next block of data. */
- SVN_ERR(get_contents(rb, buf, len));
-
- if (rb->current_fulltext)
- svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
-
- /* Perform checksumming. We want to check the checksum as soon as
- the last byte of data is read, in case the caller never performs
- a short read, but we don't want to finalize the MD5 context
- twice. */
- if (!rb->checksum_finalized)
- {
- SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
- rb->off += *len;
- if (rb->off == rb->len)
- {
- svn_checksum_t *md5_checksum;
-
- rb->checksum_finalized = TRUE;
- SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
- rb->pool));
- if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
- return svn_error_create(SVN_ERR_FS_CORRUPT,
- svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
- rb->pool,
- _("Checksum mismatch while reading representation")),
- NULL);
- }
- }
-
- if (rb->off == rb->len && rb->current_fulltext)
- {
- fs_fs_data_t *ffd = rb->fs->fsap_data;
- SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
- rb->current_fulltext, rb->pool));
- rb->current_fulltext = NULL;
- }
-
- return SVN_NO_ERROR;
-}
-
-
-/* Return a stream in *CONTENTS_P that will read the contents of a
- representation stored at the location given by REP. Appropriate
- for any kind of immutable representation, but only for file
- contents (not props or directory contents) in mutable
- representations.
-
- If REP is NULL, the representation is assumed to be empty, and the
- empty stream is returned.
-*/
-static svn_error_t *
-read_representation(svn_stream_t **contents_p,
- svn_fs_t *fs,
- representation_t *rep,
- apr_pool_t *pool)
-{
- if (! rep)
- {
- *contents_p = svn_stream_empty(pool);
- }
- else
- {
- fs_fs_data_t *ffd = fs->fsap_data;
- pair_cache_key_t fulltext_cache_key = { 0 };
- svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
- struct rep_read_baton *rb;
-
- fulltext_cache_key.revision = rep->revision;
- fulltext_cache_key.second = rep->offset;
- if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
- && fulltext_size_is_cachable(ffd, len))
- {
- svn_stringbuf_t *fulltext;
- svn_boolean_t is_cached;
- SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
- ffd->fulltext_cache, &fulltext_cache_key,
- pool));
- if (is_cached)
- {
- *contents_p = svn_stream_from_stringbuf(fulltext, pool);
- return SVN_NO_ERROR;
- }
- }
- else
- fulltext_cache_key.revision = SVN_INVALID_REVNUM;
-
- SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
-
- *contents_p = svn_stream_create(rb, pool);
- svn_stream_set_read(*contents_p, rep_read_contents);
- svn_stream_set_close(*contents_p, rep_read_contents_close);
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__get_contents(svn_stream_t **contents_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- return read_representation(contents_p, fs, noderev->data_rep, pool);
-}
-
-/* Baton used when reading delta windows. */
-struct delta_read_baton
-{
- struct rep_state *rs;
- svn_checksum_t *checksum;
-};
-
-/* This implements the svn_txdelta_next_window_fn_t interface. */
-static svn_error_t *
-delta_read_next_window(svn_txdelta_window_t **window, void *baton,
- apr_pool_t *pool)
-{
- struct delta_read_baton *drb = baton;
-
- if (drb->rs->off == drb->rs->end)
- {
- *window = NULL;
- return SVN_NO_ERROR;
- }
-
- return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
-}
-
-/* This implements the svn_txdelta_md5_digest_fn_t interface. */
-static const unsigned char *
-delta_read_md5_digest(void *baton)
-{
- struct delta_read_baton *drb = baton;
-
- if (drb->checksum->kind == svn_checksum_md5)
- return drb->checksum->digest;
- else
- return NULL;
-}
-
-svn_error_t *
-svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
- svn_fs_t *fs,
- node_revision_t *source,
- node_revision_t *target,
- apr_pool_t *pool)
-{
- svn_stream_t *source_stream, *target_stream;
-
- /* Try a shortcut: if the target is stored as a delta against the source,
- then just use that delta. */
- if (source && source->data_rep && target->data_rep)
- {
- struct rep_state *rep_state;
- struct rep_args *rep_args;
-
- /* Read target's base rep if any. */
- SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
- target->data_rep, fs, pool));
-
- /* If that matches source, then use this delta as is.
- Note that we want an actual delta here. E.g. a self-delta would
- not be good enough. */
- if (rep_args->is_delta
- && rep_args->base_revision == source->data_rep->revision
- && rep_args->base_offset == source->data_rep->offset)
- {
- /* Create the delta read baton. */
- struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
- drb->rs = rep_state;
- drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
- pool);
- *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
- delta_read_md5_digest, pool);
- return SVN_NO_ERROR;
- }
- else
- SVN_ERR(svn_io_file_close(rep_state->file, pool));
- }
-
- /* Read both fulltexts and construct a delta. */
- if (source)
- SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
- else
- source_stream = svn_stream_empty(pool);
- SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
-
- /* Because source and target stream will already verify their content,
- * there is no need to do this once more. In particular if the stream
- * content is being fetched from cache. */
- svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
-
- return SVN_NO_ERROR;
-}
-
-/* Baton for cache_access_wrapper. Wraps the original parameters of
- * svn_fs_fs__try_process_file_content().
- */
-typedef struct cache_access_wrapper_baton_t
-{
- svn_fs_process_contents_func_t func;
- void* baton;
-} cache_access_wrapper_baton_t;
-
-/* Wrapper to translate between svn_fs_process_contents_func_t and
- * svn_cache__partial_getter_func_t.
- */
-static svn_error_t *
-cache_access_wrapper(void **out,
- const void *data,
- apr_size_t data_len,
- void *baton,
- apr_pool_t *pool)
-{
- cache_access_wrapper_baton_t *wrapper_baton = baton;
-
- SVN_ERR(wrapper_baton->func((const unsigned char *)data,
- data_len - 1, /* cache adds terminating 0 */
- wrapper_baton->baton,
- pool));
-
- /* non-NULL value to signal the calling cache that all went well */
- *out = baton;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
- svn_fs_t *fs,
- node_revision_t *noderev,
- svn_fs_process_contents_func_t processor,
- void* baton,
- apr_pool_t *pool)
-{
- representation_t *rep = noderev->data_rep;
- if (rep)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
- pair_cache_key_t fulltext_cache_key = { 0 };
-
- fulltext_cache_key.revision = rep->revision;
- fulltext_cache_key.second = rep->offset;
- if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
- && fulltext_size_is_cachable(ffd, rep->expanded_size))
- {
- cache_access_wrapper_baton_t wrapper_baton;
- void *dummy = NULL;
-
- wrapper_baton.func = processor;
- wrapper_baton.baton = baton;
- return svn_cache__get_partial(&dummy, success,
- ffd->fulltext_cache,
- &fulltext_cache_key,
- cache_access_wrapper,
- &wrapper_baton,
- pool);
- }
- }
-
- *success = FALSE;
- return SVN_NO_ERROR;
-}
-
-/* Fetch the contents of a directory into ENTRIES. Values are stored
- as filename to string mappings; further conversion is necessary to
- convert them into svn_fs_dirent_t values. */
-static svn_error_t *
-get_dir_contents(apr_hash_t *entries,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- svn_stream_t *contents;
-
- if (noderev->data_rep && noderev->data_rep->txn_id)
- {
- const char *filename = path_txn_node_children(fs, noderev->id, pool);
-
- /* The representation is mutable. Read the old directory
- contents from the mutable children file, followed by the
- changes we've made in this transaction. */
- SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
- SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
- SVN_ERR(svn_stream_close(contents));
- }
- else if (noderev->data_rep)
- {
- /* use a temporary pool for temp objects.
- * Also undeltify content before parsing it. Otherwise, we could only
- * parse it byte-by-byte.
- */
- apr_pool_t *text_pool = svn_pool_create(pool);
- apr_size_t len = noderev->data_rep->expanded_size
- ? (apr_size_t)noderev->data_rep->expanded_size
- : (apr_size_t)noderev->data_rep->size;
- svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
- text->len = len;
-
- /* The representation is immutable. Read it normally. */
- SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
- SVN_ERR(svn_stream_read(contents, text->data, &text->len));
- SVN_ERR(svn_stream_close(contents));
-
- /* de-serialize hash */
- contents = svn_stream_from_stringbuf(text, text_pool);
- SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
-
- svn_pool_destroy(text_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-
-static const char *
-unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
- apr_pool_t *pool)
-{
- return apr_psprintf(pool, "%s %s",
- (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
- svn_fs_fs__id_unparse(id, pool)->data);
-}
-
-/* Given a hash ENTRIES of dirent structions, return a hash in
- *STR_ENTRIES_P, that has svn_string_t as the values in the format
- specified by the fs_fs directory contents file. Perform
- allocations in POOL. */
-static svn_error_t *
-unparse_dir_entries(apr_hash_t **str_entries_p,
- apr_hash_t *entries,
- apr_pool_t *pool)
-{
- apr_hash_index_t *hi;
-
- /* For now, we use a our own hash function to ensure that we get a
- * (largely) stable order when serializing the data. It also gives
- * us some performance improvement.
- *
- * ### TODO ###
- * Use some sorted or other fixed order data container.
- */
- *str_entries_p = svn_hash__make(pool);
-
- for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
- {
- const void *key;
- apr_ssize_t klen;
- svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
- const char *new_val;
-
- apr_hash_this(hi, &key, &klen, NULL);
- new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
- apr_hash_set(*str_entries_p, key, klen,
- svn_string_create(new_val, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-
-/* Given a hash STR_ENTRIES with values as svn_string_t as specified
- in an FSFS directory contents listing, return a hash of dirents in
- *ENTRIES_P. Perform allocations in POOL. */
-static svn_error_t *
-parse_dir_entries(apr_hash_t **entries_p,
- apr_hash_t *str_entries,
- const char *unparsed_id,
- apr_pool_t *pool)
-{
- apr_hash_index_t *hi;
-
- *entries_p = apr_hash_make(pool);
+ svn_checksum_t *empty_md5
+ = svn_checksum_empty_checksum(svn_checksum_md5, pool);
- /* Translate the string dir entries into real entries. */
- for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
- {
- const char *name = svn__apr_hash_index_key(hi);
- svn_string_t *str_val = svn__apr_hash_index_val(hi);
- char *str, *last_str;
- svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
-
- last_str = apr_pstrdup(pool, str_val->data);
- dirent->name = apr_pstrdup(pool, name);
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (str == NULL)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt in '%s'"),
- unparsed_id);
-
- if (strcmp(str, KIND_FILE) == 0)
+ if (memcmp(empty_md5->digest, data_rep->md5_digest,
+ sizeof(data_rep->md5_digest)))
{
- dirent->kind = svn_node_file;
- }
- else if (strcmp(str, KIND_DIR) == 0)
- {
- dirent->kind = svn_node_dir;
+ /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
+ actual file length. */
+ *length = data_rep->size;
}
else
{
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt in '%s'"),
- unparsed_id);
- }
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (str == NULL)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt in '%s'"),
- unparsed_id);
-
- dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
-
- svn_hash_sets(*entries_p, dirent->name, dirent);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Return the cache object in FS responsible to storing the directory
- * the NODEREV. If none exists, return NULL. */
-static svn_cache__t *
-locate_dir_cache(svn_fs_t *fs,
- node_revision_t *noderev)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- return svn_fs_fs__id_txn_id(noderev->id)
- ? ffd->txn_dir_cache
- : ffd->dir_cache;
-}
-
-svn_error_t *
-svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- const char *unparsed_id = NULL;
- apr_hash_t *unparsed_entries, *parsed_entries;
-
- /* find the cache we may use */
- svn_cache__t *cache = locate_dir_cache(fs, noderev);
- if (cache)
- {
- svn_boolean_t found;
-
- unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
- SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
- unparsed_id, pool));
- if (found)
- return SVN_NO_ERROR;
- }
-
- /* Read in the directory hash. */
- unparsed_entries = apr_hash_make(pool);
- SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
- SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
- unparsed_id, pool));
-
- /* Update the cache, if we are to use one. */
- if (cache)
- SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
-
- *entries_p = parsed_entries;
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
- svn_fs_t *fs,
- node_revision_t *noderev,
- const char *name,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- svn_boolean_t found = FALSE;
-
- /* find the cache we may use */
- svn_cache__t *cache = locate_dir_cache(fs, noderev);
- if (cache)
- {
- const char *unparsed_id =
- svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
-
- /* Cache lookup. */
- SVN_ERR(svn_cache__get_partial((void **)dirent,
- &found,
- cache,
- unparsed_id,
- svn_fs_fs__extract_dir_entry,
- (void*)name,
- result_pool));
- }
-
- /* fetch data from disk if we did not find it in the cache */
- if (! found)
- {
- apr_hash_t *entries;
- svn_fs_dirent_t *entry;
- svn_fs_dirent_t *entry_copy = NULL;
-
- /* read the dir from the file system. It will probably be put it
- into the cache for faster lookup in future calls. */
- SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
- scratch_pool));
-
- /* find desired entry and return a copy in POOL, if found */
- entry = svn_hash_gets(entries, name);
- if (entry != NULL)
- {
- entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
- entry_copy->name = apr_pstrdup(result_pool, entry->name);
- entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
- entry_copy->kind = entry->kind;
+ /* Contents is empty. */
+ *length = 0;
}
-
- *dirent = entry_copy;
}
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- apr_hash_t *proplist;
- svn_stream_t *stream;
-
- if (noderev->prop_rep && noderev->prop_rep->txn_id)
- {
- const char *filename = path_txn_node_props(fs, noderev->id, pool);
- proplist = apr_hash_make(pool);
-
- SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
- SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
- }
- else if (noderev->prop_rep)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
- representation_t *rep = noderev->prop_rep;
- pair_cache_key_t key = { 0 };
-
- key.revision = rep->revision;
- key.second = rep->offset;
- if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
- {
- svn_boolean_t is_cached;
- SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
- ffd->properties_cache, &key, pool));
- if (is_cached)
- return SVN_NO_ERROR;
- }
-
- proplist = apr_hash_make(pool);
- SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
- SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
-
- if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
- SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
- }
- else
- {
- /* return an empty prop list if the node doesn't have any props */
- proplist = apr_hash_make(pool);
- }
-
- *proplist_p = proplist;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__file_length(svn_filesize_t *length,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- if (noderev->data_rep)
- *length = noderev->data_rep->expanded_size;
- else
- *length = 0;
-
- return SVN_NO_ERROR;
-}
-
svn_boolean_t
svn_fs_fs__noderev_same_rep_key(representation_t *a,
representation_t *b)
@@ -5822,3035 +1425,299 @@ svn_fs_fs__noderev_same_rep_key(representation_t *a,
if (a == NULL || b == NULL)
return FALSE;
- if (a->offset != b->offset)
+ if (a->item_index != b->item_index)
return FALSE;
if (a->revision != b->revision)
return FALSE;
- if (a->uniquifier == b->uniquifier)
- return TRUE;
-
- if (a->uniquifier == NULL || b->uniquifier == NULL)
- return FALSE;
-
- return strcmp(a->uniquifier, b->uniquifier) == 0;
+ return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
}
svn_error_t *
-svn_fs_fs__file_checksum(svn_checksum_t **checksum,
- node_revision_t *noderev,
- svn_checksum_kind_t kind,
- apr_pool_t *pool)
-{
- if (noderev->data_rep)
- {
- switch(kind)
- {
- case svn_checksum_md5:
- *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
- pool);
- break;
- case svn_checksum_sha1:
- *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
- pool);
- break;
- default:
- *checksum = NULL;
- }
+svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
+ svn_fs_t *fs,
+ node_revision_t *a,
+ node_revision_t *b,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *contents_a, *contents_b;
+ representation_t *rep_a = a->data_rep;
+ representation_t *rep_b = b->data_rep;
+ svn_boolean_t a_empty = !rep_a;
+ svn_boolean_t b_empty = !rep_b;
+
+ /* This makes sure that neither rep will be NULL later on */
+ if (a_empty && b_empty)
+ {
+ *equal = TRUE;
+ return SVN_NO_ERROR;
}
- else
- *checksum = NULL;
-
- return SVN_NO_ERROR;
-}
-
-representation_t *
-svn_fs_fs__rep_copy(representation_t *rep,
- apr_pool_t *pool)
-{
- representation_t *rep_new;
-
- if (rep == NULL)
- return NULL;
-
- rep_new = apr_pcalloc(pool, sizeof(*rep_new));
-
- memcpy(rep_new, rep, sizeof(*rep_new));
- rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
- rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
- rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
-
- return rep_new;
-}
-/* Merge the internal-use-only CHANGE into a hash of public-FS
- svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
- single summarical (is that real word?) change per path. Also keep
- the COPYFROM_CACHE up to date with new adds and replaces. */
-static svn_error_t *
-fold_change(apr_hash_t *changes,
- const change_t *change,
- apr_hash_t *copyfrom_cache)
-{
- apr_pool_t *pool = apr_hash_pool_get(changes);
- svn_fs_path_change2_t *old_change, *new_change;
- const char *path;
- apr_size_t path_len = strlen(change->path);
-
- if ((old_change = apr_hash_get(changes, change->path, path_len)))
+ /* Same path in same rev or txn? */
+ if (svn_fs_fs__id_eq(a->id, b->id))
{
- /* This path already exists in the hash, so we have to merge
- this change into the already existing one. */
-
- /* Sanity check: only allow NULL node revision ID in the
- `reset' case. */
- if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
- return svn_error_create
- (SVN_ERR_FS_CORRUPT, NULL,
- _("Missing required node revision ID"));
-
- /* Sanity check: we should be talking about the same node
- revision ID as our last change except where the last change
- was a deletion. */
- if (change->noderev_id
- && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
- && (old_change->change_kind != svn_fs_path_change_delete))
- return svn_error_create
- (SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid change ordering: new node revision ID "
- "without delete"));
-
- /* Sanity check: an add, replacement, or reset must be the first
- thing to follow a deletion. */
- if ((old_change->change_kind == svn_fs_path_change_delete)
- && (! ((change->kind == svn_fs_path_change_replace)
- || (change->kind == svn_fs_path_change_reset)
- || (change->kind == svn_fs_path_change_add))))
- return svn_error_create
- (SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid change ordering: non-add change on deleted path"));
-
- /* Sanity check: an add can't follow anything except
- a delete or reset. */
- if ((change->kind == svn_fs_path_change_add)
- && (old_change->change_kind != svn_fs_path_change_delete)
- && (old_change->change_kind != svn_fs_path_change_reset))
- return svn_error_create
- (SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid change ordering: add change on preexisting path"));
-
- /* Now, merge that change in. */
- switch (change->kind)
- {
- case svn_fs_path_change_reset:
- /* A reset here will simply remove the path change from the
- hash. */
- old_change = NULL;
- break;
-
- case svn_fs_path_change_delete:
- if (old_change->change_kind == svn_fs_path_change_add)
- {
- /* If the path was introduced in this transaction via an
- add, and we are deleting it, just remove the path
- altogether. */
- old_change = NULL;
- }
- else
- {
- /* A deletion overrules all previous changes. */
- old_change->change_kind = svn_fs_path_change_delete;
- old_change->text_mod = change->text_mod;
- old_change->prop_mod = change->prop_mod;
- old_change->copyfrom_rev = SVN_INVALID_REVNUM;
- old_change->copyfrom_path = NULL;
- }
- break;
-
- case svn_fs_path_change_add:
- case svn_fs_path_change_replace:
- /* An add at this point must be following a previous delete,
- so treat it just like a replace. */
- old_change->change_kind = svn_fs_path_change_replace;
- old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
- pool);
- old_change->text_mod = change->text_mod;
- old_change->prop_mod = change->prop_mod;
- if (change->copyfrom_rev == SVN_INVALID_REVNUM)
- {
- old_change->copyfrom_rev = SVN_INVALID_REVNUM;
- old_change->copyfrom_path = NULL;
- }
- else
- {
- old_change->copyfrom_rev = change->copyfrom_rev;
- old_change->copyfrom_path = apr_pstrdup(pool,
- change->copyfrom_path);
- }
- break;
-
- case svn_fs_path_change_modify:
- default:
- if (change->text_mod)
- old_change->text_mod = TRUE;
- if (change->prop_mod)
- old_change->prop_mod = TRUE;
- break;
- }
-
- /* Point our new_change to our (possibly modified) old_change. */
- new_change = old_change;
- }
- else
- {
- /* This change is new to the hash, so make a new public change
- structure from the internal one (in the hash's pool), and dup
- the path into the hash's pool, too. */
- new_change = apr_pcalloc(pool, sizeof(*new_change));
- new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
- new_change->change_kind = change->kind;
- new_change->text_mod = change->text_mod;
- new_change->prop_mod = change->prop_mod;
- /* In FSFS, copyfrom_known is *always* true, since we've always
- * stored copyfroms in changed paths lists. */
- new_change->copyfrom_known = TRUE;
- if (change->copyfrom_rev != SVN_INVALID_REVNUM)
- {
- new_change->copyfrom_rev = change->copyfrom_rev;
- new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
- }
- else
- {
- new_change->copyfrom_rev = SVN_INVALID_REVNUM;
- new_change->copyfrom_path = NULL;
- }
- }
-
- if (new_change)
- new_change->node_kind = change->node_kind;
-
- /* Add (or update) this path.
-
- Note: this key might already be present, and it would be nice to
- re-use its value, but there is no way to fetch it. The API makes no
- guarantees that this (new) key will not be retained. Thus, we (again)
- copy the key into the target pool to ensure a proper lifetime. */
- path = apr_pstrmemdup(pool, change->path, path_len);
- apr_hash_set(changes, path, path_len, new_change);
-
- /* Update the copyfrom cache, if any. */
- if (copyfrom_cache)
- {
- apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
- const char *copyfrom_string = NULL, *copyfrom_key = path;
- if (new_change)
- {
- if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
- copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
- new_change->copyfrom_rev,
- new_change->copyfrom_path);
- else
- copyfrom_string = "";
- }
- /* We need to allocate a copy of the key in the copyfrom_pool if
- * we're not doing a deletion and if it isn't already there. */
- if ( copyfrom_string
- && ( ! apr_hash_count(copyfrom_cache)
- || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
- copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
-
- apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
- copyfrom_string);
+ *equal = TRUE;
+ return SVN_NO_ERROR;
}
- return SVN_NO_ERROR;
-}
-
-/* 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
-
-/* 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,
- apr_file_t *file,
- apr_pool_t *pool)
-{
- char buf[MAX_CHANGE_LINE_LEN];
- apr_size_t len = sizeof(buf);
- change_t *change;
- char *str, *last_str = buf, *kind_str;
- svn_error_t *err;
-
- /* Default return value. */
- *change_p = NULL;
-
- err = svn_io_read_length_line(file, buf, &len, pool);
-
- /* Check for a blank line. */
- if (err || (len == 0))
+ /* Beware of the combination NULL rep and possibly empty rep.
+ * Due to EXPANDED_SIZE not being reliable, we can't easily detect empty
+ * reps. So, we can only take further shortcuts if both reps are given. */
+ if (!a_empty && !b_empty)
{
- if (err && APR_STATUS_IS_EOF(err->apr_err))
+ /* File text representations always know their checksums -
+ * even in a txn. */
+ if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
+ sizeof(rep_a->md5_digest)))
{
- svn_error_clear(err);
+ *equal = FALSE;
return SVN_NO_ERROR;
}
- if ((len == 0) && (! err))
- return SVN_NO_ERROR;
- return svn_error_trace(err);
- }
-
- change = apr_pcalloc(pool, sizeof(*change));
-
- /* 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, KIND_FILE) == 0)
- change->node_kind = svn_node_file;
- else if (strcmp(kind_str, 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. */
- len = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
-
- if (len == 0)
- {
- change->copyfrom_rev = SVN_INVALID_REVNUM;
- change->copyfrom_path = NULL;
- }
- else
- {
- last_str = buf;
- 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;
-}
-
-/* Examine all the changed path entries in CHANGES and store them in
- *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
- *data. Store a hash of paths to copyfrom "REV PATH" strings in
- COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
- the changed-path entries have already been folded (by
- write_final_changed_path_info) and may be out of order, so we shouldn't
- remove children of replaced or deleted directories. Do all
- allocations in POOL. */
-static svn_error_t *
-process_changes(apr_hash_t *changed_paths,
- apr_hash_t *copyfrom_cache,
- apr_array_header_t *changes,
- svn_boolean_t prefolded,
- apr_pool_t *pool)
-{
- apr_pool_t *iterpool = svn_pool_create(pool);
- int i;
-
- /* Read in the changes one by one, folding them into our local hash
- as necessary. */
-
- for (i = 0; i < changes->nelts; ++i)
- {
- change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
-
- SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
-
- /* Now, if our change was a deletion or replacement, we have to
- blow away any changes thus far on paths that are (or, were)
- children of this path.
- ### i won't bother with another iteration pool here -- at
- most we talking about a few extra dups of paths into what
- is already a temporary subpool.
- */
-
- if (((change->kind == svn_fs_path_change_delete)
- || (change->kind == svn_fs_path_change_replace))
- && ! prefolded)
- {
- apr_hash_index_t *hi;
-
- /* a potential child path must contain at least 2 more chars
- (the path separator plus at least one char for the name).
- Also, we should not assume that all paths have been normalized
- i.e. some might have trailing path separators.
- */
- apr_ssize_t change_path_len = strlen(change->path);
- apr_ssize_t min_child_len = change_path_len == 0
- ? 1
- : change->path[change_path_len-1] == '/'
- ? change_path_len + 1
- : change_path_len + 2;
-
- /* CAUTION: This is the inner loop of an O(n^2) algorithm.
- The number of changes to process may be >> 1000.
- Therefore, keep the inner loop as tight as possible.
- */
- for (hi = apr_hash_first(iterpool, changed_paths);
- hi;
- hi = apr_hash_next(hi))
- {
- /* KEY is the path. */
- const void *path;
- apr_ssize_t klen;
- apr_hash_this(hi, &path, &klen, NULL);
-
- /* If we come across a child of our path, remove it.
- Call svn_dirent_is_child only if there is a chance that
- this is actually a sub-path.
- */
- if ( klen >= min_child_len
- && svn_dirent_is_child(change->path, path, iterpool))
- apr_hash_set(changed_paths, path, klen, NULL);
- }
- }
-
- /* Clear the per-iteration subpool. */
- svn_pool_clear(iterpool);
- }
-
- /* Destroy the per-iteration subpool. */
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Fetch all the changes from FILE and store them in *CHANGES. Do all
- allocations in POOL. */
-static svn_error_t *
-read_all_changes(apr_array_header_t **changes,
- apr_file_t *file,
- 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, file, pool));
- while (change)
- {
- APR_ARRAY_PUSH(*changes, change_t*) = change;
- SVN_ERR(read_change(&change, file, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- apr_file_t *file;
- apr_hash_t *changed_paths = apr_hash_make(pool);
- apr_array_header_t *changes;
- apr_pool_t *scratch_pool = svn_pool_create(pool);
-
- SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- SVN_ERR(read_all_changes(&changes, file, scratch_pool));
- SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
- svn_pool_destroy(scratch_pool);
-
- SVN_ERR(svn_io_file_close(file, pool));
-
- *changed_paths_p = changed_paths;
-
- return SVN_NO_ERROR;
-}
-
-/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
- * Allocate the result in POOL.
- */
-static svn_error_t *
-get_changes(apr_array_header_t **changes,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- apr_off_t changes_offset;
- apr_file_t *revision_file;
- svn_boolean_t found;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* try cache lookup first */
-
- if (ffd->changes_cache)
- {
- SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
- &rev, pool));
- if (found)
- return SVN_NO_ERROR;
- }
-
- /* read changes from revision file */
-
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
-
- SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
-
- SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
- rev, pool));
-
- SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
- SVN_ERR(read_all_changes(changes, revision_file, pool));
-
- SVN_ERR(svn_io_file_close(revision_file, pool));
-
- /* cache for future reference */
-
- if (ffd->changes_cache)
- SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
-
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *copyfrom_cache,
- apr_pool_t *pool)
-{
- apr_hash_t *changed_paths;
- apr_array_header_t *changes;
- apr_pool_t *scratch_pool = svn_pool_create(pool);
-
- SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
-
- changed_paths = svn_hash__make(pool);
-
- SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
- TRUE, pool));
- svn_pool_destroy(scratch_pool);
- *changed_paths_p = changed_paths;
-
- return SVN_NO_ERROR;
-}
-
-/* Copy a revision node-rev SRC into the current transaction TXN_ID in
- the filesystem FS. This is only used to create the root of a transaction.
- Allocations are from POOL. */
-static svn_error_t *
-create_new_txn_noderev_from_rev(svn_fs_t *fs,
- const char *txn_id,
- svn_fs_id_t *src,
- apr_pool_t *pool)
-{
- node_revision_t *noderev;
- const char *node_id, *copy_id;
-
- SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
-
- if (svn_fs_fs__id_txn_id(noderev->id))
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Copying from transactions not allowed"));
-
- noderev->predecessor_id = noderev->id;
- noderev->predecessor_count++;
- noderev->copyfrom_path = NULL;
- noderev->copyfrom_rev = SVN_INVALID_REVNUM;
-
- /* For the transaction root, the copyroot never changes. */
-
- node_id = svn_fs_fs__id_node_id(noderev->id);
- copy_id = svn_fs_fs__id_copy_id(noderev->id);
- noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
-
- return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
-}
-
-/* A structure used by get_and_increment_txn_key_body(). */
-struct get_and_increment_txn_key_baton {
- svn_fs_t *fs;
- char *txn_id;
- apr_pool_t *pool;
-};
-
-/* Callback used in the implementation of create_txn_dir(). This gets
- the current base 36 value in PATH_TXN_CURRENT and increments it.
- It returns the original value by the baton. */
-static svn_error_t *
-get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
-{
- struct get_and_increment_txn_key_baton *cb = baton;
- const char *txn_current_filename = path_txn_current(cb->fs, pool);
- const char *tmp_filename;
- char next_txn_id[MAX_KEY_SIZE+3];
- apr_size_t len;
-
- svn_stringbuf_t *buf;
- SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
-
- /* remove trailing newlines */
- svn_stringbuf_strip_whitespace(buf);
- cb->txn_id = buf->data;
- len = buf->len;
-
- /* Increment the key and add a trailing \n to the string so the
- txn-current file has a newline in it. */
- svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
- next_txn_id[len] = '\n';
- ++len;
- next_txn_id[len] = '\0';
-
- SVN_ERR(svn_io_write_unique(&tmp_filename,
- svn_dirent_dirname(txn_current_filename, pool),
- next_txn_id, len, svn_io_file_del_none, pool));
- SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
- txn_current_filename, pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Create a unique directory for a transaction in FS based on revision
- REV. Return the ID for this transaction in *ID_P. Use a sequence
- value in the transaction ID to prevent reuse of transaction IDs. */
-static svn_error_t *
-create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
- apr_pool_t *pool)
-{
- struct get_and_increment_txn_key_baton cb;
- const char *txn_dir;
-
- /* Get the current transaction sequence value, which is a base-36
- number, from the txn-current file, and write an
- incremented value back out to the file. Place the revision
- number the transaction is based off into the transaction id. */
- cb.pool = pool;
- cb.fs = fs;
- SVN_ERR(with_txn_current_lock(fs,
- get_and_increment_txn_key_body,
- &cb,
- pool));
- *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
-
- txn_dir = svn_dirent_join_many(pool,
- fs->path,
- PATH_TXNS_DIR,
- apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
- (char *)NULL),
- NULL);
-
- return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
-}
-
-/* Create a unique directory for a transaction in FS based on revision
- REV. Return the ID for this transaction in *ID_P. This
- implementation is used in svn 1.4 and earlier repositories and is
- kept in 1.5 and greater to support the --pre-1.4-compatible and
- --pre-1.5-compatible repository creation options. Reused
- transaction IDs are possible with this implementation. */
-static svn_error_t *
-create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
- apr_pool_t *pool)
-{
- unsigned int i;
- apr_pool_t *subpool;
- const char *unique_path, *prefix;
-
- /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
- prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
- apr_psprintf(pool, "%ld", rev), NULL);
-
- subpool = svn_pool_create(pool);
- for (i = 1; i <= 99999; i++)
- {
- svn_error_t *err;
-
- svn_pool_clear(subpool);
- unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
- err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
- if (! err)
+ /* Paranoia. Compare SHA1 checksums because that's the level of
+ confidence we require for e.g. the working copy. */
+ if (rep_a->has_sha1 && rep_b->has_sha1)
{
- /* We succeeded. Return the basename minus the ".txn" extension. */
- const char *name = svn_dirent_basename(unique_path, subpool);
- *id_p = apr_pstrndup(pool, name,
- strlen(name) - strlen(PATH_EXT_TXN));
- svn_pool_destroy(subpool);
+ *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
+ sizeof(rep_a->sha1_digest)) == 0;
return SVN_NO_ERROR;
}
- if (! APR_STATUS_IS_EEXIST(err->apr_err))
- return svn_error_trace(err);
- svn_error_clear(err);
}
- return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
- NULL,
- _("Unable to create transaction directory "
- "in '%s' for revision %ld"),
- svn_dirent_local_style(fs->path, pool),
- rev);
-}
-
-svn_error_t *
-svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_fs_txn_t *txn;
- svn_fs_id_t *root_id;
-
- txn = apr_pcalloc(pool, sizeof(*txn));
-
- /* Get the txn_id. */
- if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
- SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
- else
- SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
-
- txn->fs = fs;
- txn->base_rev = rev;
-
- txn->vtable = &txn_vtable;
- *txn_p = txn;
-
- /* Create a new root node for this transaction. */
- SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
- SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
-
- /* Create an empty rev file. */
- SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
- pool));
-
- /* Create an empty rev-lock file. */
- SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
- pool));
-
- /* Create an empty changes file. */
- SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
- pool));
-
- /* Create the next-ids file. */
- return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
- pool);
-}
-
-/* Store the property list for transaction TXN_ID in PROPLIST.
- Perform temporary allocations in POOL. */
-static svn_error_t *
-get_txn_proplist(apr_hash_t *proplist,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- svn_stream_t *stream;
-
- /* Check for issue #3696. (When we find and fix the cause, we can change
- * this to an assertion.) */
- if (txn_id == NULL)
- return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
- _("Internal error: a null transaction id was "
- "passed to get_txn_proplist()"));
-
- /* Open the transaction properties file. */
- SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
- pool, pool));
-
- /* Read in the property list. */
- SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
-
- return svn_stream_close(stream);
-}
-
-svn_error_t *
-svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
- const char *name,
- const svn_string_t *value,
- apr_pool_t *pool)
-{
- apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
- svn_prop_t prop;
-
- prop.name = name;
- prop.value = value;
- APR_ARRAY_PUSH(props, svn_prop_t) = prop;
-
- return svn_fs_fs__change_txn_props(txn, props, pool);
-}
-
-svn_error_t *
-svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
- const apr_array_header_t *props,
- apr_pool_t *pool)
-{
- const char *txn_prop_filename;
- svn_stringbuf_t *buf;
- svn_stream_t *stream;
- apr_hash_t *txn_prop = apr_hash_make(pool);
- int i;
- svn_error_t *err;
-
- err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
- /* Here - and here only - we need to deal with the possibility that the
- transaction property file doesn't yet exist. The rest of the
- implementation assumes that the file exists, but we're called to set the
- initial transaction properties as the transaction is being created. */
- if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
- svn_error_clear(err);
- else if (err)
- return svn_error_trace(err);
-
- for (i = 0; i < props->nelts; i++)
- {
- svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
-
- svn_hash_sets(txn_prop, prop->name, prop->value);
- }
-
- /* Create a new version of the file and write out the new props. */
- /* Open the transaction properties file. */
- buf = svn_stringbuf_create_ensure(1024, pool);
- stream = svn_stream_from_stringbuf(buf, pool);
- SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
- SVN_ERR(svn_io_write_unique(&txn_prop_filename,
- path_txn_dir(txn->fs, txn->id, pool),
- buf->data,
- buf->len,
- svn_io_file_del_none,
- pool));
- return svn_io_file_rename(txn_prop_filename,
- path_txn_props(txn->fs, txn->id, pool),
- pool);
-}
-
-svn_error_t *
-svn_fs_fs__get_txn(transaction_t **txn_p,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- transaction_t *txn;
- node_revision_t *noderev;
- svn_fs_id_t *root_id;
-
- txn = apr_pcalloc(pool, sizeof(*txn));
- txn->proplist = apr_hash_make(pool);
-
- SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
- root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
-
- SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
-
- txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
- txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
- txn->copies = NULL;
-
- *txn_p = txn;
-
- return SVN_NO_ERROR;
-}
-
-/* Write out the currently available next node_id NODE_ID and copy_id
- COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
- used both for creating new unique nodes for the given transaction, as
- well as uniquifying representations. Perform temporary allocations in
- POOL. */
-static svn_error_t *
-write_next_ids(svn_fs_t *fs,
- const char *txn_id,
- const char *node_id,
- const char *copy_id,
- apr_pool_t *pool)
-{
- apr_file_t *file;
- svn_stream_t *out_stream;
-
- SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
- APR_WRITE | APR_TRUNCATE,
- APR_OS_DEFAULT, pool));
-
- out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
-
- SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
-
- SVN_ERR(svn_stream_close(out_stream));
- return svn_io_file_close(file, pool);
-}
-
-/* Find out what the next unique node-id and copy-id are for
- transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
- and *COPY_ID. The next node-id is used both for creating new unique
- nodes for the given transaction, as well as uniquifying representations.
- Perform all allocations in POOL. */
-static svn_error_t *
-read_next_ids(const char **node_id,
- const char **copy_id,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- apr_file_t *file;
- char buf[MAX_KEY_SIZE*2+3];
- apr_size_t limit;
- char *str, *last_str = buf;
-
- SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- limit = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
-
- SVN_ERR(svn_io_file_close(file, pool));
-
- /* Parse this into two separate strings. */
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (! str)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("next-id file corrupt"));
-
- *node_id = apr_pstrdup(pool, str);
-
- str = svn_cstring_tokenize(" ", &last_str);
- if (! str)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("next-id file corrupt"));
-
- *copy_id = apr_pstrdup(pool, str);
-
- return SVN_NO_ERROR;
-}
-
-/* Get a new and unique to this transaction node-id for transaction
- TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
- Node-ids are guaranteed to be unique to this transction, but may
- not necessarily be sequential. Perform all allocations in POOL. */
-static svn_error_t *
-get_new_txn_node_id(const char **node_id_p,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- const char *cur_node_id, *cur_copy_id;
- char *node_id;
- apr_size_t len;
-
- /* First read in the current next-ids file. */
- SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
-
- node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
-
- len = strlen(cur_node_id);
- svn_fs_fs__next_key(cur_node_id, &len, node_id);
-
- SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
-
- *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__create_node(const svn_fs_id_t **id_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- const char *copy_id,
- const char *txn_id,
- apr_pool_t *pool)
-{
- const char *node_id;
- const svn_fs_id_t *id;
-
- /* Get a new node-id for this node. */
- SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
-
- id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
-
- noderev->id = id;
-
- SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
-
- *id_p = id;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__purge_txn(svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* Remove the shared transaction object associated with this transaction. */
- SVN_ERR(purge_shared_txn(fs, txn_id, pool));
- /* Remove the directory associated with this transaction. */
- SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
- NULL, NULL, pool));
- if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- {
- /* Delete protorev and its lock, which aren't in the txn
- directory. It's OK if they don't exist (for example, if this
- is post-commit and the proto-rev has been moved into
- place). */
- SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
- TRUE, pool));
- SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
- TRUE, pool));
- }
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
- apr_pool_t *pool)
-{
- SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
-
- /* Now, purge the transaction. */
- SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
- apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
- txn->id));
-
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_fs_fs__set_entry(svn_fs_t *fs,
- const char *txn_id,
- node_revision_t *parent_noderev,
- const char *name,
- const svn_fs_id_t *id,
- svn_node_kind_t kind,
- apr_pool_t *pool)
-{
- representation_t *rep = parent_noderev->data_rep;
- const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
- apr_file_t *file;
- svn_stream_t *out;
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_pool_t *subpool = svn_pool_create(pool);
-
- if (!rep || !rep->txn_id)
- {
- const char *unique_suffix;
- apr_hash_t *entries;
-
- /* Before we can modify the directory, we need to dump its old
- contents into a mutable representation file. */
- SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
- subpool));
- SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
- SVN_ERR(svn_io_file_open(&file, filename,
- APR_WRITE | APR_CREATE | APR_BUFFERED,
- APR_OS_DEFAULT, pool));
- out = svn_stream_from_aprfile2(file, TRUE, pool);
- SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
-
- svn_pool_clear(subpool);
-
- /* Mark the node-rev's data rep as mutable. */
- rep = apr_pcalloc(pool, sizeof(*rep));
- rep->revision = SVN_INVALID_REVNUM;
- rep->txn_id = txn_id;
-
- if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- {
- SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
- rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
- }
-
- parent_noderev->data_rep = rep;
- SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
- parent_noderev, FALSE, pool));
- }
- else
- {
- /* The directory rep is already mutable, so just open it for append. */
- SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
- APR_OS_DEFAULT, pool));
- out = svn_stream_from_aprfile2(file, TRUE, pool);
- }
-
- /* if we have a directory cache for this transaction, update it */
- if (ffd->txn_dir_cache)
- {
- /* build parameters: (name, new entry) pair */
- const char *key =
- svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
- replace_baton_t baton;
-
- baton.name = name;
- baton.new_entry = NULL;
-
- if (id)
- {
- baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
- baton.new_entry->name = name;
- baton.new_entry->kind = kind;
- baton.new_entry->id = id;
- }
-
- /* actually update the cached directory (if cached) */
- SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
- svn_fs_fs__replace_dir_entry, &baton,
- subpool));
- }
- svn_pool_clear(subpool);
-
- /* Append an incremental hash entry for the entry change. */
- if (id)
- {
- const char *val = unparse_dir_entry(kind, id, subpool);
-
- SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
- "V %" APR_SIZE_T_FMT "\n%s\n",
- strlen(name), name,
- strlen(val), val));
- }
- else
- {
- SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
- strlen(name), name));
- }
+ SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
+ scratch_pool));
+ SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
+ scratch_pool));
- SVN_ERR(svn_io_file_close(file, subpool));
- svn_pool_destroy(subpool);
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(apr_file_t *file,
- 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
- ? KIND_DIR : 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_io_file_write_full(file, buf, strlen(buf), NULL, pool));
-
- if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
- {
- buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
- change->copyfrom_path);
- SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
- }
-
- return svn_io_file_write_full(file, "\n", 1, NULL, pool);
-}
-
svn_error_t *
-svn_fs_fs__add_change(svn_fs_t *fs,
- const char *txn_id,
- const char *path,
- const svn_fs_id_t *id,
- svn_fs_path_change_kind_t change_kind,
- svn_boolean_t text_mod,
- svn_boolean_t prop_mod,
- svn_node_kind_t node_kind,
- svn_revnum_t copyfrom_rev,
- const char *copyfrom_path,
- apr_pool_t *pool)
-{
- apr_file_t *file;
- svn_fs_path_change2_t *change;
-
- SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
- APR_APPEND | APR_WRITE | APR_CREATE
- | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- change = svn_fs__path_change_create_internal(id, change_kind, pool);
- change->text_mod = text_mod;
- change->prop_mod = prop_mod;
- change->node_kind = node_kind;
- change->copyfrom_rev = copyfrom_rev;
- change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
-
- SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
-
- return svn_io_file_close(file, pool);
-}
-
-/* This baton is used by the representation writing streams. It keeps
- track of the checksum information as well as the total size of the
- representation so far. */
-struct rep_write_baton
-{
- /* The FS we are writing to. */
- svn_fs_t *fs;
-
- /* Actual file to which we are writing. */
- svn_stream_t *rep_stream;
-
- /* A stream from the delta combiner. Data written here gets
- deltified, then eventually written to rep_stream. */
- svn_stream_t *delta_stream;
-
- /* Where is this representation header stored. */
- apr_off_t rep_offset;
-
- /* Start of the actual data. */
- apr_off_t delta_start;
-
- /* How many bytes have been written to this rep already. */
- svn_filesize_t rep_size;
-
- /* The node revision for which we're writing out info. */
- node_revision_t *noderev;
-
- /* Actual output file. */
- apr_file_t *file;
- /* Lock 'cookie' used to unlock the output file once we've finished
- writing to it. */
- void *lockcookie;
-
- svn_checksum_ctx_t *md5_checksum_ctx;
- svn_checksum_ctx_t *sha1_checksum_ctx;
-
- apr_pool_t *pool;
-
- apr_pool_t *parent_pool;
-};
-
-/* Handler for the write method of the representation writable stream.
- BATON is a rep_write_baton, DATA is the data to write, and *LEN is
- the length of this data. */
-static svn_error_t *
-rep_write_contents(void *baton,
- const char *data,
- apr_size_t *len)
-{
- struct rep_write_baton *b = baton;
-
- SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
- SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
- b->rep_size += *len;
-
- /* If we are writing a delta, use that stream. */
- if (b->delta_stream)
- return svn_stream_write(b->delta_stream, data, len);
- else
- return svn_stream_write(b->rep_stream, data, len);
-}
-
-/* Given a node-revision NODEREV in filesystem FS, return the
- representation in *REP to use as the base for a text representation
- delta if PROPS is FALSE. If PROPS has been set, a suitable props
- base representation will be returned. Perform temporary allocations
- in *POOL. */
-static svn_error_t *
-choose_delta_base(representation_t **rep,
- svn_fs_t *fs,
- node_revision_t *noderev,
- svn_boolean_t props,
- apr_pool_t *pool)
+svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
+ svn_fs_t *fs,
+ node_revision_t *a,
+ node_revision_t *b,
+ apr_pool_t *scratch_pool)
{
- int count;
- int walk;
- node_revision_t *base;
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_boolean_t maybe_shared_rep = FALSE;
+ representation_t *rep_a = a->prop_rep;
+ representation_t *rep_b = b->prop_rep;
+ apr_hash_t *proplist_a;
+ apr_hash_t *proplist_b;
- /* If we have no predecessors, then use the empty stream as a
- base. */
- if (! noderev->predecessor_count)
+ /* Mainly for a==b==NULL */
+ if (rep_a == rep_b)
{
- *rep = NULL;
+ *equal = TRUE;
return SVN_NO_ERROR;
}
- /* Flip the rightmost '1' bit of the predecessor count to determine
- which file rev (counting from 0) we want to use. (To see why
- count & (count - 1) unsets the rightmost set bit, think about how
- you decrement a binary number.) */
- count = noderev->predecessor_count;
- count = count & (count - 1);
-
- /* We use skip delta for limiting the number of delta operations
- along very long node histories. Close to HEAD however, we create
- a linear history to minimize delta size. */
- walk = noderev->predecessor_count - count;
- if (walk < (int)ffd->max_linear_deltification)
- count = noderev->predecessor_count - 1;
-
- /* Finding the delta base over a very long distance can become extremely
- expensive for very deep histories, possibly causing client timeouts etc.
- OTOH, this is a rare operation and its gains are minimal. Lets simply
- start deltification anew close every other 1000 changes or so. */
- if (walk > (int)ffd->max_deltification_walk)
+ /* Committed property lists can be compared quickly */
+ if ( rep_a && rep_b
+ && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
+ && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
{
- *rep = NULL;
+ /* MD5 must be given. Having the same checksum is good enough for
+ accepting the prop lists as equal. */
+ *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
+ sizeof(rep_a->md5_digest)) == 0;
return SVN_NO_ERROR;
}
- /* Walk back a number of predecessors equal to the difference
- between count and the original predecessor count. (For example,
- if noderev has ten predecessors and we want the eighth file rev,
- walk back two predecessors.) */
- base = noderev;
- while ((count++) < noderev->predecessor_count)
- {
- SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
- base->predecessor_id, pool));
-
- /* If there is a shared rep along the way, we need to limit the
- * length of the deltification chain.
- *
- * Please note that copied nodes - such as branch directories - will
- * look the same (false positive) while reps shared within the same
- * revision will not be caught (false negative).
- */
- if (props)
- {
- if ( base->prop_rep
- && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
- maybe_shared_rep = TRUE;
- }
- else
- {
- if ( base->data_rep
- && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
- maybe_shared_rep = TRUE;
- }
- }
-
- /* return a suitable base representation */
- *rep = props ? base->prop_rep : base->data_rep;
-
- /* if we encountered a shared rep, it's parent chain may be different
- * from the node-rev parent chain. */
- if (*rep && maybe_shared_rep)
+ /* Same path in same txn? */
+ if (svn_fs_fs__id_eq(a->id, b->id))
{
- /* Check whether the length of the deltification chain is acceptable.
- * Otherwise, shared reps may form a non-skipping delta chain in
- * extreme cases. */
- apr_pool_t *sub_pool = svn_pool_create(pool);
- representation_t base_rep = **rep;
-
- /* Some reasonable limit, depending on how acceptable longer linear
- * chains are in this repo. Also, allow for some minimal chain. */
- int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
-
- /* re-use open files between iterations */
- svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
- apr_file_t *file_hint = NULL;
-
- /* follow the delta chain towards the end but for at most
- * MAX_CHAIN_LENGTH steps. */
- for (; max_chain_length; --max_chain_length)
- {
- struct rep_state *rep_state;
- struct rep_args *rep_args;
-
- SVN_ERR(create_rep_state_body(&rep_state,
- &rep_args,
- &file_hint,
- &rev_hint,
- &base_rep,
- fs,
- sub_pool));
- if (!rep_args->is_delta || !rep_args->base_revision)
- break;
-
- base_rep.revision = rep_args->base_revision;
- base_rep.offset = rep_args->base_offset;
- base_rep.size = rep_args->base_length;
- base_rep.txn_id = NULL;
- }
-
- /* start new delta chain if the current one has grown too long */
- if (max_chain_length == 0)
- *rep = NULL;
-
- svn_pool_destroy(sub_pool);
- }
-
- /* verify that the reps don't form a degenerated '*/
- return SVN_NO_ERROR;
-}
-
-/* Something went wrong and the pool for the rep write is being
- cleared before we've finished writing the rep. So we need
- to remove the rep from the protorevfile and we need to unlock
- the protorevfile. */
-static apr_status_t
-rep_write_cleanup(void *data)
-{
- struct rep_write_baton *b = data;
- const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
- svn_error_t *err;
-
- /* Truncate and close the protorevfile. */
- err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
- err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
-
- /* Remove our lock regardless of any preceeding errors so that the
- being_written flag is always removed and stays consistent with the
- file lock which will be removed no matter what since the pool is
- going away. */
- err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
- b->lockcookie, b->pool));
- if (err)
- {
- apr_status_t rc = err->apr_err;
- svn_error_clear(err);
- return rc;
- }
-
- return APR_SUCCESS;
-}
-
-
-/* Get a rep_write_baton and store it in *WB_P for the representation
- indicated by NODEREV in filesystem FS. Perform allocations in
- POOL. Only appropriate for file contents, not for props or
- directory contents. */
-static svn_error_t *
-rep_write_get_baton(struct rep_write_baton **wb_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- struct rep_write_baton *b;
- apr_file_t *file;
- representation_t *base_rep;
- svn_stream_t *source;
- const char *header;
- svn_txdelta_window_handler_t wh;
- void *whb;
- fs_fs_data_t *ffd = fs->fsap_data;
- int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
-
- b = apr_pcalloc(pool, sizeof(*b));
-
- b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
- b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
-
- b->fs = fs;
- b->parent_pool = pool;
- b->pool = svn_pool_create(pool);
- b->rep_size = 0;
- b->noderev = noderev;
-
- /* Open the prototype rev file and seek to its end. */
- SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
- fs, svn_fs_fs__id_txn_id(noderev->id),
- b->pool));
-
- b->file = file;
- b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
-
- SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
-
- /* Get the base for this delta. */
- SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
- SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
-
- /* Write out the rep header. */
- if (base_rep)
- {
- header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
- SVN_FILESIZE_T_FMT "\n",
- base_rep->revision, base_rep->offset,
- base_rep->size);
- }
- else
- {
- header = REP_DELTA "\n";
- }
- SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
- b->pool));
-
- /* Now determine the offset of the actual svndiff data. */
- SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
-
- /* Cleanup in case something goes wrong. */
- apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
- apr_pool_cleanup_null);
-
- /* Prepare to write the svndiff data. */
- svn_txdelta_to_svndiff3(&wh,
- &whb,
- b->rep_stream,
- diff_version,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
- pool);
-
- b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
-
- *wb_p = b;
-
- return SVN_NO_ERROR;
-}
-
-/* For the hash REP->SHA1, try to find an already existing representation
- in FS and return it in *OUT_REP. If no such representation exists or
- if rep sharing has been disabled for FS, NULL will be returned. Since
- there may be new duplicate representations within the same uncommitted
- revision, those can be passed in REPS_HASH (maps a sha1 digest onto
- representation_t*), otherwise pass in NULL for REPS_HASH.
- POOL will be used for allocations. The lifetime of the returned rep is
- limited by both, POOL and REP lifetime.
- */
-static svn_error_t *
-get_shared_rep(representation_t **old_rep,
- svn_fs_t *fs,
- representation_t *rep,
- apr_hash_t *reps_hash,
- apr_pool_t *pool)
-{
- svn_error_t *err;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* Return NULL, if rep sharing has been disabled. */
- *old_rep = NULL;
- if (!ffd->rep_sharing_allowed)
- return SVN_NO_ERROR;
-
- /* Check and see if we already have a representation somewhere that's
- identical to the one we just wrote out. Start with the hash lookup
- because it is cheepest. */
- if (reps_hash)
- *old_rep = apr_hash_get(reps_hash,
- rep->sha1_checksum->digest,
- APR_SHA1_DIGESTSIZE);
-
- /* If we haven't found anything yet, try harder and consult our DB. */
- if (*old_rep == NULL)
- {
- err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
- pool);
- /* ### Other error codes that we shouldn't mask out? */
- if (err == SVN_NO_ERROR)
- {
- if (*old_rep)
- SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
- }
- else if (err->apr_err == SVN_ERR_FS_CORRUPT
- || SVN_ERROR_IN_CATEGORY(err->apr_err,
- SVN_ERR_MALFUNC_CATEGORY_START))
- {
- /* Fatal error; don't mask it.
-
- In particular, this block is triggered when the rep-cache refers
- to revisions in the future. We signal that as a corruption situation
- since, once those revisions are less than youngest (because of more
- commits), the rep-cache would be invalid.
- */
- SVN_ERR(err);
- }
- else
- {
- /* Something's wrong with the rep-sharing index. We can continue
- without rep-sharing, but warn.
- */
- (fs->warning)(fs->warning_baton, err);
- svn_error_clear(err);
- *old_rep = NULL;
- }
- }
-
- /* look for intra-revision matches (usually data reps but not limited
- to them in case props happen to look like some data rep)
- */
- if (*old_rep == NULL && rep->txn_id)
- {
- svn_node_kind_t kind;
- const char *file_name
- = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
-
- /* in our txn, is there a rep file named with the wanted SHA1?
- If so, read it and use that rep.
- */
- SVN_ERR(svn_io_check_path(file_name, &kind, pool));
- if (kind == svn_node_file)
- {
- svn_stringbuf_t *rep_string;
- SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
- SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
- rep->txn_id, FALSE, pool));
- }
- }
-
- /* Add information that is missing in the cached data. */
- if (*old_rep)
- {
- /* Use the old rep for this content. */
- (*old_rep)->md5_checksum = rep->md5_checksum;
- (*old_rep)->uniquifier = rep->uniquifier;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Close handler for the representation write stream. BATON is a
- rep_write_baton. Writes out a new node-rev that correctly
- references the representation we just finished writing. */
-static svn_error_t *
-rep_write_contents_close(void *baton)
-{
- struct rep_write_baton *b = baton;
- const char *unique_suffix;
- representation_t *rep;
- representation_t *old_rep;
- apr_off_t offset;
- fs_fs_data_t *ffd = b->fs->fsap_data;
-
- rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
- rep->offset = b->rep_offset;
-
- /* Close our delta stream so the last bits of svndiff are written
- out. */
- if (b->delta_stream)
- SVN_ERR(svn_stream_close(b->delta_stream));
-
- /* Determine the length of the svndiff data. */
- SVN_ERR(get_file_offset(&offset, b->file, b->pool));
- rep->size = offset - b->delta_start;
-
- /* Fill in the rest of the representation field. */
- rep->expanded_size = b->rep_size;
- rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
-
- if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- {
- SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
- rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
- unique_suffix);
- }
- rep->revision = SVN_INVALID_REVNUM;
-
- /* Finalize the checksum. */
- SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
- b->parent_pool));
- SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
- b->parent_pool));
-
- /* Check and see if we already have a representation somewhere that's
- identical to the one we just wrote out. */
- SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
-
- if (old_rep)
- {
- /* We need to erase from the protorev the data we just wrote. */
- SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
-
- /* Use the old rep for this content. */
- b->noderev->data_rep = old_rep;
- }
- else
- {
- /* Write out our cosmetic end marker. */
- SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
-
- b->noderev->data_rep = rep;
+ *equal = TRUE;
+ return SVN_NO_ERROR;
}
- /* Remove cleanup callback. */
- apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
-
- /* Write out the new node-rev information. */
- SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
- b->pool));
- if (!old_rep)
- SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
-
- SVN_ERR(svn_io_file_close(b->file, b->pool));
- SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
- svn_pool_destroy(b->pool);
+ /* At least one of the reps has been modified in a txn.
+ Fetch and compare them. */
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
+ *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
return SVN_NO_ERROR;
}
-/* Store a writable stream in *CONTENTS_P that will receive all data
- written and store it as the file data representation referenced by
- NODEREV in filesystem FS. Perform temporary allocations in
- POOL. Only appropriate for file data, not props or directory
- contents. */
-static svn_error_t *
-set_representation(svn_stream_t **contents_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- struct rep_write_baton *wb;
-
- if (! svn_fs_fs__id_txn_id(noderev->id))
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Attempted to write to non-transaction '%s'"),
- svn_fs_fs__id_unparse(noderev->id, pool)->data);
-
- SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
-
- *contents_p = svn_stream_create(wb, pool);
- svn_stream_set_write(*contents_p, rep_write_contents);
- svn_stream_set_close(*contents_p, rep_write_contents_close);
-
- return SVN_NO_ERROR;
-}
svn_error_t *
-svn_fs_fs__set_contents(svn_stream_t **stream,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool)
-{
- if (noderev->kind != svn_node_file)
- return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
- _("Can't set text contents of a directory"));
-
- return set_representation(stream, fs, noderev, pool);
-}
-
-svn_error_t *
-svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
- svn_fs_t *fs,
- const svn_fs_id_t *old_idp,
- node_revision_t *new_noderev,
- const char *copy_id,
- const char *txn_id,
- apr_pool_t *pool)
-{
- const svn_fs_id_t *id;
-
- if (! copy_id)
- copy_id = svn_fs_fs__id_copy_id(old_idp);
- id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
- txn_id, pool);
-
- new_noderev->id = id;
-
- if (! new_noderev->copyroot_path)
- {
- new_noderev->copyroot_path = apr_pstrdup(pool,
- new_noderev->created_path);
- new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
- }
-
- SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
- pool));
-
- *new_id_p = id;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__set_proplist(svn_fs_t *fs,
- node_revision_t *noderev,
- apr_hash_t *proplist,
- apr_pool_t *pool)
-{
- const char *filename = path_txn_node_props(fs, noderev->id, pool);
- apr_file_t *file;
- svn_stream_t *out;
-
- /* Dump the property list to the mutable property file. */
- SVN_ERR(svn_io_file_open(&file, filename,
- APR_WRITE | APR_CREATE | APR_TRUNCATE
- | APR_BUFFERED, APR_OS_DEFAULT, pool));
- out = svn_stream_from_aprfile2(file, TRUE, pool);
- SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_io_file_close(file, pool));
-
- /* Mark the node-rev's prop rep as mutable, if not already done. */
- if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
- {
- noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
- noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
- SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Read the 'current' file for filesystem FS and store the next
- available node id in *NODE_ID, and the next available copy id in
- *COPY_ID. Allocations are performed from POOL. */
-static svn_error_t *
-get_next_revision_ids(const char **node_id,
- const char **copy_id,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- char *buf;
- char *str;
- svn_stringbuf_t *content;
-
- SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
- buf = content->data;
-
- str = svn_cstring_tokenize(" ", &buf);
- if (! str)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Corrupt 'current' file"));
-
- str = svn_cstring_tokenize(" ", &buf);
- if (! str)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Corrupt 'current' file"));
-
- *node_id = apr_pstrdup(pool, str);
-
- str = svn_cstring_tokenize(" \n", &buf);
- if (! str)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Corrupt 'current' file"));
-
- *copy_id = apr_pstrdup(pool, str);
-
- return SVN_NO_ERROR;
-}
-
-/* This baton is used by the stream created for write_hash_rep. */
-struct write_hash_baton
-{
- svn_stream_t *stream;
-
- apr_size_t size;
-
- svn_checksum_ctx_t *md5_ctx;
- svn_checksum_ctx_t *sha1_ctx;
-};
-
-/* The handler for the write_hash_rep stream. BATON is a
- write_hash_baton, DATA has the data to write and *LEN is the number
- of bytes to write. */
-static svn_error_t *
-write_hash_handler(void *baton,
- const char *data,
- apr_size_t *len)
-{
- struct write_hash_baton *whb = baton;
-
- SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
- SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
-
- SVN_ERR(svn_stream_write(whb->stream, data, len));
- whb->size += *len;
-
- return SVN_NO_ERROR;
-}
-
-/* Write out the hash HASH as a text representation to file FILE. In
- the process, record position, the total size of the dump and MD5 as
- well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH
- is not NULL, it will be used in addition to the on-disk cache to find
- earlier reps with the same content. When such existing reps can be
- found, we will truncate the one just written from the file and return
- the existing rep. Perform temporary allocations in POOL. */
-static svn_error_t *
-write_hash_rep(representation_t *rep,
- apr_file_t *file,
- apr_hash_t *hash,
- svn_fs_t *fs,
- apr_hash_t *reps_hash,
- apr_pool_t *pool)
-{
- svn_stream_t *stream;
- struct write_hash_baton *whb;
- representation_t *old_rep;
-
- SVN_ERR(get_file_offset(&rep->offset, file, pool));
-
- whb = apr_pcalloc(pool, sizeof(*whb));
-
- whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
- whb->size = 0;
- whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
- whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
-
- stream = svn_stream_create(whb, pool);
- svn_stream_set_write(stream, write_hash_handler);
-
- SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
-
- SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
-
- /* Store the results. */
- SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
- SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
-
- /* Check and see if we already have a representation somewhere that's
- identical to the one we just wrote out. */
- SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
-
- if (old_rep)
- {
- /* We need to erase from the protorev the data we just wrote. */
- SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
-
- /* Use the old rep for this content. */
- memcpy(rep, old_rep, sizeof (*rep));
- }
- else
- {
- /* Write out our cosmetic end marker. */
- SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
-
- /* update the representation */
- rep->size = whb->size;
- rep->expanded_size = whb->size;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
- text representation to file FILE. In the process, record the total size
- and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH
- is not NULL, it will be used in addition to the on-disk cache to find
- earlier reps with the same content. When such existing reps can be found,
- we will truncate the one just written from the file and return the existing
- rep. If PROPS is set, assume that we want to a props representation as
- the base for our delta. Perform temporary allocations in POOL. */
-static svn_error_t *
-write_hash_delta_rep(representation_t *rep,
- apr_file_t *file,
- apr_hash_t *hash,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_hash_t *reps_hash,
- svn_boolean_t props,
- apr_pool_t *pool)
-{
- svn_txdelta_window_handler_t diff_wh;
- void *diff_whb;
-
- svn_stream_t *file_stream;
- svn_stream_t *stream;
- representation_t *base_rep;
- representation_t *old_rep;
- svn_stream_t *source;
- const char *header;
-
- apr_off_t rep_end = 0;
- apr_off_t delta_start = 0;
-
- struct write_hash_baton *whb;
- fs_fs_data_t *ffd = fs->fsap_data;
- int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
-
- /* Get the base for this delta. */
- SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
- SVN_ERR(read_representation(&source, fs, base_rep, pool));
-
- SVN_ERR(get_file_offset(&rep->offset, file, pool));
-
- /* Write out the rep header. */
- if (base_rep)
- {
- header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
- SVN_FILESIZE_T_FMT "\n",
- base_rep->revision, base_rep->offset,
- base_rep->size);
- }
- else
- {
- header = REP_DELTA "\n";
- }
- SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
- pool));
-
- SVN_ERR(get_file_offset(&delta_start, file, pool));
- file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
-
- /* Prepare to write the svndiff data. */
- svn_txdelta_to_svndiff3(&diff_wh,
- &diff_whb,
- file_stream,
- diff_version,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
- pool);
-
- whb = apr_pcalloc(pool, sizeof(*whb));
- whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
- whb->size = 0;
- whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
- whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
-
- /* serialize the hash */
- stream = svn_stream_create(whb, pool);
- svn_stream_set_write(stream, write_hash_handler);
-
- SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(whb->stream));
-
- /* Store the results. */
- SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
- SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
-
- /* Check and see if we already have a representation somewhere that's
- identical to the one we just wrote out. */
- SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
-
- if (old_rep)
- {
- /* We need to erase from the protorev the data we just wrote. */
- SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
-
- /* Use the old rep for this content. */
- memcpy(rep, old_rep, sizeof (*rep));
- }
- else
- {
- /* Write out our cosmetic end marker. */
- SVN_ERR(get_file_offset(&rep_end, file, pool));
- SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
-
- /* update the representation */
- rep->expanded_size = whb->size;
- rep->size = rep_end - delta_start;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
- of (not yet committed) revision REV in FS. Use POOL for temporary
- allocations.
-
- If you change this function, consider updating svn_fs_fs__verify() too.
- */
-static svn_error_t *
-validate_root_noderev(svn_fs_t *fs,
- node_revision_t *root_noderev,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- svn_revnum_t head_revnum = rev-1;
- int head_predecessor_count;
-
- SVN_ERR_ASSERT(rev > 0);
-
- /* Compute HEAD_PREDECESSOR_COUNT. */
- {
- svn_fs_root_t *head_revision;
- const svn_fs_id_t *head_root_id;
- node_revision_t *head_root_noderev;
-
- /* Get /@HEAD's noderev. */
- SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
- SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
- SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
- pool));
-
- head_predecessor_count = head_root_noderev->predecessor_count;
- }
-
- /* Check that the root noderev's predecessor count equals REV.
-
- This kind of corruption was seen on svn.apache.org (both on
- the root noderev and on other fspaths' noderevs); see
- issue #4129.
-
- Normally (rev == root_noderev->predecessor_count), but here we
- use a more roundabout check that should only trigger on new instances
- of the corruption, rather then trigger on each and every new commit
- to a repository that has triggered the bug somewhere in its root
- noderev's history.
- */
- if (root_noderev->predecessor_count != -1
- && (root_noderev->predecessor_count - head_predecessor_count)
- != (rev - head_revnum))
- {
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("predecessor count for "
- "the root node-revision is wrong: "
- "found (%d+%ld != %d), committing r%ld"),
- head_predecessor_count,
- rev - head_revnum, /* This is equal to 1. */
- root_noderev->predecessor_count,
- rev);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Copy a node-revision specified by id ID in fileystem FS from a
- transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
- pointer to the new node-id which will be allocated in POOL.
- If this is a directory, copy all children as well.
-
- START_NODE_ID and START_COPY_ID are
- the first available node and copy ids for this filesystem, for older
- FS formats.
-
- REV is the revision number that this proto-rev-file will represent.
-
- INITIAL_OFFSET is the offset of the proto-rev-file on entry to
- commit_body.
-
- If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
- REPS_POOL) of each data rep that is new in this revision.
-
- If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
- of the representations of each property rep that is new in this
- revision.
-
- AT_ROOT is true if the node revision being written is the root
- node-revision. It is only controls additional sanity checking
- logic.
-
- Temporary allocations are also from POOL. */
-static svn_error_t *
-write_final_rev(const svn_fs_id_t **new_id_p,
- apr_file_t *file,
- svn_revnum_t rev,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- const char *start_node_id,
- const char *start_copy_id,
- apr_off_t initial_offset,
- apr_array_header_t *reps_to_cache,
- apr_hash_t *reps_hash,
- apr_pool_t *reps_pool,
- svn_boolean_t at_root,
- apr_pool_t *pool)
+svn_fs_fs__file_checksum(svn_checksum_t **checksum,
+ node_revision_t *noderev,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool)
{
- node_revision_t *noderev;
- apr_off_t my_offset;
- char my_node_id_buf[MAX_KEY_SIZE + 2];
- char my_copy_id_buf[MAX_KEY_SIZE + 2];
- const svn_fs_id_t *new_id;
- const char *node_id, *copy_id, *my_node_id, *my_copy_id;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- *new_id_p = NULL;
-
- /* Check to see if this is a transaction node. */
- if (! svn_fs_fs__id_txn_id(id))
- return SVN_NO_ERROR;
-
- SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
-
- if (noderev->kind == svn_node_dir)
- {
- apr_pool_t *subpool;
- apr_hash_t *entries, *str_entries;
- apr_array_header_t *sorted_entries;
- int i;
-
- /* This is a directory. Write out all the children first. */
- subpool = svn_pool_create(pool);
-
- SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
- /* For the sake of the repository administrator sort the entries
- so that the final file is deterministic and repeatable,
- however the rest of the FSFS code doesn't require any
- particular order here. */
- sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
- pool);
- for (i = 0; i < sorted_entries->nelts; ++i)
- {
- svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
- svn_sort__item_t).value;
-
- svn_pool_clear(subpool);
- SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
- start_node_id, start_copy_id, initial_offset,
- reps_to_cache, reps_hash, reps_pool, FALSE,
- subpool));
- if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
- dirent->id = svn_fs_fs__id_copy(new_id, pool);
- }
- svn_pool_destroy(subpool);
-
- if (noderev->data_rep && noderev->data_rep->txn_id)
- {
- /* Write out the contents of this directory as a text rep. */
- SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
-
- noderev->data_rep->txn_id = NULL;
- noderev->data_rep->revision = rev;
-
- if (ffd->deltify_directories)
- SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
- str_entries, fs, noderev, NULL,
- FALSE, pool));
- else
- SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
- fs, NULL, pool));
- }
- }
- else
- {
- /* This is a file. We should make sure the data rep, if it
- exists in a "this" state, gets rewritten to our new revision
- num. */
-
- if (noderev->data_rep && noderev->data_rep->txn_id)
- {
- noderev->data_rep->txn_id = NULL;
- noderev->data_rep->revision = rev;
-
- /* See issue 3845. Some unknown mechanism caused the
- protorev file to get truncated, so check for that
- here. */
- if (noderev->data_rep->offset + noderev->data_rep->size
- > initial_offset)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Truncated protorev file detected"));
- }
- }
-
- /* Fix up the property reps. */
- if (noderev->prop_rep && noderev->prop_rep->txn_id)
- {
- apr_hash_t *proplist;
- SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
-
- noderev->prop_rep->txn_id = NULL;
- noderev->prop_rep->revision = rev;
-
- if (ffd->deltify_properties)
- SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
- proplist, fs, noderev, reps_hash,
- TRUE, pool));
- else
- SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
- fs, reps_hash, pool));
- }
-
-
- /* Convert our temporary ID into a permanent revision one. */
- SVN_ERR(get_file_offset(&my_offset, file, pool));
-
- node_id = svn_fs_fs__id_node_id(noderev->id);
- if (*node_id == '_')
- {
- if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
- else
- {
- svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
- my_node_id = my_node_id_buf;
- }
- }
- else
- my_node_id = node_id;
-
- copy_id = svn_fs_fs__id_copy_id(noderev->id);
- if (*copy_id == '_')
- {
- if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
- else
- {
- svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
- my_copy_id = my_copy_id_buf;
- }
- }
- else
- my_copy_id = copy_id;
-
- if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
- noderev->copyroot_rev = rev;
-
- new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
- pool);
+ *checksum = NULL;
- noderev->id = new_id;
-
- if (ffd->rep_sharing_allowed)
+ if (noderev->data_rep)
{
- /* Save the data representation's hash in the rep cache. */
- if ( noderev->data_rep && noderev->kind == svn_node_file
- && noderev->data_rep->revision == rev)
- {
- SVN_ERR_ASSERT(reps_to_cache && reps_pool);
- APR_ARRAY_PUSH(reps_to_cache, representation_t *)
- = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
- }
+ svn_checksum_t temp;
+ temp.kind = kind;
- if (noderev->prop_rep && noderev->prop_rep->revision == rev)
+ switch(kind)
{
- /* Add new property reps to hash and on-disk cache. */
- representation_t *copy
- = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
-
- SVN_ERR_ASSERT(reps_to_cache && reps_pool);
- APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
-
- apr_hash_set(reps_hash,
- copy->sha1_checksum->digest,
- APR_SHA1_DIGESTSIZE,
- copy);
- }
- }
-
- /* don't serialize SHA1 for dirs to disk (waste of space) */
- if (noderev->data_rep && noderev->kind == svn_node_dir)
- noderev->data_rep->sha1_checksum = NULL;
-
- /* don't serialize SHA1 for props to disk (waste of space) */
- if (noderev->prop_rep)
- noderev->prop_rep->sha1_checksum = NULL;
-
- /* Workaround issue #4031: is-fresh-txn-root in revision files. */
- noderev->is_fresh_txn_root = FALSE;
-
- /* Write out our new node-revision. */
- if (at_root)
- SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
-
- SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
- noderev, ffd->format,
- svn_fs_fs__fs_supports_mergeinfo(fs),
- pool));
-
- /* Return our ID that references the revision file. */
- *new_id_p = noderev->id;
-
- return SVN_NO_ERROR;
-}
-
-/* Write the changed path info from transaction TXN_ID in filesystem
- FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
- in the file of the beginning of this information. Perform
- temporary allocations in POOL. */
-static svn_error_t *
-write_final_changed_path_info(apr_off_t *offset_p,
- apr_file_t *file,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- apr_hash_t *changed_paths;
- apr_off_t offset;
- 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;
-
- SVN_ERR(get_file_offset(&offset, file, pool));
-
- SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
- /* 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(changed_paths,
- svn_sort_compare_items_lexically, pool);
-
- /* Iterate through the changed paths one at a time, and convert the
- temporary node-id into a permanent one for each change entry. */
- for (i = 0; i < sorted_changed_paths->nelts; ++i)
- {
- node_revision_t *noderev;
- const svn_fs_id_t *id;
- 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;
+ case svn_checksum_md5:
+ temp.digest = noderev->data_rep->md5_digest;
+ break;
- id = change->node_rev_id;
+ case svn_checksum_sha1:
+ if (! noderev->data_rep->has_sha1)
+ return SVN_NO_ERROR;
- /* If this was a delete of a mutable node, then it is OK to
- leave the change entry pointing to the non-existent temporary
- node, since it will never be used. */
- if ((change->change_kind != svn_fs_path_change_delete) &&
- (! svn_fs_fs__id_txn_id(id)))
- {
- SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
+ temp.digest = noderev->data_rep->sha1_digest;
+ break;
- /* noderev has the permanent node-id at this point, so we just
- substitute it for the temporary one. */
- change->node_rev_id = noderev->id;
+ default:
+ return SVN_NO_ERROR;
}
- /* Write out the new entry into the final rev-file. */
- SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
- iterpool));
+ *checksum = svn_checksum_dup(&temp, pool);
}
- svn_pool_destroy(iterpool);
-
- *offset_p = offset;
-
- return SVN_NO_ERROR;
-}
-
-/* Atomically update the 'current' file to hold the specifed REV,
- NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
- ignored and may be NULL if the FS format does not use them.)
- Perform temporary allocations in POOL. */
-static svn_error_t *
-write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
- const char *next_copy_id, apr_pool_t *pool)
-{
- char *buf;
- const char *tmp_name, *name;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- /* Now we can just write out this line. */
- if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- buf = apr_psprintf(pool, "%ld\n", rev);
- else
- buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
-
- name = svn_fs_fs__path_current(fs, pool);
- SVN_ERR(svn_io_write_unique(&tmp_name,
- svn_dirent_dirname(name, pool),
- buf, strlen(buf),
- svn_io_file_del_none, pool));
-
- return move_into_place(tmp_name, name, name, pool);
-}
-
-/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
- youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
- NEW_REV's revision root.
-
- Intended to be called as the very last step in a commit before 'current'
- is bumped. This implies that we are holding the write lock. */
-static svn_error_t *
-verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
- svn_revnum_t new_rev,
- apr_pool_t *pool)
-{
-#ifdef SVN_DEBUG
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_fs_t *ft; /* fs++ == ft */
- svn_fs_root_t *root;
- fs_fs_data_t *ft_ffd;
- apr_hash_t *fs_config;
-
- SVN_ERR_ASSERT(ffd->svn_fs_open_);
-
- /* make sure FT does not simply return data cached by other instances
- * but actually retrieves it from disk at least once.
- */
- fs_config = apr_hash_make(pool);
- svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
- svn_uuid_generate(pool));
- SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
- fs_config,
- pool));
- ft_ffd = ft->fsap_data;
- /* Don't let FT consult rep-cache.db, either. */
- ft_ffd->rep_sharing_allowed = FALSE;
-
- /* Time travel! */
- ft_ffd->youngest_rev_cache = new_rev;
-
- SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
- SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
- SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
- SVN_ERR(svn_fs_fs__verify_root(root, pool));
-#endif /* SVN_DEBUG */
-
return SVN_NO_ERROR;
}
-/* Update the 'current' file to hold the correct next node and copy_ids
- from transaction TXN_ID in filesystem FS. The current revision is
- set to REV. Perform temporary allocations in POOL. */
-static svn_error_t *
-write_final_current(svn_fs_t *fs,
- const char *txn_id,
- svn_revnum_t rev,
- const char *start_node_id,
- const char *start_copy_id,
+representation_t *
+svn_fs_fs__rep_copy(representation_t *rep,
apr_pool_t *pool)
{
- const char *txn_node_id, *txn_copy_id;
- char new_node_id[MAX_KEY_SIZE + 2];
- char new_copy_id[MAX_KEY_SIZE + 2];
- fs_fs_data_t *ffd = fs->fsap_data;
-
- if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- return write_current(fs, rev, NULL, NULL, pool);
-
- /* To find the next available ids, we add the id that used to be in
- the 'current' file, to the next ids from the transaction file. */
- SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
-
- svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
- svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
-
- return write_current(fs, rev, new_node_id, new_copy_id, pool);
-}
+ if (rep == NULL)
+ return NULL;
-/* Verify that the user registed with FS has all the locks necessary to
- permit all the changes associate with TXN_NAME.
- The FS write lock is assumed to be held by the caller. */
-static svn_error_t *
-verify_locks(svn_fs_t *fs,
- const char *txn_name,
- apr_pool_t *pool)
-{
- apr_pool_t *subpool = svn_pool_create(pool);
- apr_hash_t *changes;
- apr_hash_index_t *hi;
- apr_array_header_t *changed_paths;
- svn_stringbuf_t *last_recursed = NULL;
- int i;
-
- /* Fetch the changes for this transaction. */
- SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
-
- /* Make an array of the changed paths, and sort them depth-first-ily. */
- changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
- sizeof(const char *));
- for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
- APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
- qsort(changed_paths->elts, changed_paths->nelts,
- changed_paths->elt_size, svn_sort_compare_paths);
-
- /* Now, traverse the array of changed paths, verify locks. Note
- that if we need to do a recursive verification a path, we'll skip
- over children of that path when we get to them. */
- for (i = 0; i < changed_paths->nelts; i++)
- {
- const char *path;
- svn_fs_path_change2_t *change;
- svn_boolean_t recurse = TRUE;
-
- svn_pool_clear(subpool);
- path = APR_ARRAY_IDX(changed_paths, i, const char *);
-
- /* If this path has already been verified as part of a recursive
- check of one of its parents, no need to do it again. */
- if (last_recursed
- && svn_dirent_is_child(last_recursed->data, path, subpool))
- continue;
-
- /* Fetch the change associated with our path. */
- change = svn_hash_gets(changes, path);
-
- /* What does it mean to succeed at lock verification for a given
- path? For an existing file or directory getting modified
- (text, props), it means we hold the lock on the file or
- directory. For paths being added or removed, we need to hold
- the locks for that path and any children of that path.
-
- WHEW! We have no reliable way to determine the node kind
- of deleted items, but fortunately we are going to do a
- recursive check on deleted paths regardless of their kind. */
- if (change->change_kind == svn_fs_path_change_modify)
- recurse = FALSE;
- SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
- subpool));
-
- /* If we just did a recursive check, remember the path we
- checked (so children can be skipped). */
- if (recurse)
- {
- if (! last_recursed)
- last_recursed = svn_stringbuf_create(path, pool);
- else
- svn_stringbuf_set(last_recursed, path);
- }
- }
- svn_pool_destroy(subpool);
- return SVN_NO_ERROR;
+ return apr_pmemdup(pool, rep, sizeof(*rep));
}
-/* Baton used for commit_body below. */
-struct commit_baton {
- svn_revnum_t *new_rev_p;
- svn_fs_t *fs;
- svn_fs_txn_t *txn;
- apr_array_header_t *reps_to_cache;
- apr_hash_t *reps_hash;
- apr_pool_t *reps_pool;
-};
-/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
- This implements the svn_fs_fs__with_write_lock() 'body' callback
- type. BATON is a 'struct commit_baton *'. */
+/* Write out the zeroth revision for filesystem FS.
+ Perform temporary allocations in SCRATCH_POOL. */
static svn_error_t *
-commit_body(void *baton, apr_pool_t *pool)
-{
- struct commit_baton *cb = baton;
- fs_fs_data_t *ffd = cb->fs->fsap_data;
- const char *old_rev_filename, *rev_filename, *proto_filename;
- const char *revprop_filename, *final_revprop;
- const svn_fs_id_t *root_id, *new_root_id;
- const char *start_node_id = NULL, *start_copy_id = NULL;
- svn_revnum_t old_rev, new_rev;
- apr_file_t *proto_file;
- void *proto_file_lockcookie;
- apr_off_t initial_offset, changed_path_offset;
- char *buf;
- apr_hash_t *txnprops;
- apr_array_header_t *txnprop_list;
- svn_prop_t prop;
- svn_string_t date;
-
- /* Get the current youngest revision. */
- SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
-
- /* Check to make sure this transaction is based off the most recent
- revision. */
- if (cb->txn->base_rev != old_rev)
- return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
- _("Transaction out of date"));
-
- /* Locks may have been added (or stolen) between the calling of
- previous svn_fs.h functions and svn_fs_commit_txn(), so we need
- to re-examine every changed-path in the txn and re-verify all
- discovered locks. */
- SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
-
- /* Get the next node_id and copy_id to use. */
- if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
- pool));
-
- /* We are going to be one better than this puny old revision. */
- new_rev = old_rev + 1;
-
- /* Get a write handle on the proto revision file. */
- SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
- cb->fs, cb->txn->id, pool));
- SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
-
- /* Write out all the node-revisions and directory contents. */
- root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
- SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
- start_node_id, start_copy_id, initial_offset,
- cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
- TRUE, pool));
-
- /* Write the changed-path information. */
- SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
- cb->fs, cb->txn->id, pool));
-
- /* Write the final line. */
- buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
- svn_fs_fs__id_offset(new_root_id),
- changed_path_offset);
- SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
- pool));
- SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
- SVN_ERR(svn_io_file_close(proto_file, pool));
-
- /* We don't unlock the prototype revision file immediately to avoid a
- race with another caller writing to the prototype revision file
- before we commit it. */
-
- /* Remove any temporary txn props representing 'flags'. */
- SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
- txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
- prop.value = NULL;
-
- if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
- {
- prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
- APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
- }
-
- if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
- {
- prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
- APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
- }
-
- if (! apr_is_empty_array(txnprop_list))
- SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
-
- /* Create the shard for the rev and revprop file, if we're sharding and
- this is the first revision of a new shard. We don't care if this
- fails because the shard already existed for some reason. */
- if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
- {
- /* Create the revs shard. */
- {
- const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
- svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
- if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
- return svn_error_trace(err);
- svn_error_clear(err);
- SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
- PATH_REVS_DIR,
- pool),
- new_dir, pool));
- }
-
- /* Create the revprops shard. */
- SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
- {
- const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
- svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
- if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
- return svn_error_trace(err);
- svn_error_clear(err);
- SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
- PATH_REVPROPS_DIR,
- pool),
- new_dir, pool));
- }
- }
-
- /* Move the finished rev file into place. */
- SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
- cb->fs, old_rev, pool));
- rev_filename = path_rev(cb->fs, new_rev, pool);
- proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
- SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
- pool));
-
- /* Now that we've moved the prototype revision file out of the way,
- we can unlock it (since further attempts to write to the file
- will fail as it no longer exists). We must do this so that we can
- remove the transaction directory later. */
- SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
-
- /* Update commit time to ensure that svn:date revprops remain ordered. */
- date.data = svn_time_to_cstring(apr_time_now(), pool);
- date.len = strlen(date.data);
-
- SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
- &date, pool));
-
- /* Move the revprops file into place. */
- SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
- revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
- final_revprop = path_revprops(cb->fs, new_rev, pool);
- SVN_ERR(move_into_place(revprop_filename, final_revprop,
- old_rev_filename, pool));
-
- /* Update the 'current' file. */
- SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
- SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
- start_copy_id, pool));
-
- /* At this point the new revision is committed and globally visible
- so let the caller know it succeeded by giving it the new revision
- number, which fulfills svn_fs_commit_txn() contract. Any errors
- after this point do not change the fact that a new revision was
- created. */
- *cb->new_rev_p = new_rev;
-
- ffd->youngest_rev_cache = new_rev;
-
- /* Remove this transaction directory. */
- SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
- * to the rep-cache database of FS. */
-static svn_error_t *
-write_reps_to_cache(svn_fs_t *fs,
- const apr_array_header_t *reps_to_cache,
+write_revision_zero(svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
- int i;
-
- for (i = 0; i < reps_to_cache->nelts; i++)
- {
- representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
-
- /* FALSE because we don't care if another parallel commit happened to
- * collide with us. (Non-parallel collisions will not be detected.) */
- SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__commit(svn_revnum_t *new_rev_p,
- svn_fs_t *fs,
- svn_fs_txn_t *txn,
- apr_pool_t *pool)
-{
- struct commit_baton cb;
- fs_fs_data_t *ffd = fs->fsap_data;
-
- cb.new_rev_p = new_rev_p;
- cb.fs = fs;
- cb.txn = txn;
-
- if (ffd->rep_sharing_allowed)
- {
- cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
- cb.reps_hash = apr_hash_make(pool);
- cb.reps_pool = pool;
- }
- else
- {
- cb.reps_to_cache = NULL;
- cb.reps_hash = NULL;
- cb.reps_pool = NULL;
- }
-
- SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
-
- /* At this point, *NEW_REV_P has been set, so errors below won't affect
- the success of the commit. (See svn_fs_commit_txn().) */
-
- if (ffd->rep_sharing_allowed)
- {
- SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
-
- /* Write new entries to the rep-sharing database.
- *
- * We use an sqlite transaction to speed things up;
- * see <http://www.sqlite.org/faq.html#q19>.
- */
- SVN_SQLITE__WITH_TXN(
- write_reps_to_cache(fs, cb.reps_to_cache, pool),
- ffd->rep_cache_db);
- }
-
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_fs_fs__reserve_copy_id(const char **copy_id_p,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool)
-{
- const char *cur_node_id, *cur_copy_id;
- char *copy_id;
- apr_size_t len;
-
- /* First read in the current next-ids file. */
- SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
-
- copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
-
- len = strlen(cur_copy_id);
- svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
-
- SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
-
- *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
-
- return SVN_NO_ERROR;
-}
-
-/* Write out the zeroth revision for filesystem FS. */
-static svn_error_t *
-write_revision_zero(svn_fs_t *fs)
-{
- const char *path_revision_zero = path_rev(fs, 0, fs->pool);
+ /* Use an explicit sub-pool to have full control over temp file lifetimes.
+ * Since we have it, use it for everything else as well. */
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
apr_hash_t *proplist;
svn_string_t date;
/* Write out a rev file for revision 0. */
- SVN_ERR(svn_io_file_create(path_revision_zero,
- "PLAIN\nEND\nENDREP\n"
- "id: 0.0.r0/17\n"
- "type: dir\n"
- "count: 0\n"
- "text: 0 0 4 4 "
- "2d2977d1c96f487abe4a1e202dd03b4e\n"
- "cpath: /\n"
- "\n\n17 107\n", fs->pool));
- SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ apr_array_header_t *index_entries;
+ svn_fs_fs__p2l_entry_t *entry;
+ svn_fs_fs__revision_file_t *rev_file;
+ const char *l2p_proto_index, *p2l_proto_index;
+
+ /* Write a skeleton r0 with no indexes. */
+ SVN_ERR(svn_io_file_create(path_revision_zero,
+ "PLAIN\nEND\nENDREP\n"
+ "id: 0.0.r0/2\n"
+ "type: dir\n"
+ "count: 0\n"
+ "text: 0 3 4 4 "
+ "2d2977d1c96f487abe4a1e202dd03b4e\n"
+ "cpath: /\n"
+ "\n\n", subpool));
+
+ /* Construct the index P2L contents: describe the 3 items we have.
+ Be sure to create them in on-disk order. */
+ index_entries = apr_array_make(subpool, 3, sizeof(entry));
+
+ entry = apr_pcalloc(subpool, sizeof(*entry));
+ entry->offset = 0;
+ entry->size = 17;
+ entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
+ entry->item.revision = 0;
+ entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
+ APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
+
+ entry = apr_pcalloc(subpool, sizeof(*entry));
+ entry->offset = 17;
+ entry->size = 89;
+ entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
+ entry->item.revision = 0;
+ entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
+ APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
+
+ entry = apr_pcalloc(subpool, sizeof(*entry));
+ entry->offset = 106;
+ entry->size = 1;
+ entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
+ entry->item.revision = 0;
+ entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
+ APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
+
+ /* Now re-open r0, create proto-index files from our entries and
+ rewrite the index section of r0. */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
+ subpool, subpool));
+ SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
+ rev_file, index_entries,
+ subpool, subpool));
+ SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
+ index_entries,
+ subpool, subpool));
+ SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
+ p2l_proto_index, 0, subpool));
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+ }
+ else
+ SVN_ERR(svn_io_file_create(path_revision_zero,
+ "PLAIN\nEND\nENDREP\n"
+ "id: 0.0.r0/17\n"
+ "type: dir\n"
+ "count: 0\n"
+ "text: 0 0 4 4 "
+ "2d2977d1c96f487abe4a1e202dd03b4e\n"
+ "cpath: /\n"
+ "\n\n17 107\n", subpool));
+
+ SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
/* Set a date on revision 0. */
- date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
+ date.data = svn_time_to_cstring(apr_time_now(), subpool);
date.len = strlen(date.data);
- proplist = apr_hash_make(fs->pool);
+ proplist = apr_hash_make(subpool);
svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
- return set_revision_proplist(fs, 0, proplist, fs->pool);
+ SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
}
svn_error_t *
-svn_fs_fs__create(svn_fs_t *fs,
- const char *path,
- apr_pool_t *pool)
+svn_fs_fs__create_file_tree(svn_fs_t *fs,
+ const char *path,
+ int format,
+ int shard_size,
+ svn_boolean_t use_log_addressing,
+ apr_pool_t *pool)
{
- int format = SVN_FS_FS__FORMAT_NUMBER;
fs_fs_data_t *ffd = fs->fsap_data;
- fs->path = apr_pstrdup(pool, path);
- /* See if compatibility with older versions was explicitly requested. */
- if (fs->config)
- {
- if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
- format = 1;
- else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
- format = 2;
- else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
- format = 3;
- else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
- format = 4;
- }
+ fs->path = apr_pstrdup(fs->pool, path);
ffd->format = format;
- /* Override the default linear layout if this is a new-enough format. */
+ /* Use an appropriate sharding mode if supported by the format. */
if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
- ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
+ ffd->max_files_per_dir = shard_size;
+ else
+ ffd->max_files_per_dir = 0;
+
+ /* Select the addressing mode depending on the format. */
+ if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
+ ffd->use_log_addressing = use_log_addressing;
+ else
+ ffd->use_log_addressing = FALSE;
/* Create the revision data directories. */
if (ffd->max_files_per_dir)
- SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
+ SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
+ pool),
+ pool));
else
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
pool),
@@ -8858,7 +1725,8 @@ svn_fs_fs__create(svn_fs_t *fs,
/* Create the revprops directory. */
if (ffd->max_files_per_dir)
- SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
+ SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
+ pool),
pool));
else
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
@@ -8867,25 +1735,22 @@ svn_fs_fs__create(svn_fs_t *fs,
pool));
/* Create the transaction directory. */
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
- pool),
+ SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
pool));
/* Create the protorevs directory. */
if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
- pool),
+ SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
+ pool),
pool));
/* Create the 'current' file. */
- SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
- (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
- ? "0\n" : "0 1 1\n"),
- pool));
- SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
- SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
+ SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
+ SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
- SVN_ERR(write_revision_zero(fs));
+ /* Create the 'uuid' file. */
+ SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
+ SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
/* Create the fsfs.conf file if supported. Older server versions would
simply ignore the file but that might result in a different behavior
@@ -8894,554 +1759,140 @@ svn_fs_fs__create(svn_fs_t *fs,
if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
SVN_ERR(write_config(fs, pool));
- SVN_ERR(read_config(ffd, fs->path, pool));
+ SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
+
+ /* Global configuration options. */
+ SVN_ERR(read_global_config(fs));
+
+ /* Add revision 0. */
+ SVN_ERR(write_revision_zero(fs, pool));
/* Create the min unpacked rev file. */
if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
+ SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
+ "0\n", pool));
/* Create the txn-current file if the repository supports
the transaction sequence file. */
if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
{
- SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
+ SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
"0\n", pool));
- SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
- "", pool));
+ SVN_ERR(svn_io_file_create_empty(
+ svn_fs_fs__path_txn_current_lock(fs, pool),
+ pool));
}
- /* This filesystem is ready. Stamp it with a format number. */
- SVN_ERR(write_format(path_format(fs, pool),
- ffd->format, ffd->max_files_per_dir, FALSE, pool));
-
ffd->youngest_rev_cache = 0;
return SVN_NO_ERROR;
}
-/* Part of the recovery procedure. Return the largest revision *REV in
- filesystem FS. Use POOL for temporary allocation. */
-static svn_error_t *
-recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
-{
- /* Discovering the largest revision in the filesystem would be an
- expensive operation if we did a readdir() or searched linearly,
- so we'll do a form of binary search. left is a revision that we
- know exists, right a revision that we know does not exist. */
- apr_pool_t *iterpool;
- svn_revnum_t left, right = 1;
-
- iterpool = svn_pool_create(pool);
- /* Keep doubling right, until we find a revision that doesn't exist. */
- while (1)
- {
- svn_error_t *err;
- apr_file_t *file;
-
- err = open_pack_or_rev_file(&file, fs, right, iterpool);
- svn_pool_clear(iterpool);
-
- if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
- {
- svn_error_clear(err);
- break;
- }
- else
- SVN_ERR(err);
-
- right <<= 1;
- }
-
- left = right >> 1;
-
- /* We know that left exists and right doesn't. Do a normal bsearch to find
- the last revision. */
- while (left + 1 < right)
- {
- svn_revnum_t probe = left + ((right - left) / 2);
- svn_error_t *err;
- apr_file_t *file;
-
- err = open_pack_or_rev_file(&file, fs, probe, iterpool);
- svn_pool_clear(iterpool);
-
- if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
- {
- svn_error_clear(err);
- right = probe;
- }
- else
- {
- SVN_ERR(err);
- left = probe;
- }
- }
-
- svn_pool_destroy(iterpool);
-
- /* left is now the largest revision that exists. */
- *rev = left;
- return SVN_NO_ERROR;
-}
-
-/* A baton for reading a fixed amount from an open file. For
- recover_find_max_ids() below. */
-struct recover_read_from_file_baton
-{
- apr_file_t *file;
- apr_pool_t *pool;
- apr_off_t remaining;
-};
-
-/* A stream read handler used by recover_find_max_ids() below.
- Read and return at most BATON->REMAINING bytes from the stream,
- returning nothing after that to indicate EOF. */
-static svn_error_t *
-read_handler_recover(void *baton, char *buffer, apr_size_t *len)
-{
- struct recover_read_from_file_baton *b = baton;
- svn_filesize_t bytes_to_read = *len;
-
- if (b->remaining == 0)
- {
- /* Return a successful read of zero bytes to signal EOF. */
- *len = 0;
- return SVN_NO_ERROR;
- }
-
- if (bytes_to_read > b->remaining)
- bytes_to_read = b->remaining;
- b->remaining -= bytes_to_read;
-
- return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
- len, NULL, b->pool);
-}
-
-/* Part of the recovery procedure. Read the directory noderev at offset
- OFFSET of file REV_FILE (the revision file of revision REV of
- filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
- and copy-id of that node, if greater than the current value stored
- in either. Recurse into any child directories that were modified in
- this revision.
-
- MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
-
- Perform temporary allocation in POOL. */
-static svn_error_t *
-recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
- apr_file_t *rev_file, apr_off_t offset,
- char *max_node_id, char *max_copy_id,
- apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__create(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
{
- apr_hash_t *headers;
- char *value;
- representation_t *data_rep;
- struct rep_args *ra;
- struct recover_read_from_file_baton baton;
- svn_stream_t *stream;
- apr_hash_t *entries;
- apr_hash_index_t *hi;
- apr_pool_t *iterpool;
-
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
- SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
- pool),
- pool));
-
- /* Check that this is a directory. It should be. */
- value = svn_hash_gets(headers, HEADER_TYPE);
- if (value == NULL || strcmp(value, KIND_DIR) != 0)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Recovery encountered a non-directory node"));
-
- /* Get the data location. No data location indicates an empty directory. */
- value = svn_hash_gets(headers, HEADER_TEXT);
- if (!value)
- return SVN_NO_ERROR;
- SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
-
- /* If the directory's data representation wasn't changed in this revision,
- we've already scanned the directory's contents for noderevs, so we don't
- need to again. This will occur if a property is changed on a directory
- without changing the directory's contents. */
- if (data_rep->revision != rev)
- return SVN_NO_ERROR;
-
- /* We could use get_dir_contents(), but this is much cheaper. It does
- rely on directory entries being stored as PLAIN reps, though. */
- offset = data_rep->offset;
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
- SVN_ERR(read_rep_line(&ra, rev_file, pool));
- if (ra->is_delta)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Recovery encountered a deltified directory "
- "representation"));
-
- /* Now create a stream that's allowed to read only as much data as is
- stored in the representation. */
- baton.file = rev_file;
- baton.pool = pool;
- baton.remaining = data_rep->expanded_size
- ? data_rep->expanded_size
- : data_rep->size;
- stream = svn_stream_create(&baton, pool);
- svn_stream_set_read(stream, read_handler_recover);
-
- /* Now read the entries from that stream. */
- entries = apr_hash_make(pool);
- SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
+ int format = SVN_FS_FS__FORMAT_NUMBER;
+ int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
+ svn_boolean_t log_addressing;
- /* Now check each of the entries in our directory to find new node and
- copy ids, and recurse into new subdirectories. */
- iterpool = svn_pool_create(pool);
- for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ /* Process the given filesystem config. */
+ if (fs->config)
{
- char *str_val;
- char *str;
- svn_node_kind_t kind;
- svn_fs_id_t *id;
- const char *node_id, *copy_id;
- apr_off_t child_dir_offset;
- const svn_string_t *path = svn__apr_hash_index_val(hi);
-
- svn_pool_clear(iterpool);
-
- str_val = apr_pstrdup(iterpool, path->data);
-
- str = svn_cstring_tokenize(" ", &str_val);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt"));
-
- if (strcmp(str, KIND_FILE) == 0)
- kind = svn_node_file;
- else if (strcmp(str, KIND_DIR) == 0)
- kind = svn_node_dir;
- else
- {
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt"));
- }
-
- str = svn_cstring_tokenize(" ", &str_val);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Directory entry corrupt"));
-
- id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
+ svn_version_t *compatible_version;
+ const char *shard_size_str;
+ SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
+ pool));
- if (svn_fs_fs__id_rev(id) != rev)
+ /* select format number */
+ switch(compatible_version->minor)
{
- /* If the node wasn't modified in this revision, we've already
- checked the node and copy id. */
- continue;
- }
+ case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("FSFS is not compatible with Subversion prior to 1.1"));
- node_id = svn_fs_fs__id_node_id(id);
- copy_id = svn_fs_fs__id_copy_id(id);
+ case 1:
+ case 2:
+ case 3: format = 1;
+ break;
- if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
- {
- SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
- apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
- }
- if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
- {
- SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
- apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
- }
+ case 4: format = 2;
+ break;
- if (kind == svn_node_file)
- continue;
+ case 5: format = 3;
+ break;
- child_dir_offset = svn_fs_fs__id_offset(id);
- SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
- max_node_id, max_copy_id, iterpool));
- }
- svn_pool_destroy(iterpool);
+ case 6:
+ case 7: format = 4;
+ break;
- return SVN_NO_ERROR;
-}
-
-/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
- * Use POOL for temporary allocations.
- * Set *MISSING, if the reason is a missing manifest or pack file.
- */
-static svn_boolean_t
-packed_revprop_available(svn_boolean_t *missing,
- svn_fs_t *fs,
- svn_revnum_t revision,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_stringbuf_t *content = NULL;
-
- /* try to read the manifest file */
- const char *folder = path_revprops_pack_shard(fs, revision, pool);
- const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
-
- svn_error_t *err = try_stringbuf_from_file(&content,
- missing,
- manifest_path,
- FALSE,
- pool);
-
- /* if the manifest cannot be read, consider the pack files inaccessible
- * even if the file itself exists. */
- if (err)
- {
- svn_error_clear(err);
- return FALSE;
- }
+ case 8: format = 6;
+ break;
- if (*missing)
- return FALSE;
-
- /* parse manifest content until we find the entry for REVISION.
- * Revision 0 is never packed. */
- revision = revision < ffd->max_files_per_dir
- ? revision - 1
- : revision % ffd->max_files_per_dir;
- while (content->data)
- {
- char *next = strchr(content->data, '\n');
- if (next)
- {
- *next = 0;
- ++next;
+ default:format = SVN_FS_FS__FORMAT_NUMBER;
}
- if (revision-- == 0)
+ shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
+ if (shard_size_str)
{
- /* the respective pack file must exist (and be a file) */
- svn_node_kind_t kind;
- err = svn_io_check_path(svn_dirent_join(folder, content->data,
- pool),
- &kind, pool);
- if (err)
- {
- svn_error_clear(err);
- return FALSE;
- }
+ apr_int64_t val;
+ SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
+ APR_INT32_MAX, 10));
- *missing = kind == svn_node_none;
- return kind == svn_node_file;
+ shard_size = (int) val;
}
-
- content->data = next;
}
- return FALSE;
-}
+ log_addressing = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
+ TRUE);
-/* Baton used for recover_body below. */
-struct recover_baton {
- svn_fs_t *fs;
- svn_cancel_func_t cancel_func;
- void *cancel_baton;
-};
+ /* Actual FS creation. */
+ SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
+ log_addressing, pool));
-/* The work-horse for svn_fs_fs__recover, called with the FS
- write lock. This implements the svn_fs_fs__with_write_lock()
- 'body' callback type. BATON is a 'struct recover_baton *'. */
-static svn_error_t *
-recover_body(void *baton, apr_pool_t *pool)
-{
- struct recover_baton *b = baton;
- svn_fs_t *fs = b->fs;
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_revnum_t max_rev;
- char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
- char *next_node_id = NULL, *next_copy_id = NULL;
- svn_revnum_t youngest_rev;
- svn_node_kind_t youngest_revprops_kind;
-
- /* Lose potentially corrupted data in temp files */
- SVN_ERR(cleanup_revprop_namespace(fs));
-
- /* We need to know the largest revision in the filesystem. */
- SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
-
- /* Get the expected youngest revision */
- SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
-
- /* Policy note:
-
- Since the revprops file is written after the revs file, the true
- maximum available revision is the youngest one for which both are
- present. That's probably the same as the max_rev we just found,
- but if it's not, we could, in theory, repeatedly decrement
- max_rev until we find a revision that has both a revs and
- revprops file, then write db/current with that.
-
- But we choose not to. If a repository is so corrupt that it's
- missing at least one revprops file, we shouldn't assume that the
- youngest revision for which both the revs and revprops files are
- present is healthy. In other words, we're willing to recover
- from a missing or out-of-date db/current file, because db/current
- is truly redundant -- it's basically a cache so we don't have to
- find max_rev each time, albeit a cache with unusual semantics,
- since it also officially defines when a revision goes live. But
- if we're missing more than the cache, it's time to back out and
- let the admin reconstruct things by hand: correctness at that
- point may depend on external things like checking a commit email
- list, looking in particular working copies, etc.
-
- This policy matches well with a typical naive backup scenario.
- Say you're rsyncing your FSFS repository nightly to the same
- location. Once revs and revprops are written, you've got the
- maximum rev; if the backup should bomb before db/current is
- written, then db/current could stay arbitrarily out-of-date, but
- we can still recover. It's a small window, but we might as well
- do what we can. */
-
- /* Even if db/current were missing, it would be created with 0 by
- get_youngest(), so this conditional remains valid. */
- if (youngest_rev > max_rev)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Expected current rev to be <= %ld "
- "but found %ld"), max_rev, youngest_rev);
-
- /* We only need to search for maximum IDs for old FS formats which
- se global ID counters. */
- if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- {
- /* Next we need to find the maximum node id and copy id in use across the
- filesystem. Unfortunately, the only way we can get this information
- is to scan all the noderevs of all the revisions and keep track as
- we go along. */
- svn_revnum_t rev;
- apr_pool_t *iterpool = svn_pool_create(pool);
- char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
- apr_size_t len;
-
- for (rev = 0; rev <= max_rev; rev++)
- {
- apr_file_t *rev_file;
- apr_off_t root_offset;
-
- svn_pool_clear(iterpool);
-
- if (b->cancel_func)
- SVN_ERR(b->cancel_func(b->cancel_baton));
-
- SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
- SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
- iterpool));
- SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
- max_node_id, max_copy_id, iterpool));
- SVN_ERR(svn_io_file_close(rev_file, iterpool));
- }
- svn_pool_destroy(iterpool);
-
- /* Now that we finally have the maximum revision, node-id and copy-id, we
- can bump the two ids to get the next of each. */
- len = strlen(max_node_id);
- svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
- next_node_id = next_node_id_buf;
- len = strlen(max_copy_id);
- svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
- next_copy_id = next_copy_id_buf;
- }
-
- /* Before setting current, verify that there is a revprops file
- for the youngest revision. (Issue #2992) */
- SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
- &youngest_revprops_kind, pool));
- if (youngest_revprops_kind == svn_node_none)
- {
- svn_boolean_t missing = TRUE;
- if (!packed_revprop_available(&missing, fs, max_rev, pool))
- {
- if (missing)
- {
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Revision %ld has a revs file but no "
- "revprops file"),
- max_rev);
- }
- else
- {
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Revision %ld has a revs file but the "
- "revprops file is inaccessible"),
- max_rev);
- }
- }
- }
- else if (youngest_revprops_kind != svn_node_file)
- {
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Revision %ld has a non-file where its "
- "revprops file should be"),
- max_rev);
- }
-
- /* Prune younger-than-(newfound-youngest) revisions from the rep
- cache if sharing is enabled taking care not to create the cache
- if it does not exist. */
- if (ffd->rep_sharing_allowed)
- {
- svn_boolean_t rep_cache_exists;
-
- SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
- if (rep_cache_exists)
- SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
- }
-
- /* Now store the discovered youngest revision, and the next IDs if
- relevant, in a new 'current' file. */
- return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
-}
+ /* This filesystem is ready. Stamp it with a format number. */
+ SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
-/* This implements the fs_library_vtable_t.recover() API. */
-svn_error_t *
-svn_fs_fs__recover(svn_fs_t *fs,
- svn_cancel_func_t cancel_func, void *cancel_baton,
- apr_pool_t *pool)
-{
- struct recover_baton b;
-
- /* We have no way to take out an exclusive lock in FSFS, so we're
- restricted as to the types of recovery we can do. Luckily,
- we just want to recreate the 'current' file, and we can do that just
- by blocking other writers. */
- b.fs = fs;
- b.cancel_func = cancel_func;
- b.cancel_baton = cancel_baton;
- return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
+ return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__set_uuid(svn_fs_t *fs,
const char *uuid,
+ const char *instance_id,
apr_pool_t *pool)
{
- char *my_uuid;
- apr_size_t my_uuid_len;
- const char *tmp_path;
+ fs_fs_data_t *ffd = fs->fsap_data;
const char *uuid_path = path_uuid(fs, pool);
+ svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
if (! uuid)
uuid = svn_uuid_generate(pool);
- /* Make sure we have a copy in FS->POOL, and append a newline. */
- my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
- my_uuid_len = strlen(my_uuid);
+ if (! instance_id)
+ instance_id = svn_uuid_generate(pool);
- SVN_ERR(svn_io_write_unique(&tmp_path,
- svn_dirent_dirname(uuid_path, pool),
- my_uuid, my_uuid_len,
- svn_io_file_del_none, pool));
+ svn_stringbuf_appendcstr(contents, uuid);
+ svn_stringbuf_appendcstr(contents, "\n");
+
+ if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
+ {
+ svn_stringbuf_appendcstr(contents, instance_id);
+ svn_stringbuf_appendcstr(contents, "\n");
+ }
/* We use the permissions of the 'current' file, because the 'uuid'
file does not exist during repository creation. */
- SVN_ERR(move_into_place(tmp_path, uuid_path,
- svn_fs_fs__path_current(fs, pool), pool));
+ SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
+ svn_fs_fs__path_current(fs, pool) /* perms */,
+ pool));
- /* Remove the newline we added, and stash the UUID. */
- my_uuid[my_uuid_len - 1] = '\0';
- fs->uuid = my_uuid;
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+
+ if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
+ ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
+ else
+ ffd->instance_id = fs->uuid;
return SVN_NO_ERROR;
}
@@ -9492,29 +1943,37 @@ get_node_origins_from_file(svn_fs_t *fs,
stream = svn_stream_from_aprfile2(fd, FALSE, pool);
*node_origins = apr_hash_make(pool);
- SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
+ err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
+ if (err)
+ return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
+ node_origins_file);
return svn_stream_close(stream);
}
svn_error_t *
svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
svn_fs_t *fs,
- const char *node_id,
+ const svn_fs_fs__id_part_t *node_id,
apr_pool_t *pool)
{
apr_hash_t *node_origins;
*origin_id = NULL;
SVN_ERR(get_node_origins_from_file(fs, &node_origins,
- path_node_origin(fs, node_id, pool),
+ svn_fs_fs__path_node_origin(fs, node_id,
+ pool),
pool));
if (node_origins)
{
- svn_string_t *origin_id_str =
- svn_hash_gets(node_origins, node_id);
+ char node_id_ptr[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
+ svn_string_t *origin_id_str
+ = apr_hash_get(node_origins, node_id_ptr, len);
+
if (origin_id_str)
- *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
- origin_id_str->len, pool);
+ SVN_ERR(svn_fs_fs__id_parse(origin_id,
+ apr_pstrdup(pool, origin_id_str->data),
+ pool));
}
return SVN_NO_ERROR;
}
@@ -9525,7 +1984,7 @@ svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
static svn_error_t *
set_node_origins_for_file(svn_fs_t *fs,
const char *node_origins_path,
- const char *node_id,
+ const svn_fs_fs__id_part_t *node_id,
svn_string_t *node_rev_id,
apr_pool_t *pool)
{
@@ -9534,6 +1993,10 @@ set_node_origins_for_file(svn_fs_t *fs,
apr_hash_t *origins_hash;
svn_string_t *old_node_rev_id;
+ /* the hash serialization functions require strings as keys */
+ char node_id_ptr[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
+
SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
PATH_NODE_ORIGINS_DIR,
pool),
@@ -9546,16 +2009,17 @@ set_node_origins_for_file(svn_fs_t *fs,
if (! origins_hash)
origins_hash = apr_hash_make(pool);
- old_node_rev_id = svn_hash_gets(origins_hash, node_id);
+ old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Node origin for '%s' exists with a different "
"value (%s) than what we were about to store "
"(%s)"),
- node_id, old_node_rev_id->data, node_rev_id->data);
+ node_id_ptr, old_node_rev_id->data,
+ node_rev_id->data);
- svn_hash_sets(origins_hash, node_id, node_rev_id);
+ apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
/* Sure, there's a race condition here. Two processes could be
trying to add different cache elements to the same file at the
@@ -9578,12 +2042,12 @@ set_node_origins_for_file(svn_fs_t *fs,
svn_error_t *
svn_fs_fs__set_node_origin(svn_fs_t *fs,
- const char *node_id,
+ const svn_fs_fs__id_part_t *node_id,
const svn_fs_id_t *node_rev_id,
apr_pool_t *pool)
{
svn_error_t *err;
- const char *filename = path_node_origin(fs, node_id, pool);
+ const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
err = set_node_origins_for_file(fs, filename,
node_id,
@@ -9599,128 +2063,6 @@ svn_fs_fs__set_node_origin(svn_fs_t *fs,
}
-svn_error_t *
-svn_fs_fs__list_transactions(apr_array_header_t **names_p,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- const char *txn_dir;
- apr_hash_t *dirents;
- apr_hash_index_t *hi;
- apr_array_header_t *names;
- apr_size_t ext_len = strlen(PATH_EXT_TXN);
-
- names = apr_array_make(pool, 1, sizeof(const char *));
-
- /* Get the transactions directory. */
- txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
-
- /* Now find a listing of this directory. */
- SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
-
- /* Loop through all the entries and return anything that ends with '.txn'. */
- for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
- {
- const char *name = svn__apr_hash_index_key(hi);
- apr_ssize_t klen = svn__apr_hash_index_klen(hi);
- const char *id;
-
- /* The name must end with ".txn" to be considered a transaction. */
- if ((apr_size_t) klen <= ext_len
- || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
- continue;
-
- /* Truncate the ".txn" extension and store the ID. */
- id = apr_pstrndup(pool, name, strlen(name) - ext_len);
- APR_ARRAY_PUSH(names, const char *) = id;
- }
-
- *names_p = names;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
- svn_fs_t *fs,
- const char *name,
- apr_pool_t *pool)
-{
- svn_fs_txn_t *txn;
- svn_node_kind_t kind;
- transaction_t *local_txn;
-
- /* First check to see if the directory exists. */
- SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
-
- /* Did we find it? */
- if (kind != svn_node_dir)
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
- _("No such transaction '%s'"),
- name);
-
- txn = apr_pcalloc(pool, sizeof(*txn));
-
- /* Read in the root node of this transaction. */
- txn->id = apr_pstrdup(pool, name);
- txn->fs = fs;
-
- SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
-
- txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
-
- txn->vtable = &txn_vtable;
- *txn_p = txn;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__txn_proplist(apr_hash_t **table_p,
- svn_fs_txn_t *txn,
- apr_pool_t *pool)
-{
- apr_hash_t *proplist = apr_hash_make(pool);
- SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
- *table_p = proplist;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__delete_node_revision(svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *pool)
-{
- node_revision_t *noderev;
-
- SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
-
- /* Delete any mutable property representation. */
- if (noderev->prop_rep && noderev->prop_rep->txn_id)
- SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
- pool));
-
- /* Delete any mutable data representation. */
- if (noderev->data_rep && noderev->data_rep->txn_id
- && noderev->kind == svn_node_dir)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
- SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
- pool));
-
- /* remove the corresponding entry from the cache, if such exists */
- if (ffd->txn_dir_cache)
- {
- const char *key = svn_fs_fs__id_unparse(id, pool)->data;
- SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
- }
- }
-
- return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
-}
-
-
/*** Revisions ***/
@@ -9734,7 +2076,7 @@ svn_fs_fs__revision_prop(svn_string_t **value_p,
apr_hash_t *table;
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
+ SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool));
*value_p = svn_hash_gets(table, propname);
@@ -9760,7 +2102,7 @@ change_rev_prop_body(void *baton, apr_pool_t *pool)
struct change_rev_prop_baton *cb = baton;
apr_hash_t *table;
- SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
+ SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool));
if (cb->old_value_p)
{
@@ -9780,7 +2122,7 @@ change_rev_prop_body(void *baton, apr_pool_t *pool)
}
svn_hash_sets(table, cb->name, cb->value);
- return set_revision_proplist(cb->fs, cb->rev, table, pool);
+ return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
}
svn_error_t *
@@ -9804,1849 +2146,60 @@ svn_fs_fs__change_rev_prop(svn_fs_t *fs,
return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
}
-
-/*** Transactions ***/
-
svn_error_t *
-svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
- const svn_fs_id_t **base_root_id_p,
+svn_fs_fs__info_format(int *fs_format,
+ svn_version_t **supports_version,
svn_fs_t *fs,
- const char *txn_name,
- apr_pool_t *pool)
-{
- transaction_t *txn;
- SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
- *root_id_p = txn->root_id;
- *base_root_id_p = txn->base_id;
- return SVN_NO_ERROR;
-}
-
-
-/* Generic transaction operations. */
-
-svn_error_t *
-svn_fs_fs__txn_prop(svn_string_t **value_p,
- svn_fs_txn_t *txn,
- const char *propname,
- apr_pool_t *pool)
-{
- apr_hash_t *table;
- svn_fs_t *fs = txn->fs;
-
- SVN_ERR(svn_fs__check_fs(fs, TRUE));
- SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
-
- *value_p = svn_hash_gets(table, propname);
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_uint32_t flags,
- apr_pool_t *pool)
-{
- svn_string_t date;
- svn_prop_t prop;
- apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
-
- SVN_ERR(svn_fs__check_fs(fs, TRUE));
-
- SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
-
- /* Put a datestamp on the newly created txn, so we always know
- exactly how old it is. (This will help sysadmins identify
- long-abandoned txns that may need to be manually removed.) When
- a txn is promoted to a revision, this property will be
- automatically overwritten with a revision datestamp. */
- date.data = svn_time_to_cstring(apr_time_now(), pool);
- date.len = strlen(date.data);
-
- prop.name = SVN_PROP_REVISION_DATE;
- prop.value = &date;
- APR_ARRAY_PUSH(props, svn_prop_t) = prop;
-
- /* Set temporary txn props that represent the requested 'flags'
- behaviors. */
- if (flags & SVN_FS_TXN_CHECK_OOD)
- {
- prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
- prop.value = svn_string_create("true", pool);
- APR_ARRAY_PUSH(props, svn_prop_t) = prop;
- }
-
- if (flags & SVN_FS_TXN_CHECK_LOCKS)
- {
- prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
- prop.value = svn_string_create("true", pool);
- APR_ARRAY_PUSH(props, svn_prop_t) = prop;
- }
-
- return svn_fs_fs__change_txn_props(*txn_p, props, pool);
-}
-
-
-/****** Packing FSFS shards *********/
-
-/* Write a file FILENAME in directory FS_PATH, containing a single line
- * with the number REVNUM in ASCII decimal. Move the file into place
- * atomically, overwriting any existing file.
- *
- * Similar to write_current(). */
-static svn_error_t *
-write_revnum_file(const char *fs_path,
- const char *filename,
- svn_revnum_t revnum,
- apr_pool_t *scratch_pool)
-{
- const char *final_path, *tmp_path;
- svn_stream_t *tmp_stream;
-
- final_path = svn_dirent_join(fs_path, filename, scratch_pool);
- SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
- svn_io_file_del_none,
- scratch_pool, scratch_pool));
- SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
- SVN_ERR(svn_stream_close(tmp_stream));
- SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
- return SVN_NO_ERROR;
-}
-
-/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
- * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
- * CANCEL_FUNC and CANCEL_BATON are what you think they are.
- *
- * If for some reason we detect a partial packing already performed, we
- * remove the pack file and start again.
- */
-static svn_error_t *
-pack_rev_shard(const char *pack_file_dir,
- const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- const char *pack_file_path, *manifest_file_path;
- svn_stream_t *pack_stream, *manifest_stream;
- svn_revnum_t start_rev, end_rev, rev;
- apr_off_t next_offset;
- apr_pool_t *iterpool;
-
- /* Some useful paths. */
- pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
- manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
-
- /* Remove any existing pack file for this shard, since it is incomplete. */
- SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
- pool));
-
- /* Create the new directory and pack and manifest files. */
- SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
- SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
- pool));
- SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
- pool, pool));
-
- start_rev = (svn_revnum_t) (shard * max_files_per_dir);
- end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
- next_offset = 0;
- iterpool = svn_pool_create(pool);
-
- /* Iterate over the revisions in this shard, squashing them together. */
- for (rev = start_rev; rev <= end_rev; rev++)
- {
- svn_stream_t *rev_stream;
- apr_finfo_t finfo;
- const char *path;
-
- svn_pool_clear(iterpool);
-
- /* Get the size of the file. */
- path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
- iterpool);
- SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
-
- /* Update the manifest. */
- SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
- "\n", next_offset));
- next_offset += finfo.size;
-
- /* Copy all the bits from the rev file to the end of the pack file. */
- SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
- SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
- iterpool),
- cancel_func, cancel_baton, iterpool));
- }
-
- SVN_ERR(svn_stream_close(manifest_stream));
- SVN_ERR(svn_stream_close(pack_stream));
- SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
- SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
- SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
- * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
- *
- * The file sizes have already been determined and written to SIZES.
- * Please note that this function will be executed while the filesystem
- * has been locked and that revprops files will therefore not be modified
- * while the pack is in progress.
- *
- * COMPRESSION_LEVEL defines how well the resulting pack file shall be
- * compressed or whether is shall be compressed at all. TOTAL_SIZE is
- * a hint on which initial buffer size we should use to hold the pack file
- * content.
- *
- * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
- * are done in SCRATCH_POOL.
- */
-static svn_error_t *
-copy_revprops(const char *pack_file_dir,
- const char *pack_filename,
- const char *shard_path,
- svn_revnum_t start_rev,
- svn_revnum_t end_rev,
- apr_array_header_t *sizes,
- apr_size_t total_size,
- int compression_level,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool)
-{
- svn_stream_t *pack_stream;
- apr_file_t *pack_file;
- svn_revnum_t rev;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- svn_stream_t *stream;
-
- /* create empty data buffer and a write stream on top of it */
- svn_stringbuf_t *uncompressed
- = svn_stringbuf_create_ensure(total_size, scratch_pool);
- svn_stringbuf_t *compressed
- = svn_stringbuf_create_empty(scratch_pool);
- pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
-
- /* write the pack file header */
- SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
- sizes->nelts, iterpool));
-
- /* Some useful paths. */
- SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
- pack_filename,
- scratch_pool),
- APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
- scratch_pool));
-
- /* Iterate over the revisions in this shard, squashing them together. */
- for (rev = start_rev; rev <= end_rev; rev++)
- {
- const char *path;
-
- svn_pool_clear(iterpool);
-
- /* Construct the file name. */
- path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
- iterpool);
-
- /* Copy all the bits from the non-packed revprop file to the end of
- * the pack file. */
- SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
- SVN_ERR(svn_stream_copy3(stream, pack_stream,
- cancel_func, cancel_baton, iterpool));
- }
-
- /* flush stream buffers to content buffer */
- SVN_ERR(svn_stream_close(pack_stream));
-
- /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
- SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
- compressed, compression_level));
-
- /* write the pack file content to disk */
- stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
- SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
- SVN_ERR(svn_stream_close(stream));
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
- * revprop files in it, create a packed shared at PACK_FILE_DIR.
- *
- * COMPRESSION_LEVEL defines how well the resulting pack file shall be
- * compressed or whether is shall be compressed at all. Individual pack
- * file containing more than one revision will be limited to a size of
- * MAX_PACK_SIZE bytes before compression.
- *
- * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
- * allocations are done in SCRATCH_POOL.
- */
-static svn_error_t *
-pack_revprops_shard(const char *pack_file_dir,
- const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- apr_off_t max_pack_size,
- int compression_level,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool)
-{
- const char *manifest_file_path, *pack_filename = NULL;
- svn_stream_t *manifest_stream;
- svn_revnum_t start_rev, end_rev, rev;
- apr_off_t total_size;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- apr_array_header_t *sizes;
-
- /* Some useful paths. */
- manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
- scratch_pool);
-
- /* Remove any existing pack file for this shard, since it is incomplete. */
- SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
- scratch_pool));
-
- /* Create the new directory and manifest file stream. */
- SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
- SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
- scratch_pool, scratch_pool));
-
- /* revisions to handle. Special case: revision 0 */
- start_rev = (svn_revnum_t) (shard * max_files_per_dir);
- end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
- if (start_rev == 0)
- ++start_rev;
-
- /* initialize the revprop size info */
- sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
- total_size = 2 * SVN_INT64_BUFFER_SIZE;
-
- /* Iterate over the revisions in this shard, determine their size and
- * squashing them together into pack files. */
- for (rev = start_rev; rev <= end_rev; rev++)
- {
- apr_finfo_t finfo;
- const char *path;
-
- svn_pool_clear(iterpool);
-
- /* Get the size of the file. */
- path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
- iterpool);
- SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
-
- /* if we already have started a pack file and this revprop cannot be
- * appended to it, write the previous pack file. */
- if (sizes->nelts != 0 &&
- total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
- {
- SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
- start_rev, rev-1, sizes, (apr_size_t)total_size,
- compression_level, cancel_func, cancel_baton,
- iterpool));
-
- /* next pack file starts empty again */
- apr_array_clear(sizes);
- total_size = 2 * SVN_INT64_BUFFER_SIZE;
- start_rev = rev;
- }
-
- /* Update the manifest. Allocate a file name for the current pack
- * file if it is a new one */
- if (sizes->nelts == 0)
- pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
-
- SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
- pack_filename));
-
- /* add to list of files to put into the current pack file */
- APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
- total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
- }
-
- /* write the last pack file */
- if (sizes->nelts != 0)
- SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
- start_rev, rev-1, sizes, (apr_size_t)total_size,
- compression_level, cancel_func, cancel_baton,
- iterpool));
-
- /* flush the manifest file and update permissions */
- SVN_ERR(svn_stream_close(manifest_stream));
- SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
- * MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the
- * revprop file for revision 0.
- *
- * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
- * allocations are done in SCRATCH_POOL.
- */
-static svn_error_t *
-delete_revprops_shard(const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool)
-{
- if (shard == 0)
- {
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- int i;
-
- /* delete all files except the one for revision 0 */
- for (i = 1; i < max_files_per_dir; ++i)
- {
- const char *path = svn_dirent_join(shard_path,
- apr_psprintf(iterpool, "%d", i),
- iterpool);
- if (cancel_func)
- SVN_ERR((*cancel_func)(cancel_baton));
-
- SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
- svn_pool_clear(iterpool);
- }
-
- svn_pool_destroy(iterpool);
- }
- else
- SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
- cancel_func, cancel_baton, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
- * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
- * for allocations. REVPROPS_DIR will be NULL if revprop packing is not
- * supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
- * case.
- *
- * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
- * NOTIFY_FUNC and NOTIFY_BATON.
- *
- * If for some reason we detect a partial packing already performed, we
- * remove the pack file and start again.
- */
-static svn_error_t *
-pack_shard(const char *revs_dir,
- const char *revsprops_dir,
- const char *fs_path,
- apr_int64_t shard,
- int max_files_per_dir,
- apr_off_t max_pack_size,
- int compression_level,
- svn_fs_pack_notify_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- const char *rev_shard_path, *rev_pack_file_dir;
- const char *revprops_shard_path, *revprops_pack_file_dir;
-
- /* Notify caller we're starting to pack this shard. */
- if (notify_func)
- SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
- pool));
-
- /* Some useful paths. */
- rev_pack_file_dir = svn_dirent_join(revs_dir,
- apr_psprintf(pool,
- "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
- shard),
- pool);
- rev_shard_path = svn_dirent_join(revs_dir,
- apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
- pool);
-
- /* pack the revision content */
- SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
- shard, max_files_per_dir,
- cancel_func, cancel_baton, pool));
-
- /* if enabled, pack the revprops in an equivalent way */
- if (revsprops_dir)
- {
- revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
- apr_psprintf(pool,
- "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
- shard),
- pool);
- revprops_shard_path = svn_dirent_join(revsprops_dir,
- apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
- pool);
-
- SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
- shard, max_files_per_dir,
- (int)(0.9 * max_pack_size),
- compression_level,
- cancel_func, cancel_baton, pool));
- }
-
- /* Update the min-unpacked-rev file to reflect our newly packed shard.
- * (This doesn't update ffd->min_unpacked_rev. That will be updated by
- * update_min_unpacked_rev() when necessary.) */
- SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
- (svn_revnum_t)((shard + 1) * max_files_per_dir),
- pool));
-
- /* Finally, remove the existing shard directories. */
- SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
- cancel_func, cancel_baton, pool));
- if (revsprops_dir)
- SVN_ERR(delete_revprops_shard(revprops_shard_path,
- shard, max_files_per_dir,
- cancel_func, cancel_baton, pool));
-
- /* Notify caller we're starting to pack this shard. */
- if (notify_func)
- SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
- pool));
-
- return SVN_NO_ERROR;
-}
-
-struct pack_baton
-{
- svn_fs_t *fs;
- svn_fs_pack_notify_t notify_func;
- void *notify_baton;
- svn_cancel_func_t cancel_func;
- void *cancel_baton;
-};
-
-
-/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
- This implements the svn_fs_fs__with_write_lock() 'body' callback
- type. BATON is a 'struct pack_baton *'.
-
- WARNING: if you add a call to this function, please note:
- The code currently assumes that any piece of code running with
- the write-lock set can rely on the ffd->min_unpacked_rev and
- ffd->min_unpacked_revprop caches to be up-to-date (and, by
- extension, on not having to use a retry when calling
- svn_fs_fs__path_rev_absolute() and friends). If you add a call
- to this function, consider whether you have to call
- update_min_unpacked_rev().
- See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
- */
-static svn_error_t *
-pack_body(void *baton,
- apr_pool_t *pool)
-{
- struct pack_baton *pb = baton;
- fs_fs_data_t ffd = {0};
- apr_int64_t completed_shards;
- apr_int64_t i;
- svn_revnum_t youngest;
- apr_pool_t *iterpool;
- const char *rev_data_path;
- const char *revprops_data_path = NULL;
-
- /* read repository settings */
- SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
- path_format(pb->fs, pool), pool));
- SVN_ERR(check_format(ffd.format));
- SVN_ERR(read_config(&ffd, pb->fs->path, pool));
-
- /* If the repository isn't a new enough format, we don't support packing.
- Return a friendly error to that effect. */
- if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
- ffd.format);
-
- /* If we aren't using sharding, we can't do any packing, so quit. */
- if (!ffd.max_files_per_dir)
- return SVN_NO_ERROR;
-
- SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
- path_min_unpacked_rev(pb->fs, pool),
- pool));
-
- SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
- completed_shards = (youngest + 1) / ffd.max_files_per_dir;
-
- /* See if we've already completed all possible shards thus far. */
- if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
- return SVN_NO_ERROR;
-
- rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
- if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
- pool);
-
- iterpool = svn_pool_create(pool);
- for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
- i < completed_shards;
- i++)
- {
- svn_pool_clear(iterpool);
-
- if (pb->cancel_func)
- SVN_ERR(pb->cancel_func(pb->cancel_baton));
-
- SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
- pb->fs->path, i, ffd.max_files_per_dir,
- ffd.revprop_pack_size,
- ffd.compress_packed_revprops
- ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
- : SVN_DELTA_COMPRESSION_LEVEL_NONE,
- pb->notify_func, pb->notify_baton,
- pb->cancel_func, pb->cancel_baton, iterpool));
- }
-
- svn_pool_destroy(iterpool);
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__pack(svn_fs_t *fs,
- svn_fs_pack_notify_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- struct pack_baton pb = { 0 };
- pb.fs = fs;
- pb.notify_func = notify_func;
- pb.notify_baton = notify_baton;
- pb.cancel_func = cancel_func;
- pb.cancel_baton = cancel_baton;
- return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
-}
-
-
-/** Verifying. **/
-
-/* Baton type expected by verify_walker(). The purpose is to reuse open
- * rev / pack file handles between calls. Its contents need to be cleaned
- * periodically to limit resource usage.
- */
-typedef struct verify_walker_baton_t
-{
- /* number of calls to verify_walker() since the last clean */
- int iteration_count;
-
- /* number of files opened since the last clean */
- int file_count;
-
- /* progress notification callback to invoke periodically (may be NULL) */
- svn_fs_progress_notify_func_t notify_func;
-
- /* baton to use with NOTIFY_FUNC */
- void *notify_baton;
-
- /* remember the last revision for which we called notify_func */
- svn_revnum_t last_notified_revision;
-
- /* current file handle (or NULL) */
- apr_file_t *file_hint;
-
- /* corresponding revision (or SVN_INVALID_REVNUM) */
- svn_revnum_t rev_hint;
-
- /* pool to use for the file handles etc. */
- apr_pool_t *pool;
-} verify_walker_baton_t;
-
-/* Used by svn_fs_fs__verify().
- Implements svn_fs_fs__walk_rep_reference().walker. */
-static svn_error_t *
-verify_walker(representation_t *rep,
- void *baton,
- svn_fs_t *fs,
- apr_pool_t *scratch_pool)
-{
- struct rep_state *rs;
- struct rep_args *rep_args;
-
- if (baton)
- {
- verify_walker_baton_t *walker_baton = baton;
- apr_file_t * previous_file;
-
- /* notify and free resources periodically */
- if ( walker_baton->iteration_count > 1000
- || walker_baton->file_count > 16)
- {
- if ( walker_baton->notify_func
- && rep->revision != walker_baton->last_notified_revision)
- {
- walker_baton->notify_func(rep->revision,
- walker_baton->notify_baton,
- scratch_pool);
- walker_baton->last_notified_revision = rep->revision;
- }
-
- svn_pool_clear(walker_baton->pool);
-
- walker_baton->iteration_count = 0;
- walker_baton->file_count = 0;
- walker_baton->file_hint = NULL;
- walker_baton->rev_hint = SVN_INVALID_REVNUM;
- }
-
- /* access the repo data */
- previous_file = walker_baton->file_hint;
- SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
- &walker_baton->rev_hint, rep, fs,
- walker_baton->pool));
-
- /* update resource usage counters */
- walker_baton->iteration_count++;
- if (previous_file != walker_baton->file_hint)
- walker_baton->file_count++;
- }
- else
- {
- /* ### Should this be using read_rep_line() directly? */
- SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__verify(svn_fs_t *fs,
- svn_revnum_t start,
- svn_revnum_t end,
- svn_fs_progress_notify_func_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_boolean_t exists;
- svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
-
- if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- return SVN_NO_ERROR;
-
- /* Input validation. */
- if (! SVN_IS_VALID_REVNUM(start))
- start = 0;
- if (! SVN_IS_VALID_REVNUM(end))
- end = youngest;
- SVN_ERR(ensure_revision_exists(fs, start, pool));
- SVN_ERR(ensure_revision_exists(fs, end, pool));
-
- /* rep-cache verification. */
- SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
- if (exists)
- {
- /* provide a baton to allow the reuse of open file handles between
- iterations (saves 2/3 of OS level file operations). */
- verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
- baton->rev_hint = SVN_INVALID_REVNUM;
- baton->pool = svn_pool_create(pool);
- baton->last_notified_revision = SVN_INVALID_REVNUM;
- baton->notify_func = notify_func;
- baton->notify_baton = notify_baton;
-
- /* tell the user that we are now ready to do *something* */
- if (notify_func)
- notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
-
- /* Do not attempt to walk the rep-cache database if its file does
- not exist, since doing so would create it --- which may confuse
- the administrator. Don't take any lock. */
- SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
- verify_walker, baton,
- cancel_func, cancel_baton,
- pool));
-
- /* walker resource cleanup */
- svn_pool_destroy(baton->pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-
-/** Hotcopy. **/
-
-/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
- * the destination and do not differ in terms of kind, size, and mtime. */
-static svn_error_t *
-hotcopy_io_dir_file_copy(const char *src_path,
- const char *dst_path,
- const char *file,
- apr_pool_t *scratch_pool)
-{
- const svn_io_dirent2_t *src_dirent;
- const svn_io_dirent2_t *dst_dirent;
- const char *src_target;
- const char *dst_target;
-
- /* Does the destination already exist? If not, we must copy it. */
- dst_target = svn_dirent_join(dst_path, file, scratch_pool);
- SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
- scratch_pool, scratch_pool));
- if (dst_dirent->kind != svn_node_none)
- {
- /* If the destination's stat information indicates that the file
- * is equal to the source, don't bother copying the file again. */
- src_target = svn_dirent_join(src_path, file, scratch_pool);
- SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
- scratch_pool, scratch_pool));
- if (src_dirent->kind == dst_dirent->kind &&
- src_dirent->special == dst_dirent->special &&
- src_dirent->filesize == dst_dirent->filesize &&
- src_dirent->mtime <= dst_dirent->mtime)
- return SVN_NO_ERROR;
- }
-
- return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
- scratch_pool));
-}
-
-/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
- * NAME is in the internal encoding used by APR; PARENT is in
- * UTF-8 and in internal (not local) style.
- *
- * Use PARENT only for generating an error string if the conversion
- * fails because NAME could not be represented in UTF-8. In that
- * case, return a two-level error in which the outer error's message
- * mentions PARENT, but the inner error's message does not mention
- * NAME (except possibly in hex) since NAME may not be printable.
- * Such a compound error at least allows the user to go looking in the
- * right directory for the problem.
- *
- * If there is any other error, just return that error directly.
- *
- * If there is any error, the effect on *NAME_P is undefined.
- *
- * *NAME_P and NAME may refer to the same storage.
- */
-static svn_error_t *
-entry_name_to_utf8(const char **name_p,
- const char *name,
- const char *parent,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
- if (err && err->apr_err == APR_EINVAL)
- {
- return svn_error_createf(err->apr_err, err,
- _("Error converting entry "
- "in directory '%s' to UTF-8"),
- svn_dirent_local_style(parent, pool));
- }
- return err;
-}
-
-/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
- * exist in the destination and do not differ from the source in terms of
- * kind, size, and mtime. */
-static svn_error_t *
-hotcopy_io_copy_dir_recursively(const char *src,
- const char *dst_parent,
- const char *dst_basename,
- svn_boolean_t copy_perms,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- svn_node_kind_t kind;
- apr_status_t status;
- const char *dst_path;
- apr_dir_t *this_dir;
- apr_finfo_t this_entry;
- apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
-
- /* Make a subpool for recursion */
- apr_pool_t *subpool = svn_pool_create(pool);
-
- /* The 'dst_path' is simply dst_parent/dst_basename */
- dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
-
- /* Sanity checks: SRC and DST_PARENT are directories, and
- DST_BASENAME doesn't already exist in DST_PARENT. */
- SVN_ERR(svn_io_check_path(src, &kind, subpool));
- if (kind != svn_node_dir)
- return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
- _("Source '%s' is not a directory"),
- svn_dirent_local_style(src, pool));
-
- SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
- if (kind != svn_node_dir)
- return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
- _("Destination '%s' is not a directory"),
- svn_dirent_local_style(dst_parent, pool));
-
- SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
-
- /* Create the new directory. */
- /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
- SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
-
- /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
- SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
-
- for (status = apr_dir_read(&this_entry, flags, this_dir);
- status == APR_SUCCESS;
- status = apr_dir_read(&this_entry, flags, this_dir))
- {
- if ((this_entry.name[0] == '.')
- && ((this_entry.name[1] == '\0')
- || ((this_entry.name[1] == '.')
- && (this_entry.name[2] == '\0'))))
- {
- continue;
- }
- else
- {
- const char *entryname_utf8;
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
- src, subpool));
- if (this_entry.filetype == APR_REG) /* regular file */
- {
- SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
- subpool));
- }
- else if (this_entry.filetype == APR_LNK) /* symlink */
- {
- const char *src_target = svn_dirent_join(src, entryname_utf8,
- subpool);
- const char *dst_target = svn_dirent_join(dst_path,
- entryname_utf8,
- subpool);
- SVN_ERR(svn_io_copy_link(src_target, dst_target,
- subpool));
- }
- else if (this_entry.filetype == APR_DIR) /* recurse */
- {
- const char *src_target;
-
- /* Prevent infinite recursion by filtering off our
- newly created destination path. */
- if (strcmp(src, dst_parent) == 0
- && strcmp(entryname_utf8, dst_basename) == 0)
- continue;
-
- src_target = svn_dirent_join(src, entryname_utf8, subpool);
- SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
- dst_path,
- entryname_utf8,
- copy_perms,
- cancel_func,
- cancel_baton,
- subpool));
- }
- /* ### support other APR node types someday?? */
-
- }
- }
-
- if (! (APR_STATUS_IS_ENOENT(status)))
- return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
- svn_dirent_local_style(src, pool));
-
- status = apr_dir_close(this_dir);
- if (status)
- return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
- svn_dirent_local_style(src, pool));
-
- /* Free any memory used by recursion */
- svn_pool_destroy(subpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
- * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
- * Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-hotcopy_copy_shard_file(const char *src_subdir,
- const char *dst_subdir,
- svn_revnum_t rev,
- int max_files_per_dir,
- apr_pool_t *scratch_pool)
-{
- const char *src_subdir_shard = src_subdir,
- *dst_subdir_shard = dst_subdir;
-
- if (max_files_per_dir)
- {
- const char *shard = apr_psprintf(scratch_pool, "%ld",
- rev / max_files_per_dir);
- src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
- dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
-
- if (rev % max_files_per_dir == 0)
- {
- SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
- SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
- scratch_pool));
- }
- }
-
- SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
- apr_psprintf(scratch_pool, "%ld", rev),
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-
-/* Copy a packed shard containing revision REV, and which contains
- * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
- * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
- * Do not re-copy data which already exists in DST_FS.
- * Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
- svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- svn_revnum_t rev,
- int max_files_per_dir,
- apr_pool_t *scratch_pool)
-{
- const char *src_subdir;
- const char *dst_subdir;
- const char *packed_shard;
- const char *src_subdir_packed_shard;
- svn_revnum_t revprop_rev;
- apr_pool_t *iterpool;
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
-
- /* Copy the packed shard. */
- src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
- dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
- packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
- rev / max_files_per_dir);
- src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
- scratch_pool);
- SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
- dst_subdir, packed_shard,
- TRUE /* copy_perms */,
- NULL /* cancel_func */, NULL,
- scratch_pool));
-
- /* Copy revprops belonging to revisions in this pack. */
- src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
- dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
-
- if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
- || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
- {
- /* copy unpacked revprops rev by rev */
- iterpool = svn_pool_create(scratch_pool);
- for (revprop_rev = rev;
- revprop_rev < rev + max_files_per_dir;
- revprop_rev++)
- {
- svn_pool_clear(iterpool);
-
- SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
- revprop_rev, max_files_per_dir,
- iterpool));
- }
- svn_pool_destroy(iterpool);
- }
- else
- {
- /* revprop for revision 0 will never be packed */
- if (rev == 0)
- SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
- 0, max_files_per_dir,
- scratch_pool));
-
- /* packed revprops folder */
- packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
- rev / max_files_per_dir);
- src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
- scratch_pool);
- SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
- dst_subdir, packed_shard,
- TRUE /* copy_perms */,
- NULL /* cancel_func */, NULL,
- scratch_pool));
- }
-
- /* If necessary, update the min-unpacked rev file in the hotcopy. */
- if (*dst_min_unpacked_rev < rev + max_files_per_dir)
- {
- *dst_min_unpacked_rev = rev + max_files_per_dir;
- SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
- *dst_min_unpacked_rev,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
- * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
- * Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-hotcopy_update_current(svn_revnum_t *dst_youngest,
- svn_fs_t *dst_fs,
- svn_revnum_t new_youngest,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- char next_node_id[MAX_KEY_SIZE] = "0";
- char next_copy_id[MAX_KEY_SIZE] = "0";
- fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
-
- if (*dst_youngest >= new_youngest)
- return SVN_NO_ERROR;
-
- /* If necessary, get new current next_node and next_copy IDs. */
- if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
- {
- apr_off_t root_offset;
- apr_file_t *rev_file;
- char max_node_id[MAX_KEY_SIZE] = "0";
- char max_copy_id[MAX_KEY_SIZE] = "0";
- apr_size_t len;
-
- if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
-
- SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
- scratch_pool));
- SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
- dst_fs, new_youngest, scratch_pool));
- SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
- root_offset, max_node_id, max_copy_id,
- scratch_pool));
- SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
-
- /* We store the _next_ ids. */
- len = strlen(max_node_id);
- svn_fs_fs__next_key(max_node_id, &len, next_node_id);
- len = strlen(max_copy_id);
- svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
- }
-
- /* Update 'current'. */
- SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
- scratch_pool));
-
- *dst_youngest = new_youngest;
-
- return SVN_NO_ERROR;
-}
-
-
-/* Remove revision or revprop files between START_REV (inclusive) and
- * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume
- * sharding as per MAX_FILES_PER_DIR.
- * Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-hotcopy_remove_files(svn_fs_t *dst_fs,
- const char *dst_subdir,
- svn_revnum_t start_rev,
- svn_revnum_t end_rev,
- int max_files_per_dir,
- apr_pool_t *scratch_pool)
-{
- const char *shard;
- const char *dst_subdir_shard;
- svn_revnum_t rev;
- apr_pool_t *iterpool;
-
- /* Pre-compute paths for initial shard. */
- shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
- dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
-
- iterpool = svn_pool_create(scratch_pool);
- for (rev = start_rev; rev < end_rev; rev++)
- {
- const char *path;
- svn_pool_clear(iterpool);
-
- /* If necessary, update paths for shard. */
- if (rev != start_rev && rev % max_files_per_dir == 0)
- {
- shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
- dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
- }
-
- /* remove files for REV */
- path = svn_dirent_join(dst_subdir_shard,
- apr_psprintf(iterpool, "%ld", rev),
- iterpool);
-
- /* Make the rev file writable and remove it. */
- SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
- SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
- * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
- * Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-hotcopy_remove_rev_files(svn_fs_t *dst_fs,
- svn_revnum_t start_rev,
- svn_revnum_t end_rev,
- int max_files_per_dir,
- apr_pool_t *scratch_pool)
-{
- SVN_ERR_ASSERT(start_rev <= end_rev);
- SVN_ERR(hotcopy_remove_files(dst_fs,
- svn_dirent_join(dst_fs->path,
- PATH_REVS_DIR,
- scratch_pool),
- start_rev, end_rev,
- max_files_per_dir, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Remove revision properties between START_REV (inclusive) and END_REV
- * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
- * Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will
- * not be deleted. */
-static svn_error_t *
-hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
- svn_revnum_t start_rev,
- svn_revnum_t end_rev,
- int max_files_per_dir,
- apr_pool_t *scratch_pool)
-{
- SVN_ERR_ASSERT(start_rev <= end_rev);
-
- /* don't delete rev 0 props */
- SVN_ERR(hotcopy_remove_files(dst_fs,
- svn_dirent_join(dst_fs->path,
- PATH_REVPROPS_DIR,
- scratch_pool),
- start_rev ? start_rev : 1, end_rev,
- max_files_per_dir, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Verify that DST_FS is a suitable destination for an incremental
- * hotcopy from SRC_FS. */
-static svn_error_t *
-hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- apr_pool_t *pool)
-{
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
- fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
-
- /* We only support incremental hotcopy between the same format. */
- if (src_ffd->format != dst_ffd->format)
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("The FSFS format (%d) of the hotcopy source does not match the "
- "FSFS format (%d) of the hotcopy destination; please upgrade "
- "both repositories to the same format"),
- src_ffd->format, dst_ffd->format);
-
- /* Make sure the UUID of source and destination match up.
- * We don't want to copy over a different repository. */
- if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
- return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
- _("The UUID of the hotcopy source does "
- "not match the UUID of the hotcopy "
- "destination"));
-
- /* Also require same shard size. */
- if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
- return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("The sharding layout configuration "
- "of the hotcopy source does not match "
- "the sharding layout configuration of "
- "the hotcopy destination"));
- return SVN_NO_ERROR;
-}
-
-/* Remove folder PATH. Ignore errors due to the sub-tree not being empty.
- * CANCEL_FUNC and CANCEL_BATON do the usual thing.
- * Use POOL for temporary allocations.
- */
-static svn_error_t *
-remove_folder(const char *path,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_io_remove_dir2(path, TRUE,
- cancel_func, cancel_baton, pool);
-
- if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
- {
- svn_error_clear(err);
- err = SVN_NO_ERROR;
- }
-
- return svn_error_trace(err);
-}
-
-/* Baton for hotcopy_body(). */
-struct hotcopy_body_baton {
- svn_fs_t *src_fs;
- svn_fs_t *dst_fs;
- svn_boolean_t incremental;
- svn_cancel_func_t cancel_func;
- void *cancel_baton;
-} hotcopy_body_baton;
-
-/* Perform a hotcopy, either normal or incremental.
- *
- * Normal hotcopy assumes that the destination exists as an empty
- * directory. It behaves like an incremental hotcopy except that
- * none of the copied files already exist in the destination.
- *
- * An incremental hotcopy copies only changed or new files to the destination,
- * and removes files from the destination no longer present in the source.
- * While the incremental hotcopy is running, readers should still be able
- * to access the destintation repository without error and should not see
- * revisions currently in progress of being copied. Readers are able to see
- * new fully copied revisions even if the entire incremental hotcopy procedure
- * has not yet completed.
- *
- * Writers are blocked out completely during the entire incremental hotcopy
- * process to ensure consistency. This function assumes that the repository
- * write-lock is held.
- */
-static svn_error_t *
-hotcopy_body(void *baton, apr_pool_t *pool)
-{
- struct hotcopy_body_baton *hbb = baton;
- svn_fs_t *src_fs = hbb->src_fs;
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
- svn_fs_t *dst_fs = hbb->dst_fs;
- fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
- int max_files_per_dir = src_ffd->max_files_per_dir;
- svn_boolean_t incremental = hbb->incremental;
- svn_cancel_func_t cancel_func = hbb->cancel_func;
- void* cancel_baton = hbb->cancel_baton;
- svn_revnum_t src_youngest;
- svn_revnum_t dst_youngest;
- svn_revnum_t rev;
- svn_revnum_t src_min_unpacked_rev;
- svn_revnum_t dst_min_unpacked_rev;
- const char *src_subdir;
- const char *dst_subdir;
- const char *revprop_src_subdir;
- const char *revprop_dst_subdir;
- apr_pool_t *iterpool;
- svn_node_kind_t kind;
-
- /* Try to copy the config.
- *
- * ### We try copying the config file before doing anything else,
- * ### because higher layers will abort the hotcopy if we throw
- * ### an error from this function, and that renders the hotcopy
- * ### unusable anyway. */
- if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
- {
- svn_error_t *err;
-
- err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
- pool);
- if (err)
- {
- if (APR_STATUS_IS_ENOENT(err->apr_err))
- {
- /* 1.6.0 to 1.6.11 did not copy the configuration file during
- * hotcopy. So if we're hotcopying a repository which has been
- * created as a hotcopy itself, it's possible that fsfs.conf
- * does not exist. Ask the user to re-create it.
- *
- * ### It would be nice to make this a non-fatal error,
- * ### but this function does not get an svn_fs_t object
- * ### so we have no way of just printing a warning via
- * ### the fs->warning() callback. */
-
- const char *msg;
- const char *src_abspath;
- const char *dst_abspath;
- const char *config_relpath;
- svn_error_t *err2;
-
- config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
- err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
- if (err2)
- return svn_error_trace(svn_error_compose_create(err, err2));
- err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
- if (err2)
- return svn_error_trace(svn_error_compose_create(err, err2));
-
- /* ### hack: strip off the 'db/' directory from paths so
- * ### they make sense to the user */
- src_abspath = svn_dirent_dirname(src_abspath, pool);
- dst_abspath = svn_dirent_dirname(dst_abspath, pool);
-
- msg = apr_psprintf(pool,
- _("Failed to create hotcopy at '%s'. "
- "The file '%s' is missing from the source "
- "repository. Please create this file, for "
- "instance by running 'svnadmin upgrade %s'"),
- dst_abspath, config_relpath, src_abspath);
- return svn_error_quick_wrap(err, msg);
- }
- else
- return svn_error_trace(err);
- }
- }
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Find the youngest revision in the source and destination.
- * We only support hotcopies from sources with an equal or greater amount
- * of revisions than the destination.
- * This also catches the case where users accidentally swap the
- * source and destination arguments. */
- SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
- if (incremental)
- {
- SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
- if (src_youngest < dst_youngest)
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("The hotcopy destination already contains more revisions "
- "(%lu) than the hotcopy source contains (%lu); are source "
- "and destination swapped?"),
- dst_youngest, src_youngest);
- }
- else
- dst_youngest = 0;
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Copy the min unpacked rev, and read its value. */
- if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- {
- const char *min_unpacked_rev_path;
-
- min_unpacked_rev_path = svn_dirent_join(src_fs->path,
- PATH_MIN_UNPACKED_REV,
- pool);
- SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
- min_unpacked_rev_path,
- pool));
-
- min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
- PATH_MIN_UNPACKED_REV,
- pool);
- SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
- min_unpacked_rev_path,
- pool));
-
- /* We only support packs coming from the hotcopy source.
- * The destination should not be packed independently from
- * the source. This also catches the case where users accidentally
- * swap the source and destination arguments. */
- if (src_min_unpacked_rev < dst_min_unpacked_rev)
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("The hotcopy destination already contains "
- "more packed revisions (%lu) than the "
- "hotcopy source contains (%lu)"),
- dst_min_unpacked_rev - 1,
- src_min_unpacked_rev - 1);
-
- SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
- PATH_MIN_UNPACKED_REV, pool));
- }
- else
- {
- src_min_unpacked_rev = 0;
- dst_min_unpacked_rev = 0;
- }
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /*
- * Copy the necessary rev files.
- */
-
- src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
- dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
- SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
-
- iterpool = svn_pool_create(pool);
- /* First, copy packed shards. */
- for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
- {
- svn_pool_clear(iterpool);
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Copy the packed shard. */
- SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
- src_fs, dst_fs,
- rev, max_files_per_dir,
- iterpool));
-
- /* If necessary, update 'current' to the most recent packed rev,
- * so readers can see new revisions which arrived in this pack. */
- SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
- rev + max_files_per_dir - 1,
- iterpool));
-
- /* Remove revision files which are now packed. */
- if (incremental)
- {
- SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
- rev + max_files_per_dir,
- max_files_per_dir, iterpool));
- if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
- rev + max_files_per_dir,
- max_files_per_dir,
- iterpool));
- }
-
- /* Now that all revisions have moved into the pack, the original
- * rev dir can be removed. */
- SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
- cancel_func, cancel_baton, iterpool));
- if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
- cancel_func, cancel_baton, iterpool));
- }
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Now, copy pairs of non-packed revisions and revprop files.
- * If necessary, update 'current' after copying all files from a shard. */
- SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
- SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
- revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
- revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
- SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
- for (; rev <= src_youngest; rev++)
- {
- svn_error_t *err;
-
- svn_pool_clear(iterpool);
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Copy the rev file. */
- err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
- rev, max_files_per_dir,
- iterpool);
- if (err)
- {
- if (APR_STATUS_IS_ENOENT(err->apr_err) &&
- src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- {
- svn_error_clear(err);
-
- /* The source rev file does not exist. This can happen if the
- * source repository is being packed concurrently with this
- * hotcopy operation.
- *
- * If the new revision is now packed, and the youngest revision
- * we're interested in is not inside this pack, try to copy the
- * pack instead.
- *
- * If the youngest revision ended up being packed, don't try
- * to be smart and work around this. Just abort the hotcopy. */
- SVN_ERR(update_min_unpacked_rev(src_fs, pool));
- if (is_packed_rev(src_fs, rev))
- {
- if (is_packed_rev(src_fs, src_youngest))
- return svn_error_createf(
- SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("The assumed HEAD revision (%lu) of the "
- "hotcopy source has been packed while the "
- "hotcopy was in progress; please restart "
- "the hotcopy operation"),
- src_youngest);
-
- SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
- src_fs, dst_fs,
- rev, max_files_per_dir,
- iterpool));
- rev = dst_min_unpacked_rev;
- continue;
- }
- else
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("Revision %lu disappeared from the "
- "hotcopy source while hotcopy was "
- "in progress"), rev);
- }
- else
- return svn_error_trace(err);
- }
-
- /* Copy the revprop file. */
- SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
- revprop_dst_subdir,
- rev, max_files_per_dir,
- iterpool));
-
- /* After completing a full shard, update 'current'. */
- if (max_files_per_dir && rev % max_files_per_dir == 0)
- SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
- }
- svn_pool_destroy(iterpool);
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* We assume that all revisions were copied now, i.e. we didn't exit the
- * above loop early. 'rev' was last incremented during exit of the loop. */
- SVN_ERR_ASSERT(rev == src_youngest + 1);
-
- /* All revisions were copied. Update 'current'. */
- SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
-
- /* Replace the locks tree.
- * This is racy in case readers are currently trying to list locks in
- * the destination. However, we need to get rid of stale locks.
- * This is the simplest way of doing this, so we accept this small race. */
- dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
- SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
- pool));
- src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
- SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
- if (kind == svn_node_dir)
- SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
- PATH_LOCKS_DIR, TRUE,
- cancel_func, cancel_baton, pool));
-
- /* Now copy the node-origins cache tree. */
- src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
- SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
- if (kind == svn_node_dir)
- SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
- PATH_NODE_ORIGINS_DIR, TRUE,
- cancel_func, cancel_baton, pool));
-
- /*
- * NB: Data copied below is only read by writers, not readers.
- * Writers are still locked out at this point.
- */
-
- if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- {
- /* Copy the rep cache and then remove entries for revisions
- * younger than the destination's youngest revision. */
- src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
- dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
- SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
- if (kind == svn_node_file)
- {
- SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
- SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
- }
- }
-
- /* Copy the txn-current file. */
- if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
- SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
- PATH_TXN_CURRENT, pool));
-
- /* If a revprop generation file exists in the source filesystem,
- * reset it to zero (since this is on a different path, it will not
- * overlap with data already in cache). Also, clean up stale files
- * used for the named atomics implementation. */
- SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
- &kind, pool));
- if (kind == svn_node_file)
- SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
-
- SVN_ERR(cleanup_revprop_namespace(dst_fs));
-
- /* Hotcopied FS is complete. Stamp it with a format file. */
- SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
- dst_ffd->format, max_files_per_dir, TRUE, pool));
-
- return SVN_NO_ERROR;
-}
-
-
-/* Set up shared data between SRC_FS and DST_FS. */
-static void
-hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
-{
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
- fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
-
- /* The common pool and mutexes are shared between src and dst filesystems.
- * During hotcopy we only grab the mutexes for the destination, so there
- * is no risk of dead-lock. We don't write to the src filesystem. Shared
- * data for the src_fs has already been initialised in fs_hotcopy(). */
- dst_ffd->shared = src_ffd->shared;
-}
-
-/* Create an empty filesystem at DST_FS at DST_PATH with the same
- * configuration as SRC_FS (uuid, format, and other parameters).
- * After creation DST_FS has no revisions, not even revision zero. */
-static svn_error_t *
-hotcopy_create_empty_dest(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *dst_path,
- apr_pool_t *pool)
-{
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
- fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
-
- dst_fs->path = apr_pstrdup(pool, dst_path);
-
- dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
- dst_ffd->config = src_ffd->config;
- dst_ffd->format = src_ffd->format;
-
- /* Create the revision data directories. */
- if (dst_ffd->max_files_per_dir)
- SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
- pool));
- else
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
- PATH_REVS_DIR, pool),
- pool));
-
- /* Create the revprops directory. */
- if (src_ffd->max_files_per_dir)
- SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
- pool));
- else
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
- PATH_REVPROPS_DIR,
- pool),
- pool));
-
- /* Create the transaction directory. */
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
- pool),
- pool));
-
- /* Create the protorevs directory. */
- if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
- PATH_TXN_PROTOS_DIR,
- pool),
- pool));
-
- /* Create the 'current' file. */
- SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
- (dst_ffd->format >=
- SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
- ? "0\n" : "0 1 1\n"),
- pool));
+ fs_fs_data_t *ffd = fs->fsap_data;
+ *fs_format = ffd->format;
+ *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
- /* Create lock file and UUID. */
- SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
- SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
+ (*supports_version)->major = SVN_VER_MAJOR;
+ (*supports_version)->minor = 1;
+ (*supports_version)->patch = 0;
+ (*supports_version)->tag = "";
- /* Create the min unpacked rev file. */
- if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
- "0\n", pool));
- /* Create the txn-current file if the repository supports
- the transaction sequence file. */
- if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ switch (ffd->format)
{
- SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
- "0\n", pool));
- SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
- "", pool));
+ case 1:
+ break;
+ case 2:
+ (*supports_version)->minor = 4;
+ break;
+ case 3:
+ (*supports_version)->minor = 5;
+ break;
+ case 4:
+ (*supports_version)->minor = 6;
+ break;
+ case 6:
+ (*supports_version)->minor = 8;
+ break;
+ case 7:
+ (*supports_version)->minor = 9;
+ break;
+#ifdef SVN_DEBUG
+# if SVN_FS_FS__FORMAT_NUMBER != 7
+# error "Need to add a 'case' statement here"
+# endif
+#endif
}
- dst_ffd->youngest_rev_cache = 0;
-
- hotcopy_setup_shared_fs_data(src_fs, dst_fs);
- SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
-
return SVN_NO_ERROR;
}
svn_error_t *
-svn_fs_fs__hotcopy(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *src_path,
- const char *dst_path,
- svn_boolean_t incremental,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
+svn_fs_fs__info_config_files(apr_array_header_t **files,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- struct hotcopy_body_baton hbb;
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
-
- if (incremental)
- {
- const char *dst_format_abspath;
- svn_node_kind_t dst_format_kind;
-
- /* Check destination format to be sure we know how to incrementally
- * hotcopy to the destination FS. */
- dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
- SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
- if (dst_format_kind == svn_node_none)
- {
- /* Destination doesn't exist yet. Perform a normal hotcopy to a
- * empty destination using the same configuration as the source. */
- SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
- }
- else
- {
- /* Check the existing repository. */
- SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
- SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
- pool));
- hotcopy_setup_shared_fs_data(src_fs, dst_fs);
- SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
- }
- }
- else
- {
- /* Start out with an empty destination using the same configuration
- * as the source. */
- SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
- }
-
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- hbb.src_fs = src_fs;
- hbb.dst_fs = dst_fs;
- hbb.incremental = incremental;
- hbb.cancel_func = cancel_func;
- hbb.cancel_baton = cancel_baton;
- SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
-
+ *files = apr_array_make(result_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
+ result_pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h
index c09f861..b6c94c7 100644
--- a/subversion/libsvn_fs_fs/fs_fs.h
+++ b/subversion/libsvn_fs_fs/fs_fs.h
@@ -25,6 +25,11 @@
#include "fs.h"
+/* Read the 'format' file of fsfs filesystem FS and store its info in FS.
+ * Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool);
+
/* Open the fsfs filesystem pointed to by PATH and associate it with
filesystem object FS. Use POOL for temporary allocations.
@@ -34,166 +39,41 @@ svn_error_t *svn_fs_fs__open(svn_fs_t *fs,
const char *path,
apr_pool_t *pool);
-/* Upgrade the fsfs filesystem FS. Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__upgrade(svn_fs_t *fs,
- apr_pool_t *pool);
-
-/* Verify metadata in fsfs filesystem FS. Limit the checks to revisions
- * START to END where possible. Indicate progress via the optional
+/* Upgrade the fsfs filesystem FS. Indicate progress via the optional
* NOTIFY_FUNC callback using NOTIFY_BATON. The optional CANCEL_FUNC
* will periodically be called with CANCEL_BATON to allow for preemption.
* Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__verify(svn_fs_t *fs,
- svn_revnum_t start,
- svn_revnum_t end,
- svn_fs_progress_notify_func_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool);
-
-/* Copy the fsfs filesystem SRC_FS at SRC_PATH into a new copy DST_FS at
- * DST_PATH. If INCREMENTAL is TRUE, do not re-copy data which already
- * exists in DST_FS. Use POOL for temporary allocations. */
-svn_error_t * svn_fs_fs__hotcopy(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *src_path,
- const char *dst_path,
- svn_boolean_t incremental,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool);
-
-/* Recover the fsfs associated with filesystem FS.
- Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
- Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__recover(svn_fs_t *fs,
+svn_error_t *svn_fs_fs__upgrade(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool);
-/* Set *NODEREV_P to the node-revision for the node ID in FS. Do any
- allocations in POOL. */
-svn_error_t *svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
- svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *pool);
-
-/* Store NODEREV as the node-revision for the node whose id is ID in
- FS, after setting its is_fresh_txn_root to FRESH_TXN_ROOT. Do any
- necessary temporary allocation in POOL. */
-svn_error_t *svn_fs_fs__put_node_revision(svn_fs_t *fs,
- const svn_fs_id_t *id,
- node_revision_t *noderev,
- svn_boolean_t fresh_txn_root,
- 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. */
-/* ### Currently used only by fs_fs.c */
-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);
-
-/* Read a node-revision from STREAM. Set *NODEREV to the new structure,
- allocated in POOL. */
-/* ### Currently used only by fs_fs.c */
-svn_error_t *
-svn_fs_fs__read_noderev(node_revision_t **noderev,
- svn_stream_t *stream,
- apr_pool_t *pool);
-
-
/* Set *YOUNGEST to the youngest revision in filesystem FS. Do any
temporary allocation in POOL. */
svn_error_t *svn_fs_fs__youngest_rev(svn_revnum_t *youngest,
svn_fs_t *fs,
apr_pool_t *pool);
-/* Return an error iff REV does not exist in FS. */
-svn_error_t *
-svn_fs_fs__revision_exists(svn_revnum_t rev,
- svn_fs_t *fs,
- apr_pool_t *pool);
-
-/* Set *ROOT_ID to the node-id for the root of revision REV in
- filesystem FS. Do any allocations in POOL. */
-svn_error_t *svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool);
+/* Return the shard size of filesystem FS. Return 0 for non-shared ones. */
+int
+svn_fs_fs__shard_size(svn_fs_t *fs);
-/* Set *ENTRIES to an apr_hash_t of dirent structs that contain the
- directory entries of node-revision NODEREV in filesystem FS. The
- returned table (and its keys and values) is allocated in POOL,
- which is also used for temporary allocations. */
-svn_error_t *svn_fs_fs__rep_contents_dir(apr_hash_t **entries,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool);
-
-/* Set *DIRENT to the entry identified by NAME in the directory given
- by NODEREV in filesystem FS. If no such entry exits, *DIRENT will
- be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL
- used for temporary allocations. */
+/* Set *MIN_UNPACKED to the oldest non-packed revision in filesystem FS.
+ Do any temporary allocation in POOL. */
svn_error_t *
-svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
- svn_fs_t *fs,
- node_revision_t *noderev,
- const char *name,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
-/* Set *CONTENTS to be a readable svn_stream_t that receives the text
- representation of node-revision NODEREV as seen in filesystem FS.
- Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__get_contents(svn_stream_t **contents,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool);
+svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
-/* Attempt to fetch the text representation of node-revision NODEREV as
- seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
- Set *SUCCESS only of the data could be provided and the processing
- had been called.
- Use POOL for all allocations.
- */
+/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision REV is newer
+ than the current youngest revision in FS or is simply not a valid
+ revision number, else return success. */
svn_error_t *
-svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
- svn_fs_t *fs,
- node_revision_t *noderev,
- svn_fs_process_contents_func_t processor,
- void* baton,
- apr_pool_t *pool);
-
-/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
- the contents of the file TARGET, allocated in POOL.
- If SOURCE is null, the empty string will be used. */
-svn_error_t *svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
- svn_fs_t *fs,
- node_revision_t *source,
- node_revision_t *target,
- apr_pool_t *pool);
-
-/* Set *PROPLIST to be an apr_hash_t containing the property list of
- node-revision NODEREV as seen in filesystem FS. Use POOL for
- temporary allocations. */
-svn_error_t *svn_fs_fs__get_proplist(apr_hash_t **proplist,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool);
-
-/* Set *PROPLIST to be an apr_hash_t containing the property list of
- revision REV as seen in filesystem FS. Use POOL for temporary
- allocations. */
-svn_error_t *svn_fs_fs__revision_proplist(apr_hash_t **proplist,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool);
+svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
/* Set *LENGTH to the be fulltext length of the node revision
specified by NODEREV. Use POOL for temporary allocations. */
@@ -206,6 +86,26 @@ svn_error_t *svn_fs_fs__file_length(svn_filesize_t *length,
svn_boolean_t svn_fs_fs__noderev_same_rep_key(representation_t *a,
representation_t *b);
+/* Set *EQUAL to TRUE if the text representations in A and B within FS
+ have equal contents, else set it to FALSE.
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
+ svn_fs_t *fs,
+ node_revision_t *a,
+ node_revision_t *b,
+ apr_pool_t *scratch_pool);
+
+/* Set *EQUAL to TRUE if the property representations in A and B within FS
+ have equal contents, else set it to FALSE.
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
+ svn_fs_t *fs,
+ node_revision_t *a,
+ node_revision_t *b,
+ apr_pool_t *scratch_pool);
+
/* Return a copy of the representation REP allocated from POOL. */
representation_t *svn_fs_fs__rep_copy(representation_t *rep,
@@ -220,152 +120,27 @@ svn_error_t *svn_fs_fs__file_checksum(svn_checksum_t **checksum,
svn_checksum_kind_t kind,
apr_pool_t *pool);
-/* Find the paths which were changed in revision REV of filesystem FS
- and store them in *CHANGED_PATHS_P. Cached copyfrom information
- will be stored in *COPYFROM_CACHE. Get any temporary allocations
- from POOL. */
-svn_error_t *svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *copyfrom_cache,
- apr_pool_t *pool);
-
-/* Create a new transaction in filesystem FS, based on revision REV,
- and store it in *TXN_P. Allocate all necessary variables from
- POOL. */
-svn_error_t *svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool);
-
-/* Set the transaction property NAME to the value VALUE in transaction
- TXN. Perform temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
- const char *name,
- const svn_string_t *value,
- apr_pool_t *pool);
-
-/* Change transaction properties in transaction TXN based on PROPS.
- Perform temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
- const apr_array_header_t *props,
- apr_pool_t *pool);
-
/* Return whether or not the given FS supports mergeinfo metadata. */
svn_boolean_t svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs);
-/* Store a transaction record in *TXN_P for the transaction identified
- by TXN_ID in filesystem FS. Allocate everything from POOL. */
-svn_error_t *svn_fs_fs__get_txn(transaction_t **txn_p,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool);
-
-/* Abort the existing transaction TXN, performing any temporary
- allocations in POOL. */
-svn_error_t *svn_fs_fs__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool);
-
-/* Create an entirely new mutable node in the filesystem FS, whose
- node-revision is NODEREV. Set *ID_P to the new node revision's ID.
- Use POOL for any temporary allocation. COPY_ID is the copy_id to
- use in the node revision ID. TXN_ID is the Subversion transaction
- under which this occurs. */
-svn_error_t *svn_fs_fs__create_node(const svn_fs_id_t **id_p,
- svn_fs_t *fs,
- node_revision_t *noderev,
- const char *copy_id,
- const char *txn_id,
- apr_pool_t *pool);
-
-/* Remove all references to the transaction TXN_ID from filesystem FS.
- Temporary allocations are from POOL. */
-svn_error_t *svn_fs_fs__purge_txn(svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool);
-
-/* Add or set in filesystem FS, transaction TXN_ID, in directory
- PARENT_NODEREV a directory entry for NAME pointing to ID of type
- KIND. Allocations are done in POOL. */
-svn_error_t *svn_fs_fs__set_entry(svn_fs_t *fs,
- const char *txn_id,
- node_revision_t *parent_noderev,
- const char *name,
- const svn_fs_id_t *id,
- svn_node_kind_t kind,
- apr_pool_t *pool);
-
-/* Add a change to the changes record for filesystem FS in transaction
- TXN_ID. Mark path PATH, having node-id ID, as changed according to
- the type in CHANGE_KIND. If the text representation was changed
- set TEXT_MOD to TRUE, and likewise for PROP_MOD. If this change
- was the result of a copy, set COPYFROM_REV and COPYFROM_PATH to the
- revision and path of the copy source, otherwise they should be set
- to SVN_INVALID_REVNUM and NULL. Perform any temporary allocations
- from POOL. */
-svn_error_t *svn_fs_fs__add_change(svn_fs_t *fs,
- const char *txn_id,
- const char *path,
- const svn_fs_id_t *id,
- svn_fs_path_change_kind_t change_kind,
- svn_boolean_t text_mod,
- svn_boolean_t prop_mod,
- svn_node_kind_t node_kind,
- svn_revnum_t copyfrom_rev,
- const char *copyfrom_path,
- apr_pool_t *pool);
-
-/* Return a writable stream in *STREAM that allows storing the text
- representation of node-revision NODEREV in filesystem FS.
- Allocations are from POOL. */
-svn_error_t *svn_fs_fs__set_contents(svn_stream_t **stream,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *pool);
-
-/* Create a node revision in FS which is an immediate successor of
- OLD_ID, whose contents are NEW_NR. Set *NEW_ID_P to the new node
- revision's ID. Use POOL for any temporary allocation.
-
- COPY_ID, if non-NULL, is a key into the `copies' table, and
- indicates that this new node is being created as the result of a
- copy operation, and specifically which operation that was. If
- COPY_ID is NULL, then re-use the copy ID from the predecessor node.
-
- TXN_ID is the Subversion transaction under which this occurs.
-
- After this call, the deltification code assumes that the new node's
- contents will change frequently, and will avoid representing other
- nodes as deltas against this node's contents. */
-svn_error_t *svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
- svn_fs_t *fs,
- const svn_fs_id_t *old_idp,
- node_revision_t *new_noderev,
- const char *copy_id,
- const char *txn_id,
- apr_pool_t *pool);
-
-/* Write a new property list PROPLIST for node-revision NODEREV in
- filesystem FS. Perform any temporary allocations in POOL. */
-svn_error_t *svn_fs_fs__set_proplist(svn_fs_t *fs,
- node_revision_t *noderev,
- apr_hash_t *proplist,
- apr_pool_t *pool);
-
-/* Commit the transaction TXN in filesystem FS and return its new
- revision number in *REV. If the transaction is out of date, return
- the error SVN_ERR_FS_TXN_OUT_OF_DATE. Use POOL for temporary
- allocations. */
-svn_error_t *svn_fs_fs__commit(svn_revnum_t *new_rev_p,
- svn_fs_t *fs,
- svn_fs_txn_t *txn,
- apr_pool_t *pool);
-
-/* Return the next available copy_id in *COPY_ID for the transaction
- TXN_ID in filesystem FS. Allocate space in POOL. */
-svn_error_t *svn_fs_fs__reserve_copy_id(const char **copy_id,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool);
+/* Under the repository db PATH, create a FSFS repository with FORMAT,
+ * the given SHARD_SIZE. If USE_LOG_ADDRESSING is non-zero, repository
+ * will use logical addressing. If not supported by the respective format,
+ * the latter two parameters will be ignored. FS will be updated.
+ *
+ * The only file not being written is the 'format' file. This allows
+ * callers such as hotcopy to modify the contents before turning the
+ * tree into an accessible repository.
+ *
+ * Use POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__create_file_tree(svn_fs_t *fs,
+ const char *path,
+ int format,
+ int shard_size,
+ svn_boolean_t use_log_addressing,
+ apr_pool_t *pool);
/* Create a fs_fs fileysystem referenced by FS at path PATH. Get any
temporary allocations from POOL.
@@ -376,70 +151,29 @@ svn_error_t *svn_fs_fs__create(svn_fs_t *fs,
const char *path,
apr_pool_t *pool);
-/* Set the uuid of repository FS to UUID, if UUID is not NULL;
- otherwise, set the uuid of FS to a newly generated UUID. Perform
- temporary allocations in POOL. */
+/* Set the uuid of repository FS to UUID and the instance ID to INSTANCE_ID.
+ If any of them is NULL, use a newly generated UUID / ID instead. Ignore
+ INSTANCE_ID whenever instance IDs are not supported by the FS format.
+ Perform temporary allocations in POOL. */
svn_error_t *svn_fs_fs__set_uuid(svn_fs_t *fs,
const char *uuid,
+ const char *instance_id,
apr_pool_t *pool);
-/* Set *NAMES_P to an array of names which are all the active
- transactions in filesystem FS. Allocate the array from POOL. */
-svn_error_t *svn_fs_fs__list_transactions(apr_array_header_t **names_p,
- svn_fs_t *fs,
- apr_pool_t *pool);
-
-/* Open the transaction named NAME in filesystem FS. Set *TXN_P to
- * the transaction. If there is no such transaction, return
-` * SVN_ERR_FS_NO_SUCH_TRANSACTION. Allocate the new transaction in
- * POOL. */
-svn_error_t *svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
- svn_fs_t *fs,
- const char *name,
- apr_pool_t *pool);
-
-/* Return the property list from transaction TXN and store it in
- *PROPLIST. Allocate the property list from POOL. */
-svn_error_t *svn_fs_fs__txn_proplist(apr_hash_t **proplist,
- svn_fs_txn_t *txn,
- apr_pool_t *pool);
-
-/* Delete the mutable node-revision referenced by ID, along with any
- mutable props or directory contents associated with it. Perform
- temporary allocations in POOL. */
-svn_error_t *svn_fs_fs__delete_node_revision(svn_fs_t *fs,
- const svn_fs_id_t *id,
- apr_pool_t *pool);
-
-
-/* Find the paths which were changed in transaction TXN_ID of
- filesystem FS and store them in *CHANGED_PATHS_P.
- Get any temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__txn_changes_fetch(apr_hash_t **changes,
- svn_fs_t *fs,
- const char *txn_id,
- apr_pool_t *pool);
-
-
-/* Set *PATH to the path of REV in FS, whether in a pack file or not.
- Allocate *PATH in POOL.
-
- Note: If the caller does not have the write lock on FS, then the path is
- not guaranteed to be correct or to remain correct after the function
- returns, because the revision might become packed before or after this
- call. If a file exists at that path, then it is correct; if not, then
- the caller should call update_min_unpacked_rev() and re-try once. */
-svn_error_t *
-svn_fs_fs__path_rev_absolute(const char **path,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool);
-
/* Return the path to the 'current' file in FS.
Perform allocation in POOL. */
const char *
svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool);
+/* Write the format number and maximum number of files per directory
+ for FS, possibly expecting to overwrite a previously existing file.
+
+ Use POOL for temporary allocation. */
+svn_error_t *
+svn_fs_fs__write_format(svn_fs_t *fs,
+ svn_boolean_t overwrite,
+ apr_pool_t *pool);
+
/* Obtain a write lock on the filesystem FS in a subpool of POOL, call
BODY with BATON and that subpool, destroy the subpool (releasing the write
lock) and return what BODY returned. */
@@ -450,6 +184,38 @@ svn_fs_fs__with_write_lock(svn_fs_t *fs,
void *baton,
apr_pool_t *pool);
+/* Obtain a pack operation lock on the filesystem FS in a subpool of POOL,
+ call BODY with BATON and that subpool, destroy the subpool (releasing the
+ write lock) and return what BODY returned. */
+svn_error_t *
+svn_fs_fs__with_pack_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
+/* Run BODY (with BATON and POOL) while the txn-current file
+ of FS is locked. */
+svn_error_t *
+svn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
+/* Obtain all locks on the filesystem FS in a subpool of POOL, call BODY
+ with BATON and that subpool, destroy the subpool (releasing the locks)
+ and return what BODY returned.
+
+ This combines svn_fs_fs__with_write_lock, svn_fs_fs__with_pack_lock,
+ and svn_fs_fs__with_txn_current_lock, ensuring correct lock ordering. */
+svn_error_t *
+svn_fs_fs__with_all_locks(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
/* Find the value of the property named PROPNAME in transaction TXN.
Return the contents in *VALUE_P. The contents will be allocated
from POOL. */
@@ -469,38 +235,6 @@ svn_error_t *svn_fs_fs__change_rev_prop(svn_fs_t *fs, svn_revnum_t rev,
const svn_string_t *value,
apr_pool_t *pool);
-/* Retrieve information about the Subversion transaction SVN_TXN from
- the `transactions' table of FS, allocating from POOL. Set
- *ROOT_ID_P to the ID of the transaction's root directory. Set
- *BASE_ROOT_ID_P to the ID of the root directory of the
- transaction's base revision.
-
- If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
- the error returned.
-
- Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
- transaction that has already been committed.
-
- Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */
-svn_error_t *svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
- const svn_fs_id_t **base_root_id_p,
- svn_fs_t *fs,
- const char *txn_name,
- apr_pool_t *pool);
-
-/* Begin a new transaction in filesystem FS, based on existing
- revision REV. The new transaction is returned in *TXN_P. Allocate
- the new transaction structure from POOL. */
-svn_error_t *svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs,
- svn_revnum_t rev, apr_uint32_t flags,
- apr_pool_t *pool);
-
-/* Find the value of the property named PROPNAME in transaction TXN.
- Return the contents in *VALUE_P. The contents will be allocated
- from POOL. */
-svn_error_t *svn_fs_fs__txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
- const char *propname, apr_pool_t *pool);
-
/* If directory PATH does not exist, create it and give it the same
permissions as FS_PATH.*/
svn_error_t *svn_fs_fs__ensure_dir_exists(const char *path,
@@ -516,7 +250,7 @@ svn_error_t *svn_fs_fs__ensure_dir_exists(const char *path,
*/
svn_error_t *
svn_fs_fs__set_node_origin(svn_fs_t *fs,
- const char *node_id,
+ const svn_fs_fs__id_part_t *node_id,
const svn_fs_id_t *node_rev_id,
apr_pool_t *pool);
@@ -530,12 +264,12 @@ svn_fs_fs__set_node_origin(svn_fs_t *fs,
svn_error_t *
svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
svn_fs_t *fs,
- const char *node_id,
+ const svn_fs_fs__id_part_t *node_id,
apr_pool_t *pool);
/* Initialize all session-local caches in FS according to the global
- cache settings. Use POOL for allocations.
+ cache settings. Use POOL for temporary allocations.
Please note that it is permissible for this function to set some
or all of these caches to NULL, regardless of any setting. */
@@ -558,18 +292,4 @@ svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
void
svn_fs_fs__reset_txn_caches(svn_fs_t *fs);
-/* Possibly pack the repository at PATH. This just take full shards, and
- combines all the revision files into a single one, with a manifest header.
- Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
-
- Existing filesystem references need not change. */
-svn_error_t *
-svn_fs_fs__pack(svn_fs_t *fs,
- svn_fs_pack_notify_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool);
-
-
#endif
diff --git a/subversion/libsvn_fs_fs/hotcopy.c b/subversion/libsvn_fs_fs/hotcopy.c
new file mode 100644
index 0000000..43f513e
--- /dev/null
+++ b/subversion/libsvn_fs_fs/hotcopy.c
@@ -0,0 +1,1097 @@
+/* hotcopy.c --- FS hotcopy functionality for FSFS
+ *
+ * ====================================================================
+ * 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_pools.h"
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+
+#include "fs_fs.h"
+#include "hotcopy.h"
+#include "util.h"
+#include "recovery.h"
+#include "revprops.h"
+#include "rep-cache.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
+ * the destination and do not differ in terms of kind, size, and mtime.
+ * Set *SKIPPED_P to FALSE only if the file was copied, do not change
+ * the value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not
+ * required. */
+static svn_error_t *
+hotcopy_io_dir_file_copy(svn_boolean_t *skipped_p,
+ const char *src_path,
+ const char *dst_path,
+ const char *file,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *src_dirent;
+ const svn_io_dirent2_t *dst_dirent;
+ const char *src_target;
+ const char *dst_target;
+
+ /* Does the destination already exist? If not, we must copy it. */
+ dst_target = svn_dirent_join(dst_path, file, scratch_pool);
+ SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ if (dst_dirent->kind != svn_node_none)
+ {
+ /* If the destination's stat information indicates that the file
+ * is equal to the source, don't bother copying the file again. */
+ src_target = svn_dirent_join(src_path, file, scratch_pool);
+ SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+ if (src_dirent->kind == dst_dirent->kind &&
+ src_dirent->special == dst_dirent->special &&
+ src_dirent->filesize == dst_dirent->filesize &&
+ src_dirent->mtime <= dst_dirent->mtime)
+ return SVN_NO_ERROR;
+ }
+
+ if (skipped_p)
+ *skipped_p = FALSE;
+
+ return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
+ scratch_pool));
+}
+
+/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
+ * NAME is in the internal encoding used by APR; PARENT is in
+ * UTF-8 and in internal (not local) style.
+ *
+ * Use PARENT only for generating an error string if the conversion
+ * fails because NAME could not be represented in UTF-8. In that
+ * case, return a two-level error in which the outer error's message
+ * mentions PARENT, but the inner error's message does not mention
+ * NAME (except possibly in hex) since NAME may not be printable.
+ * Such a compound error at least allows the user to go looking in the
+ * right directory for the problem.
+ *
+ * If there is any other error, just return that error directly.
+ *
+ * If there is any error, the effect on *NAME_P is undefined.
+ *
+ * *NAME_P and NAME may refer to the same storage.
+ */
+static svn_error_t *
+entry_name_to_utf8(const char **name_p,
+ const char *name,
+ const char *parent,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
+ if (err && err->apr_err == APR_EINVAL)
+ {
+ return svn_error_createf(err->apr_err, err,
+ _("Error converting entry "
+ "in directory '%s' to UTF-8"),
+ svn_dirent_local_style(parent, pool));
+ }
+ return err;
+}
+
+/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
+ * exist in the destination and do not differ from the source in terms of
+ * kind, size, and mtime. Set *SKIPPED_P to FALSE only if at least one
+ * file was copied, do not change the value in *SKIPPED_P otherwise.
+ * SKIPPED_P may be NULL if not required. */
+static svn_error_t *
+hotcopy_io_copy_dir_recursively(svn_boolean_t *skipped_p,
+ const char *src,
+ const char *dst_parent,
+ const char *dst_basename,
+ svn_boolean_t copy_perms,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_status_t status;
+ const char *dst_path;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Make a subpool for recursion */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* The 'dst_path' is simply dst_parent/dst_basename */
+ dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
+
+ /* Sanity checks: SRC and DST_PARENT are directories, and
+ DST_BASENAME doesn't already exist in DST_PARENT. */
+ SVN_ERR(svn_io_check_path(src, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Source '%s' is not a directory"),
+ svn_dirent_local_style(src, pool));
+
+ SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Destination '%s' is not a directory"),
+ svn_dirent_local_style(dst_parent, pool));
+
+ SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
+
+ /* Create the new directory. */
+ /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
+ SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
+
+ /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
+ SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *entryname_utf8;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
+ src, subpool));
+ if (this_entry.filetype == APR_REG) /* regular file */
+ {
+ SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, src, dst_path,
+ entryname_utf8, subpool));
+ }
+ else if (this_entry.filetype == APR_LNK) /* symlink */
+ {
+ const char *src_target = svn_dirent_join(src, entryname_utf8,
+ subpool);
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_link(src_target, dst_target,
+ subpool));
+ }
+ else if (this_entry.filetype == APR_DIR) /* recurse */
+ {
+ const char *src_target;
+
+ /* Prevent infinite recursion by filtering off our
+ newly created destination path. */
+ if (strcmp(src, dst_parent) == 0
+ && strcmp(entryname_utf8, dst_basename) == 0)
+ continue;
+
+ src_target = svn_dirent_join(src, entryname_utf8, subpool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
+ src_target,
+ dst_path,
+ entryname_utf8,
+ copy_perms,
+ cancel_func,
+ cancel_baton,
+ subpool));
+ }
+ /* ### support other APR node types someday?? */
+
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ /* Free any memory used by recursion */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
+ * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
+ * Set *SKIPPED_P to FALSE only if the file was copied, do not change the
+ * value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not required.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
+ const char *src_subdir,
+ const char *dst_subdir,
+ svn_revnum_t rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_subdir_shard = src_subdir,
+ *dst_subdir_shard = dst_subdir;
+
+ if (max_files_per_dir)
+ {
+ const char *shard = apr_psprintf(scratch_pool, "%ld",
+ rev / max_files_per_dir);
+ src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+ if (rev % max_files_per_dir == 0)
+ {
+ SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
+ SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
+ src_subdir_shard, dst_subdir_shard,
+ apr_psprintf(scratch_pool, "%ld", rev),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Copy a packed shard containing revision REV, and which contains
+ * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
+ * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
+ * Do not re-copy data which already exists in DST_FS.
+ * Set *SKIPPED_P to FALSE only if at least one part of the shard
+ * was copied, do not change the value in *SKIPPED_P otherwise.
+ * SKIPPED_P may be NULL if not required.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
+ svn_revnum_t *dst_min_unpacked_rev,
+ svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_revnum_t rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_subdir;
+ const char *dst_subdir;
+ const char *packed_shard;
+ const char *src_subdir_packed_shard;
+ svn_revnum_t revprop_rev;
+ apr_pool_t *iterpool;
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+
+ /* Copy the packed shard. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
+ packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / max_files_per_dir);
+ src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+ scratch_pool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
+ dst_subdir, packed_shard,
+ TRUE /* copy_perms */,
+ NULL /* cancel_func */, NULL,
+ scratch_pool));
+
+ /* Copy revprops belonging to revisions in this pack. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+
+ if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
+ || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
+ {
+ /* copy unpacked revprops rev by rev */
+ iterpool = svn_pool_create(scratch_pool);
+ for (revprop_rev = rev;
+ revprop_rev < rev + max_files_per_dir;
+ revprop_rev++)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
+ revprop_rev, max_files_per_dir,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ else
+ {
+ /* revprop for revision 0 will never be packed */
+ if (rev == 0)
+ SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
+ 0, max_files_per_dir,
+ scratch_pool));
+
+ /* packed revprops folder */
+ packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / max_files_per_dir);
+ src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+ scratch_pool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
+ src_subdir_packed_shard,
+ dst_subdir, packed_shard,
+ TRUE /* copy_perms */,
+ NULL /* cancel_func */, NULL,
+ scratch_pool));
+ }
+
+ /* If necessary, update the min-unpacked rev file in the hotcopy. */
+ if (*dst_min_unpacked_rev < rev + max_files_per_dir)
+ {
+ *dst_min_unpacked_rev = rev + max_files_per_dir;
+ SVN_ERR(svn_fs_fs__write_min_unpacked_rev(dst_fs,
+ *dst_min_unpacked_rev,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove file PATH, if it exists - even if it is read-only.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_remove_file(const char *path,
+ apr_pool_t *pool)
+{
+ /* Make the rev file writable and remove it. */
+ SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
+ SVN_ERR(svn_io_remove_file2(path, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Remove revision or revprop files between START_REV (inclusive) and
+ * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume
+ * sharding as per MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_remove_files(svn_fs_t *dst_fs,
+ const char *dst_subdir,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *shard;
+ const char *dst_subdir_shard;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool;
+
+ /* Pre-compute paths for initial shard. */
+ shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (rev = start_rev; rev < end_rev; rev++)
+ {
+ svn_pool_clear(iterpool);
+
+ /* If necessary, update paths for shard. */
+ if (rev != start_rev && rev % max_files_per_dir == 0)
+ {
+ shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+ }
+
+ /* remove files for REV */
+ SVN_ERR(hotcopy_remove_file(svn_dirent_join(dst_subdir_shard,
+ apr_psprintf(iterpool,
+ "%ld", rev),
+ iterpool),
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
+ * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_remove_rev_files(svn_fs_t *dst_fs,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+ SVN_ERR(hotcopy_remove_files(dst_fs,
+ svn_dirent_join(dst_fs->path,
+ PATH_REVS_DIR,
+ scratch_pool),
+ start_rev, end_rev,
+ max_files_per_dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove revision properties between START_REV (inclusive) and END_REV
+ * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will
+ * not be deleted. */
+static svn_error_t *
+hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+
+ /* don't delete rev 0 props */
+ SVN_ERR(hotcopy_remove_files(dst_fs,
+ svn_dirent_join(dst_fs->path,
+ PATH_REVPROPS_DIR,
+ scratch_pool),
+ start_rev ? start_rev : 1, end_rev,
+ max_files_per_dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that DST_FS is a suitable destination for an incremental
+ * hotcopy from SRC_FS. */
+static svn_error_t *
+hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+ /* We only support incremental hotcopy between the same format. */
+ if (src_ffd->format != dst_ffd->format)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The FSFS format (%d) of the hotcopy source does not match the "
+ "FSFS format (%d) of the hotcopy destination; please upgrade "
+ "both repositories to the same format"),
+ src_ffd->format, dst_ffd->format);
+
+ /* Make sure the UUID of source and destination match up.
+ * We don't want to copy over a different repository. */
+ if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
+ return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
+ _("The UUID of the hotcopy source does "
+ "not match the UUID of the hotcopy "
+ "destination"));
+
+ /* Also require same shard size. */
+ if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The sharding layout configuration "
+ "of the hotcopy source does not match "
+ "the sharding layout configuration of "
+ "the hotcopy destination"));
+ return SVN_NO_ERROR;
+}
+
+/* Remove folder PATH. Ignore errors due to the sub-tree not being empty.
+ * CANCEL_FUNC and CANCEL_BATON do the usual thing.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+remove_folder(const char *path,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_remove_dir2(path, TRUE,
+ cancel_func, cancel_baton, pool);
+
+ if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Copy the revision and revprop files (possibly sharded / packed) from
+ * SRC_FS to DST_FS. Do not re-copy data which already exists in DST_FS.
+ * When copying packed or unpacked shards, checkpoint the result in DST_FS
+ * for every shard by updating the 'current' file if necessary. Assume
+ * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
+ * global next-ID counters. Indicate progress via the optional NOTIFY_FUNC
+ * callback using NOTIFY_BATON. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+hotcopy_revisions(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_revnum_t src_youngest,
+ svn_revnum_t dst_youngest,
+ svn_boolean_t incremental,
+ const char *src_revs_dir,
+ const char *dst_revs_dir,
+ const char *src_revprops_dir,
+ const char *dst_revprops_dir,
+ svn_fs_hotcopy_notify_t notify_func,
+ void* notify_baton,
+ svn_cancel_func_t cancel_func,
+ void* cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+ int max_files_per_dir = src_ffd->max_files_per_dir;
+ svn_revnum_t src_min_unpacked_rev;
+ svn_revnum_t dst_min_unpacked_rev;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool;
+
+ /* Copy the min unpacked rev, and read its value. */
+ if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&src_min_unpacked_rev,
+ src_fs, pool));
+ SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&dst_min_unpacked_rev,
+ dst_fs, pool));
+
+ /* We only support packs coming from the hotcopy source.
+ * The destination should not be packed independently from
+ * the source. This also catches the case where users accidentally
+ * swap the source and destination arguments. */
+ if (src_min_unpacked_rev < dst_min_unpacked_rev)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The hotcopy destination already contains "
+ "more packed revisions (%lu) than the "
+ "hotcopy source contains (%lu)"),
+ dst_min_unpacked_rev - 1,
+ src_min_unpacked_rev - 1);
+
+ SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+ PATH_MIN_UNPACKED_REV, pool));
+ }
+ else
+ {
+ src_min_unpacked_rev = 0;
+ dst_min_unpacked_rev = 0;
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /*
+ * Copy the necessary rev files.
+ */
+
+ iterpool = svn_pool_create(pool);
+ /* First, copy packed shards. */
+ for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
+ {
+ svn_boolean_t skipped = TRUE;
+ svn_revnum_t pack_end_rev;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Copy the packed shard. */
+ SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
+ src_fs, dst_fs,
+ rev, max_files_per_dir,
+ iterpool));
+
+ pack_end_rev = rev + max_files_per_dir - 1;
+
+ /* Whenever this pack did not previously exist in the destination,
+ * update 'current' to the most recent packed rev (so readers can see
+ * new revisions which arrived in this pack). */
+ if (pack_end_rev > dst_youngest)
+ {
+ SVN_ERR(svn_fs_fs__write_current(dst_fs, pack_end_rev, 0, 0,
+ iterpool));
+ }
+
+ /* When notifying about packed shards, make things simpler by either
+ * reporting a full revision range, i.e [pack start, pack end] or
+ * reporting nothing. There is one case when this approach might not
+ * be exact (incremental hotcopy with a pack replacing last unpacked
+ * revisions), but generally this is good enough. */
+ if (notify_func && !skipped)
+ notify_func(notify_baton, rev, pack_end_rev, iterpool);
+
+ /* Remove revision files which are now packed. */
+ if (incremental)
+ {
+ SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
+ rev + max_files_per_dir,
+ max_files_per_dir, iterpool));
+ if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
+ rev + max_files_per_dir,
+ max_files_per_dir,
+ iterpool));
+ }
+
+ /* Now that all revisions have moved into the pack, the original
+ * rev dir can be removed. */
+ SVN_ERR(remove_folder(svn_fs_fs__path_rev_shard(dst_fs, rev, iterpool),
+ cancel_func, cancel_baton, iterpool));
+ if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ SVN_ERR(remove_folder(svn_fs_fs__path_revprops_shard(dst_fs, rev,
+ iterpool),
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
+ SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
+
+ /* Now, copy pairs of non-packed revisions and revprop files.
+ * If necessary, update 'current' after copying all files from a shard. */
+ for (; rev <= src_youngest; rev++)
+ {
+ svn_boolean_t skipped = TRUE;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Copying non-packed revisions is racy in case the source repository is
+ * being packed concurrently with this hotcopy operation. The race can
+ * happen with FS formats prior to SVN_FS_FS__MIN_PACK_LOCK_FORMAT that
+ * support packed revisions. With the pack lock, however, the race is
+ * impossible, because hotcopy and pack operations block each other.
+ *
+ * We assume that all revisions coming after 'min-unpacked-rev' really
+ * are unpacked and that's not necessarily true with concurrent packing.
+ * Don't try to be smart in this edge case, because handling it properly
+ * might require copying *everything* from the start. Just abort the
+ * hotcopy with an ENOENT (revision file moved to a pack, so it is no
+ * longer where we expect it to be). */
+
+ /* Copy the rev file. */
+ SVN_ERR(hotcopy_copy_shard_file(&skipped,
+ src_revs_dir, dst_revs_dir, rev,
+ max_files_per_dir,
+ iterpool));
+ /* Copy the revprop file. */
+ SVN_ERR(hotcopy_copy_shard_file(&skipped,
+ src_revprops_dir, dst_revprops_dir,
+ rev, max_files_per_dir,
+ iterpool));
+
+ /* Whenever this revision did not previously exist in the destination,
+ * checkpoint the progress via 'current' (do that once per full shard
+ * in order not to slow things down). */
+ if (rev > dst_youngest)
+ {
+ if (max_files_per_dir && (rev % max_files_per_dir == 0))
+ {
+ SVN_ERR(svn_fs_fs__write_current(dst_fs, rev, 0, 0,
+ iterpool));
+ }
+ }
+
+ if (notify_func && !skipped)
+ notify_func(notify_baton, rev, rev, iterpool);
+ }
+ svn_pool_destroy(iterpool);
+
+ /* We assume that all revisions were copied now, i.e. we didn't exit the
+ * above loop early. 'rev' was last incremented during exit of the loop. */
+ SVN_ERR_ASSERT(rev == src_youngest + 1);
+
+ return SVN_NO_ERROR;
+}
+
+/* Shortcut for the revision and revprop copying for old (1 or 2) format
+ * filesystems without sharding and packing. Copy the non-sharded revision
+ * and revprop files from SRC_FS to DST_FS. Do not re-copy data which
+ * already exists in DST_FS. Do not somehow checkpoint the results in
+ * the 'current' file in DST_FS. Indicate progress via the optional
+ * NOTIFY_FUNC callback using NOTIFY_BATON. Use POOL for temporary
+ * allocations. Also see hotcopy_revisions().
+ */
+static svn_error_t *
+hotcopy_revisions_old(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_revnum_t src_youngest,
+ const char *src_revs_dir,
+ const char *dst_revs_dir,
+ const char *src_revprops_dir,
+ const char *dst_revprops_dir,
+ svn_fs_hotcopy_notify_t notify_func,
+ void* notify_baton,
+ svn_cancel_func_t cancel_func,
+ void* cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_revnum_t rev;
+
+ for (rev = 0; rev <= src_youngest; rev++)
+ {
+ svn_boolean_t skipped = TRUE;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(hotcopy_io_dir_file_copy(&skipped, src_revs_dir, dst_revs_dir,
+ apr_psprintf(iterpool, "%ld", rev),
+ iterpool));
+ SVN_ERR(hotcopy_io_dir_file_copy(&skipped, src_revprops_dir,
+ dst_revprops_dir,
+ apr_psprintf(iterpool, "%ld", rev),
+ iterpool));
+
+ if (notify_func && !skipped)
+ notify_func(notify_baton, rev, rev, iterpool);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for hotcopy_body(). */
+struct hotcopy_body_baton {
+ svn_fs_t *src_fs;
+ svn_fs_t *dst_fs;
+ svn_boolean_t incremental;
+ svn_fs_hotcopy_notify_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+/* Perform a hotcopy, either normal or incremental.
+ *
+ * Normal hotcopy assumes that the destination exists as an empty
+ * directory. It behaves like an incremental hotcopy except that
+ * none of the copied files already exist in the destination.
+ *
+ * An incremental hotcopy copies only changed or new files to the destination,
+ * and removes files from the destination no longer present in the source.
+ * While the incremental hotcopy is running, readers should still be able
+ * to access the destintation repository without error and should not see
+ * revisions currently in progress of being copied. Readers are able to see
+ * new fully copied revisions even if the entire incremental hotcopy procedure
+ * has not yet completed.
+ *
+ * Writers are blocked out completely during the entire incremental hotcopy
+ * process to ensure consistency. This function assumes that the repository
+ * write-lock is held.
+ */
+static svn_error_t *
+hotcopy_body(void *baton, apr_pool_t *pool)
+{
+ struct hotcopy_body_baton *hbb = baton;
+ svn_fs_t *src_fs = hbb->src_fs;
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ svn_fs_t *dst_fs = hbb->dst_fs;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+ svn_boolean_t incremental = hbb->incremental;
+ svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
+ void* notify_baton = hbb->notify_baton;
+ svn_cancel_func_t cancel_func = hbb->cancel_func;
+ void* cancel_baton = hbb->cancel_baton;
+ svn_revnum_t src_youngest;
+ apr_uint64_t src_next_node_id;
+ apr_uint64_t src_next_copy_id;
+ svn_revnum_t dst_youngest;
+ const char *src_revprops_dir;
+ const char *dst_revprops_dir;
+ const char *src_revs_dir;
+ const char *dst_revs_dir;
+ const char *src_subdir;
+ const char *dst_subdir;
+ svn_node_kind_t kind;
+
+ /* Try to copy the config.
+ *
+ * ### We try copying the config file before doing anything else,
+ * ### because higher layers will abort the hotcopy if we throw
+ * ### an error from this function, and that renders the hotcopy
+ * ### unusable anyway. */
+ if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
+ pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* 1.6.0 to 1.6.11 did not copy the configuration file during
+ * hotcopy. So if we're hotcopying a repository which has been
+ * created as a hotcopy itself, it's possible that fsfs.conf
+ * does not exist. Ask the user to re-create it.
+ *
+ * ### It would be nice to make this a non-fatal error,
+ * ### but this function does not get an svn_fs_t object
+ * ### so we have no way of just printing a warning via
+ * ### the fs->warning() callback. */
+
+ const char *src_abspath;
+ const char *dst_abspath;
+ const char *config_relpath;
+ svn_error_t *err2;
+
+ config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
+ err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
+ if (err2)
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
+ if (err2)
+ return svn_error_trace(svn_error_compose_create(err, err2));
+
+ /* ### hack: strip off the 'db/' directory from paths so
+ * ### they make sense to the user */
+ src_abspath = svn_dirent_dirname(src_abspath, pool);
+ dst_abspath = svn_dirent_dirname(dst_abspath, pool);
+
+ return svn_error_quick_wrapf(err,
+ _("Failed to create hotcopy at '%s'. "
+ "The file '%s' is missing from the source "
+ "repository. Please create this file, for "
+ "instance by running 'svnadmin upgrade %s'"),
+ dst_abspath, config_relpath, src_abspath);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Find the youngest revision in the source and destination.
+ * We only support hotcopies from sources with an equal or greater amount
+ * of revisions than the destination.
+ * This also catches the case where users accidentally swap the
+ * source and destination arguments. */
+ SVN_ERR(svn_fs_fs__read_current(&src_youngest, &src_next_node_id,
+ &src_next_copy_id, src_fs, pool));
+ if (incremental)
+ {
+ SVN_ERR(svn_fs_fs__youngest_rev(&dst_youngest, dst_fs, pool));
+ if (src_youngest < dst_youngest)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The hotcopy destination already contains more revisions "
+ "(%lu) than the hotcopy source contains (%lu); are source "
+ "and destination swapped?"),
+ dst_youngest, src_youngest);
+ }
+ else
+ dst_youngest = 0;
+
+ src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
+ dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
+ src_revprops_dir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
+ dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
+
+ /* Ensure that the required folders exist in the destination
+ * before actually copying the revisions and revprops. */
+ SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, pool));
+ SVN_ERR(svn_io_make_dir_recursively(dst_revprops_dir, pool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Split the logic for new and old FS formats. The latter is much simpler
+ * due to the absense of sharding and packing. However, it requires special
+ * care when updating the 'current' file (which contains not just the
+ * revision number, but also the next-ID counters). */
+ if (src_ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
+ incremental, src_revs_dir, dst_revs_dir,
+ src_revprops_dir, dst_revprops_dir,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool));
+ SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, 0, 0, pool));
+ }
+ else
+ {
+ SVN_ERR(hotcopy_revisions_old(src_fs, dst_fs, src_youngest,
+ src_revs_dir, dst_revs_dir,
+ src_revprops_dir, dst_revprops_dir,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool));
+ SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, src_next_node_id,
+ src_next_copy_id, pool));
+ }
+
+ /* Replace the locks tree.
+ * This is racy in case readers are currently trying to list locks in
+ * the destination. However, we need to get rid of stale locks.
+ * This is the simplest way of doing this, so we accept this small race. */
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
+ SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
+ pool));
+ src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
+ PATH_LOCKS_DIR, TRUE,
+ cancel_func, cancel_baton, pool));
+
+ /* Now copy the node-origins cache tree. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_dir)
+ SVN_ERR(hotcopy_io_copy_dir_recursively(NULL, src_subdir, dst_fs->path,
+ PATH_NODE_ORIGINS_DIR, TRUE,
+ cancel_func, cancel_baton, pool));
+
+ /*
+ * NB: Data copied below is only read by writers, not readers.
+ * Writers are still locked out at this point.
+ */
+
+ if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ {
+ /* Copy the rep cache and then remove entries for revisions
+ * that did not make it into the destination. */
+ src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
+
+ /* The source might have r/o flags set on it - which would be
+ carried over to the copy. */
+ SVN_ERR(svn_io_set_file_read_write(dst_subdir, FALSE, pool));
+ SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, src_youngest, pool));
+ }
+ }
+
+ /* Copy the txn-current file. */
+ if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+ PATH_TXN_CURRENT, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create an empty filesystem at DST_FS at DST_PATH with the same
+ * configuration as SRC_FS (uuid, format, and other parameters).
+ * After creation DST_FS has no revisions, not even revision zero. */
+static svn_error_t *
+hotcopy_create_empty_dest(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *dst_path,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+
+ /* Create the DST_FS repository with the same layout as SRC_FS. */
+ SVN_ERR(svn_fs_fs__create_file_tree(dst_fs, dst_path, src_ffd->format,
+ src_ffd->max_files_per_dir,
+ src_ffd->use_log_addressing,
+ pool));
+
+ /* Copy the UUID. Hotcopy destination receives a new instance ID, but
+ * has the same filesystem UUID as the source. */
+ SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, NULL, pool));
+
+ /* Remove revision 0 contents. Otherwise, it may not get overwritten
+ * due to having a newer timestamp. */
+ SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_rev(dst_fs, 0, pool), pool));
+ SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_revprops(dst_fs, 0, pool),
+ pool));
+
+ /* This filesystem is ready. Stamp it with a format number. Fail if
+ * the 'format' file should already exist. */
+ SVN_ERR(svn_fs_fs__write_format(dst_fs, FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *dst_path,
+ svn_boolean_t incremental,
+ apr_pool_t *pool)
+{
+ if (incremental)
+ {
+ const char *dst_format_abspath;
+ svn_node_kind_t dst_format_kind;
+
+ /* Check destination format to be sure we know how to incrementally
+ * hotcopy to the destination FS. */
+ dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
+ SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
+ if (dst_format_kind == svn_node_none)
+ {
+ /* Destination doesn't exist yet. Perform a normal hotcopy to a
+ * empty destination using the same configuration as the source. */
+ SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
+ }
+ else
+ {
+ /* Check the existing repository. */
+ SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
+ SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
+ pool));
+ }
+ }
+ else
+ {
+ /* Start out with an empty destination using the same configuration
+ * as the source. */
+ SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_boolean_t incremental,
+ svn_fs_hotcopy_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct hotcopy_body_baton hbb;
+
+ hbb.src_fs = src_fs;
+ hbb.dst_fs = dst_fs;
+ hbb.incremental = incremental;
+ hbb.notify_func = notify_func;
+ hbb.notify_baton = notify_baton;
+ hbb.cancel_func = cancel_func;
+ hbb.cancel_baton = cancel_baton;
+ SVN_ERR(svn_fs_fs__with_all_locks(dst_fs, hotcopy_body, &hbb, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/hotcopy.h b/subversion/libsvn_fs_fs/hotcopy.h
new file mode 100644
index 0000000..ddd6218
--- /dev/null
+++ b/subversion/libsvn_fs_fs/hotcopy.h
@@ -0,0 +1,51 @@
+/* hotcopy.h : interface to the native filesystem layer
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__HOTCOPY_H
+#define SVN_LIBSVN_FS__HOTCOPY_H
+
+#include "fs.h"
+
+/* Create an empty copy of the fsfs filesystem SRC_FS into a new DST_FS at
+ * DST_PATH. If INCREMENTAL is TRUE, perform a few pre-checks only if
+ * a repo already exists at DST_PATH. Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *dst_path,
+ svn_boolean_t incremental,
+ apr_pool_t *pool);
+
+/* Copy the fsfs filesystem SRC_FS into DST_FS. If INCREMENTAL is TRUE, do
+ * not re-copy data which already exists in DST_FS. Indicate progress via
+ * the optional NOTIFY_FUNC callback using NOTIFY_BATON. Use POOL for
+ * temporary allocations. */
+svn_error_t * svn_fs_fs__hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_boolean_t incremental,
+ svn_fs_hotcopy_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/id.c b/subversion/libsvn_fs_fs/id.c
index 1317829..bd505e0 100644
--- a/subversion/libsvn_fs_fs/id.c
+++ b/subversion/libsvn_fs_fs/id.c
@@ -24,90 +24,311 @@
#include <stdlib.h>
#include "id.h"
+#include "index.h"
+
#include "../libsvn_fs/fs-loader.h"
#include "private/svn_temp_serializer.h"
#include "private/svn_string_private.h"
-typedef struct id_private_t {
- const char *node_id;
- const char *copy_id;
- const char *txn_id;
- svn_revnum_t rev;
- apr_off_t offset;
-} id_private_t;
+typedef struct fs_fs__id_t
+{
+ /* API visible part */
+ svn_fs_id_t generic_id;
+
+ /* private members */
+ struct
+ {
+ svn_fs_fs__id_part_t node_id;
+ svn_fs_fs__id_part_t copy_id;
+ svn_fs_fs__id_part_t txn_id;
+ svn_fs_fs__id_part_t rev_item;
+ } private_id;
+} fs_fs__id_t;
-/* Accessing ID Pieces. */
-const char *
-svn_fs_fs__id_node_id(const svn_fs_id_t *id)
+/** Like strtol but with a fixed base of 10, locale independent and limited
+ * to non-negative values. Overflows are indicated by a FALSE return value
+ * in which case *RESULT_P will not be modified.
+ *
+ * This allows the compiler to generate massively faster code.
+ * (E.g. Avoiding locale specific processing). ID parsing is one of the
+ * most CPU consuming parts of FSFS data access. Better be quick.
+ */
+static svn_boolean_t
+locale_independent_strtol(long *result_p,
+ const char* buffer,
+ const char** end)
+{
+ /* We allow positive values only. We use unsigned arithmetics to get
+ * well-defined overflow behavior. It also happens to allow for a wider
+ * range of compiler-side optimizations. */
+ unsigned long result = 0;
+ while (1)
+ {
+ unsigned long c = (unsigned char)*buffer - (unsigned char)'0';
+ unsigned long next;
+
+ /* This implies the NUL check. */
+ if (c > 9)
+ break;
+
+ /* Overflow check. Passing this, NEXT can be no more than ULONG_MAX+9
+ * before being truncated to ULONG but it still covers 0 .. ULONG_MAX.
+ */
+ if (result > ULONG_MAX / 10)
+ return FALSE;
+
+ next = result * 10 + c;
+
+ /* Overflow check. In case of an overflow, NEXT is 0..9.
+ * In the non-overflow case, RESULT is either >= 10 or RESULT and NEXT
+ * are both 0. */
+ if (next < result)
+ return FALSE;
+
+ result = next;
+ ++buffer;
+ }
+
+ *end = buffer;
+ if (result > LONG_MAX)
+ return FALSE;
+
+ *result_p = (long)result;
+
+ return TRUE;
+}
+
+/* Parse the NUL-terminated ID part at DATA and write the result into *PART.
+ * Return TRUE if no errors were detected. */
+static svn_boolean_t
+part_parse(svn_fs_fs__id_part_t *part,
+ const char *data)
{
- id_private_t *pvt = id->fsap_data;
+ const char *end;
+
+ /* special case: ID inside some transaction */
+ if (data[0] == '_')
+ {
+ part->revision = SVN_INVALID_REVNUM;
+ part->number = svn__base36toui64(&data, data + 1);
+ return *data == '\0';
+ }
+
+ /* special case: 0 / default ID */
+ if (data[0] == '0' && data[1] == '\0')
+ {
+ part->revision = 0;
+ part->number = 0;
+ return TRUE;
+ }
- return pvt->node_id;
+ /* read old style / new style ID */
+ part->number = svn__base36toui64(&data, data);
+ if (data[0] != '-')
+ {
+ part->revision = 0;
+ return *data == '\0';
+ }
+
+ return locale_independent_strtol(&part->revision, data+1, &end);
}
+/* Parse the transaction id in DATA and store the result in *TXN_ID.
+ * Return FALSE if there was some problem.
+ */
+static svn_boolean_t
+txn_id_parse(svn_fs_fs__id_part_t *txn_id,
+ const char *data)
+{
+ const char *end;
+ if (!locale_independent_strtol(&txn_id->revision, data, &end))
+ return FALSE;
+
+ data = end;
+ if (*data != '-')
+ return FALSE;
-const char *
-svn_fs_fs__id_copy_id(const svn_fs_id_t *id)
+ ++data;
+ txn_id->number = svn__base36toui64(&data, data);
+ return *data == '\0';
+}
+
+/* Write the textual representation of *PART into P and return a pointer
+ * to the first position behind that string.
+ */
+static char *
+unparse_id_part(char *p,
+ const svn_fs_fs__id_part_t *part)
+{
+ if (SVN_IS_VALID_REVNUM(part->revision))
+ {
+ /* ordinary old style / new style ID */
+ p += svn__ui64tobase36(p, part->number);
+ if (part->revision > 0)
+ {
+ *(p++) = '-';
+ p += svn__i64toa(p, part->revision);
+ }
+ }
+ else
+ {
+ /* in txn: mark with "_" prefix */
+ *(p++) = '_';
+ p += svn__ui64tobase36(p, part->number);
+ }
+
+ *(p++) = '.';
+
+ return p;
+}
+
+
+
+/* Operations on ID parts */
+
+svn_boolean_t
+svn_fs_fs__id_part_is_root(const svn_fs_fs__id_part_t* part)
+{
+ return part->revision == 0 && part->number == 0;
+}
+
+svn_boolean_t
+svn_fs_fs__id_part_eq(const svn_fs_fs__id_part_t *lhs,
+ const svn_fs_fs__id_part_t *rhs)
+{
+ return lhs->revision == rhs->revision && lhs->number == rhs->number;
+}
+
+svn_boolean_t
+svn_fs_fs__id_txn_used(const svn_fs_fs__id_part_t *txn_id)
{
- id_private_t *pvt = id->fsap_data;
+ return SVN_IS_VALID_REVNUM(txn_id->revision) || (txn_id->number != 0);
+}
- return pvt->copy_id;
+void
+svn_fs_fs__id_txn_reset(svn_fs_fs__id_part_t *txn_id)
+{
+ txn_id->revision = SVN_INVALID_REVNUM;
+ txn_id->number = 0;
}
+svn_error_t *
+svn_fs_fs__id_txn_parse(svn_fs_fs__id_part_t *txn_id,
+ const char *data)
+{
+ if (! txn_id_parse(txn_id, data))
+ return svn_error_createf(SVN_ERR_FS_MALFORMED_TXN_ID, NULL,
+ "malformed txn id '%s'", data);
+
+ return SVN_NO_ERROR;
+}
const char *
-svn_fs_fs__id_txn_id(const svn_fs_id_t *id)
+svn_fs_fs__id_txn_unparse(const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
{
- id_private_t *pvt = id->fsap_data;
+ char string[2 * SVN_INT64_BUFFER_SIZE + 1];
+ char *p = string;
- return pvt->txn_id;
+ p += svn__i64toa(p, txn_id->revision);
+ *(p++) = '-';
+ p += svn__ui64tobase36(p, txn_id->number);
+
+ return apr_pstrmemdup(pool, string, p - string);
}
+
+
+/* Accessing ID Pieces. */
-svn_revnum_t
-svn_fs_fs__id_rev(const svn_fs_id_t *id)
+const svn_fs_fs__id_part_t *
+svn_fs_fs__id_node_id(const svn_fs_id_t *fs_id)
{
- id_private_t *pvt = id->fsap_data;
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
- return pvt->rev;
+ return &id->private_id.node_id;
}
-apr_off_t
-svn_fs_fs__id_offset(const svn_fs_id_t *id)
+const svn_fs_fs__id_part_t *
+svn_fs_fs__id_copy_id(const svn_fs_id_t *fs_id)
{
- id_private_t *pvt = id->fsap_data;
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
- return pvt->offset;
+ return &id->private_id.copy_id;
}
+const svn_fs_fs__id_part_t *
+svn_fs_fs__id_txn_id(const svn_fs_id_t *fs_id)
+{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ return &id->private_id.txn_id;
+}
+
+
+const svn_fs_fs__id_part_t *
+svn_fs_fs__id_rev_item(const svn_fs_id_t *fs_id)
+{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ return &id->private_id.rev_item;
+}
+
+svn_revnum_t
+svn_fs_fs__id_rev(const svn_fs_id_t *fs_id)
+{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ return id->private_id.rev_item.revision;
+}
+
+apr_uint64_t
+svn_fs_fs__id_item(const svn_fs_id_t *fs_id)
+{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ return id->private_id.rev_item.number;
+}
+
+svn_boolean_t
+svn_fs_fs__id_is_txn(const svn_fs_id_t *fs_id)
+{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ return svn_fs_fs__id_txn_used(&id->private_id.txn_id);
+}
+
svn_string_t *
-svn_fs_fs__id_unparse(const svn_fs_id_t *id,
+svn_fs_fs__id_unparse(const svn_fs_id_t *fs_id,
apr_pool_t *pool)
{
- id_private_t *pvt = id->fsap_data;
+ char string[6 * SVN_INT64_BUFFER_SIZE + 10];
+ const fs_fs__id_t *id = (const fs_fs__id_t *)fs_id;
+
+ char *p = unparse_id_part(string, &id->private_id.node_id);
+ p = unparse_id_part(p, &id->private_id.copy_id);
- if ((! pvt->txn_id))
+ if (svn_fs_fs__id_txn_used(&id->private_id.txn_id))
{
- char rev_string[SVN_INT64_BUFFER_SIZE];
- char offset_string[SVN_INT64_BUFFER_SIZE];
-
- svn__i64toa(rev_string, pvt->rev);
- svn__i64toa(offset_string, pvt->offset);
- return svn_string_createf(pool, "%s.%s.r%s/%s",
- pvt->node_id, pvt->copy_id,
- rev_string, offset_string);
+ *(p++) = 't';
+ p += svn__i64toa(p, id->private_id.txn_id.revision);
+ *(p++) = '-';
+ p += svn__ui64tobase36(p, id->private_id.txn_id.number);
}
else
{
- return svn_string_createf(pool, "%s.%s.t%s",
- pvt->node_id, pvt->copy_id,
- pvt->txn_id);
+ *(p++) = 'r';
+ p += svn__i64toa(p, id->private_id.rev_item.revision);
+ *(p++) = '/';
+ p += svn__i64toa(p, id->private_id.rev_item.number);
}
+
+ return svn_string_ncreate(string, p - string, pool);
}
@@ -117,23 +338,20 @@ svn_boolean_t
svn_fs_fs__id_eq(const svn_fs_id_t *a,
const svn_fs_id_t *b)
{
- id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+ const fs_fs__id_t *id_a = (const fs_fs__id_t *)a;
+ const fs_fs__id_t *id_b = (const fs_fs__id_t *)b;
if (a == b)
return TRUE;
- if (strcmp(pvta->node_id, pvtb->node_id) != 0)
- return FALSE;
- if (strcmp(pvta->copy_id, pvtb->copy_id) != 0)
- return FALSE;
- if ((pvta->txn_id == NULL) != (pvtb->txn_id == NULL))
- return FALSE;
- if (pvta->txn_id && pvtb->txn_id && strcmp(pvta->txn_id, pvtb->txn_id) != 0)
- return FALSE;
- if (pvta->rev != pvtb->rev)
- return FALSE;
- if (pvta->offset != pvtb->offset)
- return FALSE;
- return TRUE;
+
+ return svn_fs_fs__id_part_eq(&id_a->private_id.node_id,
+ &id_b->private_id.node_id)
+ && svn_fs_fs__id_part_eq(&id_a->private_id.copy_id,
+ &id_b->private_id.copy_id)
+ && svn_fs_fs__id_part_eq(&id_a->private_id.txn_id,
+ &id_b->private_id.txn_id)
+ && svn_fs_fs__id_part_eq(&id_a->private_id.rev_item,
+ &id_b->private_id.rev_item);
}
@@ -141,30 +359,54 @@ svn_boolean_t
svn_fs_fs__id_check_related(const svn_fs_id_t *a,
const svn_fs_id_t *b)
{
- id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+ const fs_fs__id_t *id_a = (const fs_fs__id_t *)a;
+ const fs_fs__id_t *id_b = (const fs_fs__id_t *)b;
if (a == b)
return TRUE;
- /* If both node_ids start with _ and they have differing transaction
- IDs, then it is impossible for them to be related. */
- if (pvta->node_id[0] == '_')
+
+ /* If both node_ids have been created within _different_ transactions
+ (and are still uncommitted), then it is impossible for them to be
+ related.
+
+ Due to our txn-local temporary IDs, however, they might have been
+ given the same temporary node ID. We need to detect that case.
+ */
+ if ( id_a->private_id.node_id.revision == SVN_INVALID_REVNUM
+ && id_b->private_id.node_id.revision == SVN_INVALID_REVNUM)
{
- if (pvta->txn_id && pvtb->txn_id &&
- (strcmp(pvta->txn_id, pvtb->txn_id) != 0))
+ if (!svn_fs_fs__id_part_eq(&id_a->private_id.txn_id,
+ &id_b->private_id.txn_id))
return FALSE;
+
+ /* At this point, matching node_ids implies relatedness. */
}
- return (strcmp(pvta->node_id, pvtb->node_id) == 0);
+ return svn_fs_fs__id_part_eq(&id_a->private_id.node_id,
+ &id_b->private_id.node_id);
}
-int
+svn_fs_node_relation_t
svn_fs_fs__id_compare(const svn_fs_id_t *a,
const svn_fs_id_t *b)
{
if (svn_fs_fs__id_eq(a, b))
- return 0;
- return (svn_fs_fs__id_check_related(a, b) ? 1 : -1);
+ return svn_fs_node_unchanged;
+ return (svn_fs_fs__id_check_related(a, b) ? svn_fs_node_common_ancestor
+ : svn_fs_node_unrelated);
+}
+
+int
+svn_fs_fs__id_part_compare(const svn_fs_fs__id_part_t *a,
+ const svn_fs_fs__id_part_t *b)
+{
+ if (a->revision < b->revision)
+ return -1;
+ if (a->revision > b->revision)
+ return 1;
+
+ return a->number < b->number ? -1 : a->number == b->number ? 0 : 1;
}
@@ -176,87 +418,102 @@ static id_vtable_t id_vtable = {
svn_fs_fs__id_compare
};
-
svn_fs_id_t *
-svn_fs_fs__id_txn_create(const char *node_id,
- const char *copy_id,
- const char *txn_id,
- apr_pool_t *pool)
+svn_fs_fs__id_txn_create_root(const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
{
- svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
- id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+ fs_fs__id_t *id = apr_pcalloc(pool, sizeof(*id));
+
+ /* node ID and copy ID are "0" */
+
+ id->private_id.txn_id = *txn_id;
+ id->private_id.rev_item.revision = SVN_INVALID_REVNUM;
- pvt->node_id = apr_pstrdup(pool, node_id);
- pvt->copy_id = apr_pstrdup(pool, copy_id);
- pvt->txn_id = apr_pstrdup(pool, txn_id);
- pvt->rev = SVN_INVALID_REVNUM;
- pvt->offset = -1;
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
- id->vtable = &id_vtable;
- id->fsap_data = pvt;
- return id;
+ return (svn_fs_id_t *)id;
}
+svn_fs_id_t *svn_fs_fs__id_create_root(const svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ fs_fs__id_t *id = apr_pcalloc(pool, sizeof(*id));
+
+ id->private_id.txn_id.revision = SVN_INVALID_REVNUM;
+ id->private_id.rev_item.revision = revision;
+ id->private_id.rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
+
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
+
+ return (svn_fs_id_t *)id;
+}
svn_fs_id_t *
-svn_fs_fs__id_rev_create(const char *node_id,
- const char *copy_id,
- svn_revnum_t rev,
- apr_off_t offset,
+svn_fs_fs__id_txn_create(const svn_fs_fs__id_part_t *node_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
{
- svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
- id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+ fs_fs__id_t *id = apr_pcalloc(pool, sizeof(*id));
- pvt->node_id = apr_pstrdup(pool, node_id);
- pvt->copy_id = apr_pstrdup(pool, copy_id);
- pvt->txn_id = NULL;
- pvt->rev = rev;
- pvt->offset = offset;
+ id->private_id.node_id = *node_id;
+ id->private_id.copy_id = *copy_id;
+ id->private_id.txn_id = *txn_id;
+ id->private_id.rev_item.revision = SVN_INVALID_REVNUM;
- id->vtable = &id_vtable;
- id->fsap_data = pvt;
- return id;
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
+
+ return (svn_fs_id_t *)id;
}
svn_fs_id_t *
-svn_fs_fs__id_copy(const svn_fs_id_t *id, apr_pool_t *pool)
+svn_fs_fs__id_rev_create(const svn_fs_fs__id_part_t *node_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *rev_item,
+ apr_pool_t *pool)
{
- svn_fs_id_t *new_id = apr_palloc(pool, sizeof(*new_id));
- id_private_t *new_pvt = apr_palloc(pool, sizeof(*new_pvt));
- id_private_t *pvt = id->fsap_data;
+ fs_fs__id_t *id = apr_pcalloc(pool, sizeof(*id));
- new_pvt->node_id = apr_pstrdup(pool, pvt->node_id);
- new_pvt->copy_id = apr_pstrdup(pool, pvt->copy_id);
- new_pvt->txn_id = pvt->txn_id ? apr_pstrdup(pool, pvt->txn_id) : NULL;
- new_pvt->rev = pvt->rev;
- new_pvt->offset = pvt->offset;
+ id->private_id.node_id = *node_id;
+ id->private_id.copy_id = *copy_id;
+ id->private_id.txn_id.revision = SVN_INVALID_REVNUM;
+ id->private_id.rev_item = *rev_item;
- new_id->vtable = &id_vtable;
- new_id->fsap_data = new_pvt;
- return new_id;
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
+
+ return (svn_fs_id_t *)id;
}
svn_fs_id_t *
-svn_fs_fs__id_parse(const char *data,
- apr_size_t len,
- apr_pool_t *pool)
+svn_fs_fs__id_copy(const svn_fs_id_t *source, apr_pool_t *pool)
{
- svn_fs_id_t *id;
- id_private_t *pvt;
- char *data_copy, *str;
+ const fs_fs__id_t *id = (const fs_fs__id_t *)source;
+ fs_fs__id_t *new_id = apr_pmemdup(pool, id, sizeof(*new_id));
+
+ new_id->generic_id.fsap_data = new_id;
+
+ return (svn_fs_id_t *)new_id;
+}
- /* Dup the ID data into POOL. Our returned ID will have references
- into this memory. */
- data_copy = apr_pstrmemdup(pool, data, len);
+/* Return an ID resulting from parsing the string DATA, or NULL if DATA is
+ an invalid ID string. *DATA will be modified / invalidated by this call. */
+static svn_fs_id_t *
+id_parse(char *data,
+ apr_pool_t *pool)
+{
+ fs_fs__id_t *id;
+ char *str;
/* Alloc a new svn_fs_id_t structure. */
- id = apr_palloc(pool, sizeof(*id));
- pvt = apr_palloc(pool, sizeof(*pvt));
- id->vtable = &id_vtable;
- id->fsap_data = pvt;
+ id = apr_pcalloc(pool, sizeof(*id));
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
/* Now, we basically just need to "split" this data on `.'
characters. We will use svn_cstring_tokenize, which will put
@@ -265,141 +522,120 @@ svn_fs_fs__id_parse(const char *data,
string.*/
/* Node Id */
- str = svn_cstring_tokenize(".", &data_copy);
+ str = svn_cstring_tokenize(".", &data);
if (str == NULL)
return NULL;
- pvt->node_id = str;
+ if (! part_parse(&id->private_id.node_id, str))
+ return NULL;
/* Copy Id */
- str = svn_cstring_tokenize(".", &data_copy);
+ str = svn_cstring_tokenize(".", &data);
if (str == NULL)
return NULL;
- pvt->copy_id = str;
+ if (! part_parse(&id->private_id.copy_id, str))
+ return NULL;
/* Txn/Rev Id */
- str = svn_cstring_tokenize(".", &data_copy);
+ str = svn_cstring_tokenize(".", &data);
if (str == NULL)
return NULL;
if (str[0] == 'r')
{
apr_int64_t val;
+ const char *tmp;
svn_error_t *err;
/* This is a revision type ID */
- pvt->txn_id = NULL;
+ id->private_id.txn_id.revision = SVN_INVALID_REVNUM;
+ id->private_id.txn_id.number = 0;
- data_copy = str + 1;
- str = svn_cstring_tokenize("/", &data_copy);
+ data = str + 1;
+ str = svn_cstring_tokenize("/", &data);
if (str == NULL)
return NULL;
- pvt->rev = SVN_STR_TO_REV(str);
-
- str = svn_cstring_tokenize("/", &data_copy);
- if (str == NULL)
+ if (!locale_independent_strtol(&id->private_id.rev_item.revision,
+ str, &tmp))
return NULL;
- err = svn_cstring_atoi64(&val, str);
+
+ err = svn_cstring_atoi64(&val, data);
if (err)
{
svn_error_clear(err);
return NULL;
}
- pvt->offset = (apr_off_t)val;
+ id->private_id.rev_item.number = (apr_uint64_t)val;
}
else if (str[0] == 't')
{
/* This is a transaction type ID */
- pvt->txn_id = str + 1;
- pvt->rev = SVN_INVALID_REVNUM;
- pvt->offset = -1;
+ id->private_id.rev_item.revision = SVN_INVALID_REVNUM;
+ id->private_id.rev_item.number = 0;
+
+ if (! txn_id_parse(&id->private_id.txn_id, str + 1))
+ return NULL;
}
else
return NULL;
- return id;
+ return (svn_fs_id_t *)id;
}
-/* (de-)serialization support */
-
-/* Serialization of the PVT sub-structure within the CONTEXT.
- */
-static void
-serialize_id_private(svn_temp_serializer__context_t *context,
- const id_private_t * const *pvt)
+svn_error_t *
+svn_fs_fs__id_parse(const svn_fs_id_t **id_p,
+ char *data,
+ apr_pool_t *pool)
{
- const id_private_t *private = *pvt;
-
- /* serialize the pvt data struct itself */
- svn_temp_serializer__push(context,
- (const void * const *)pvt,
- sizeof(*private));
+ svn_fs_id_t *id = id_parse(data, pool);
+ if (id == NULL)
+ return svn_error_createf(SVN_ERR_FS_MALFORMED_NODEREV_ID, NULL,
+ "Malformed node revision ID string");
- /* append the referenced strings */
- svn_temp_serializer__add_string(context, &private->node_id);
- svn_temp_serializer__add_string(context, &private->copy_id);
- svn_temp_serializer__add_string(context, &private->txn_id);
+ *id_p = id;
- /* return to caller's nesting level */
- svn_temp_serializer__pop(context);
+ return SVN_NO_ERROR;
}
+/* (de-)serialization support */
+
/* Serialize an ID within the serialization CONTEXT.
*/
void
svn_fs_fs__id_serialize(svn_temp_serializer__context_t *context,
- const struct svn_fs_id_t * const *id)
+ const svn_fs_id_t * const *in)
{
+ const fs_fs__id_t *id = (const fs_fs__id_t *)*in;
+
/* nothing to do for NULL ids */
- if (*id == NULL)
+ if (id == NULL)
return;
/* serialize the id data struct itself */
- svn_temp_serializer__push(context,
- (const void * const *)id,
- sizeof(**id));
-
- /* serialize the id_private_t data sub-struct */
- serialize_id_private(context,
- (const id_private_t * const *)&(*id)->fsap_data);
-
- /* return to caller's nesting level */
- svn_temp_serializer__pop(context);
-}
-
-/* Deserialization of the PVT sub-structure in BUFFER.
- */
-static void
-deserialize_id_private(void *buffer, id_private_t **pvt)
-{
- /* fixup the reference to the only sub-structure */
- id_private_t *private;
- svn_temp_deserializer__resolve(buffer, (void**)pvt);
-
- /* fixup the sub-structure itself */
- private = *pvt;
- svn_temp_deserializer__resolve(private, (void**)&private->node_id);
- svn_temp_deserializer__resolve(private, (void**)&private->copy_id);
- svn_temp_deserializer__resolve(private, (void**)&private->txn_id);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)in,
+ sizeof(fs_fs__id_t));
}
/* Deserialize an ID inside the BUFFER.
*/
void
-svn_fs_fs__id_deserialize(void *buffer, svn_fs_id_t **id)
+svn_fs_fs__id_deserialize(void *buffer, svn_fs_id_t **in_out)
{
+ fs_fs__id_t *id;
+
/* The id maybe all what is in the whole buffer.
* Don't try to fixup the pointer in that case*/
- if (*id != buffer)
- svn_temp_deserializer__resolve(buffer, (void**)id);
+ if (*in_out != buffer)
+ svn_temp_deserializer__resolve(buffer, (void**)in_out);
+
+ id = (fs_fs__id_t *)*in_out;
/* no id, no sub-structure fixup necessary */
- if (*id == NULL)
+ if (id == NULL)
return;
/* the stored vtable is bogus at best -> set the right one */
- (*id)->vtable = &id_vtable;
-
- /* handle sub-structures */
- deserialize_id_private(*id, (id_private_t **)&(*id)->fsap_data);
+ id->generic_id.vtable = &id_vtable;
+ id->generic_id.fsap_data = id;
}
diff --git a/subversion/libsvn_fs_fs/id.h b/subversion/libsvn_fs_fs/id.h
index 11da466..d556a16 100644
--- a/subversion/libsvn_fs_fs/id.h
+++ b/subversion/libsvn_fs_fs/id.h
@@ -24,30 +24,63 @@
#define SVN_LIBSVN_FS_FS_ID_H
#include "svn_fs.h"
+#include "private/svn_fs_fs_private.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
+/*** Operations on ID parts. ***/
+
+/* Return TRUE, if both elements of the PART is 0, i.e. this is the default
+ * value if e.g. no copies were made of this node. */
+svn_boolean_t svn_fs_fs__id_part_is_root(const svn_fs_fs__id_part_t *part);
+
+/* Return TRUE, if all element values of *LHS and *RHS match. */
+svn_boolean_t svn_fs_fs__id_part_eq(const svn_fs_fs__id_part_t *lhs,
+ const svn_fs_fs__id_part_t *rhs);
+
+/* Return TRUE, if TXN_ID is used, i.e. doesn't contain just the defaults. */
+svn_boolean_t svn_fs_fs__id_txn_used(const svn_fs_fs__id_part_t *txn_id);
+
+/* Reset TXN_ID to the defaults. */
+void svn_fs_fs__id_txn_reset(svn_fs_fs__id_part_t *txn_id);
+
+/* Parse the transaction id in DATA and store the result in *TXN_ID */
+svn_error_t *svn_fs_fs__id_txn_parse(svn_fs_fs__id_part_t *txn_id,
+ const char *data);
+
+/* Convert the transaction id in *TXN_ID into a textual representation
+ * allocated in POOL. */
+const char *svn_fs_fs__id_txn_unparse(const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+
/*** ID accessor functions. ***/
/* Get the "node id" portion of ID. */
-const char *svn_fs_fs__id_node_id(const svn_fs_id_t *id);
+const svn_fs_fs__id_part_t *svn_fs_fs__id_node_id(const svn_fs_id_t *id);
/* Get the "copy id" portion of ID. */
-const char *svn_fs_fs__id_copy_id(const svn_fs_id_t *id);
+const svn_fs_fs__id_part_t *svn_fs_fs__id_copy_id(const svn_fs_id_t *id);
/* Get the "txn id" portion of ID, or NULL if it is a permanent ID. */
-const char *svn_fs_fs__id_txn_id(const svn_fs_id_t *id);
+const svn_fs_fs__id_part_t *svn_fs_fs__id_txn_id(const svn_fs_id_t *id);
+
+/* Get the "rev,item" portion of ID. */
+const svn_fs_fs__id_part_t *svn_fs_fs__id_rev_item(const svn_fs_id_t *id);
/* Get the "rev" portion of ID, or SVN_INVALID_REVNUM if it is a
transaction ID. */
svn_revnum_t svn_fs_fs__id_rev(const svn_fs_id_t *id);
-/* Access the "offset" portion of the ID, or -1 if it is a transaction
+/* Access the "item" portion of the ID, or 0 if it is a transaction
ID. */
-apr_off_t svn_fs_fs__id_offset(const svn_fs_id_t *id);
+apr_uint64_t svn_fs_fs__id_item(const svn_fs_id_t *id);
+
+/* Return TRUE, if this is a transaction ID. */
+svn_boolean_t svn_fs_fs__id_is_txn(const svn_fs_id_t *id);
/* Convert ID into string form, allocated in POOL. */
svn_string_t *svn_fs_fs__id_unparse(const svn_fs_id_t *id,
@@ -61,34 +94,48 @@ svn_boolean_t svn_fs_fs__id_eq(const svn_fs_id_t *a,
svn_boolean_t svn_fs_fs__id_check_related(const svn_fs_id_t *a,
const svn_fs_id_t *b);
-/* Return 0 if A and B are equal, 1 if they are related, -1 otherwise. */
-int svn_fs_fs__id_compare(const svn_fs_id_t *a,
- const svn_fs_id_t *b);
+/* Return the noderev relationship between A and B. */
+svn_fs_node_relation_t svn_fs_fs__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return 0 if A and B are equal, 1 if A is "greater than" B, -1 otherwise. */
+int svn_fs_fs__id_part_compare(const svn_fs_fs__id_part_t *a,
+ const svn_fs_fs__id_part_t *b);
+
+/* Create the txn root ID for transaction TXN_ID. Allocate it in POOL. */
+svn_fs_id_t *svn_fs_fs__id_txn_create_root(const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Create the root ID for REVISION (logical addressing node only).
+ Allocate it in POOL. */
+svn_fs_id_t *svn_fs_fs__id_create_root(const svn_revnum_t revision,
+ apr_pool_t *pool);
/* Create an ID within a transaction based on NODE_ID, COPY_ID, and
TXN_ID, allocated in POOL. */
-svn_fs_id_t *svn_fs_fs__id_txn_create(const char *node_id,
- const char *copy_id,
- const char *txn_id,
+svn_fs_id_t *svn_fs_fs__id_txn_create(const svn_fs_fs__id_part_t *node_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool);
-/* Create a permanent ID based on NODE_ID, COPY_ID, REV, and OFFSET,
+/* Create a permanent ID based on NODE_ID, COPY_ID and REV_ITEM,
allocated in POOL. */
-svn_fs_id_t *svn_fs_fs__id_rev_create(const char *node_id,
- const char *copy_id,
- svn_revnum_t rev,
- apr_off_t offset,
+svn_fs_id_t *svn_fs_fs__id_rev_create(const svn_fs_fs__id_part_t *node_id,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *rev_item,
apr_pool_t *pool);
/* Return a copy of ID, allocated from POOL. */
svn_fs_id_t *svn_fs_fs__id_copy(const svn_fs_id_t *id,
apr_pool_t *pool);
-/* Return an ID resulting from parsing the string DATA (with length
- LEN), or NULL if DATA is an invalid ID string. */
-svn_fs_id_t *svn_fs_fs__id_parse(const char *data,
- apr_size_t len,
- apr_pool_t *pool);
+/* Return an ID in *ID_P resulting from parsing the string DATA, or an error
+ if DATA is an invalid ID string. *DATA will be modified / invalidated by
+ this call. */
+svn_error_t *
+svn_fs_fs__id_parse(const svn_fs_id_t **id_p,
+ char *data,
+ apr_pool_t *pool);
/* (de-)serialization support*/
diff --git a/subversion/libsvn_fs_fs/index.c b/subversion/libsvn_fs_fs/index.c
new file mode 100644
index 0000000..a669558
--- /dev/null
+++ b/subversion/libsvn_fs_fs/index.c
@@ -0,0 +1,3470 @@
+/* index.c indexing support for FSFS support
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_temp_serializer.h"
+
+#include "index.h"
+#include "pack.h"
+#include "temp_serializer.h"
+#include "util.h"
+#include "fs_fs.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+/* maximum length of a uint64 in an 7/8b encoding */
+#define ENCODED_INT_LENGTH 10
+
+/* APR is missing an APR_OFF_T_MAX. So, define one. We will use it to
+ * limit file offsets stored in the indexes.
+ *
+ * We assume that everything shorter than 64 bits, it is at least 32 bits.
+ * We also assume that the type is always signed meaning we only have an
+ * effective positive range of 63 or 31 bits, respectively.
+ */
+static
+const apr_uint64_t off_t_max = (sizeof(apr_off_t) == sizeof(apr_int64_t))
+ ? APR_INT64_MAX
+ : APR_INT32_MAX;
+
+/* We store P2L proto-index entries as 6 values, 64 bits each on disk.
+ * See also svn_fs_fs__p2l_proto_index_add_entry().
+ */
+#define P2L_PROTO_INDEX_ENTRY_SIZE (6 * sizeof(apr_uint64_t))
+
+/* We put this string in front of the L2P index header. */
+#define L2P_STREAM_PREFIX "L2P-INDEX\n"
+
+/* We put this string in front of the P2L index header. */
+#define P2L_STREAM_PREFIX "P2L-INDEX\n"
+
+/* Size of the buffer that will fit the index header prefixes. */
+#define STREAM_PREFIX_LEN MAX(sizeof(L2P_STREAM_PREFIX), \
+ sizeof(P2L_STREAM_PREFIX))
+
+/* Page tables in the log-to-phys index file exclusively contain entries
+ * of this type to describe position and size of a given page.
+ */
+typedef struct l2p_page_table_entry_t
+{
+ /* global offset on the page within the index file */
+ apr_uint64_t offset;
+
+ /* number of mapping entries in that page */
+ apr_uint32_t entry_count;
+
+ /* size of the page on disk (in the index file) */
+ apr_uint32_t size;
+} l2p_page_table_entry_t;
+
+/* Master run-time data structure of an log-to-phys index. It contains
+ * the page tables of every revision covered by that index - but not the
+ * pages themselves.
+ */
+typedef struct l2p_header_t
+{
+ /* first revision covered by this index */
+ svn_revnum_t first_revision;
+
+ /* number of revisions covered */
+ apr_size_t revision_count;
+
+ /* (max) number of entries per page */
+ apr_uint32_t page_size;
+
+ /* indexes into PAGE_TABLE that mark the first page of the respective
+ * revision. PAGE_TABLE_INDEX[REVISION_COUNT] points to the end of
+ * PAGE_TABLE.
+ */
+ apr_size_t * page_table_index;
+
+ /* Page table covering all pages in the index */
+ l2p_page_table_entry_t * page_table;
+} l2p_header_t;
+
+/* Run-time data structure containing a single log-to-phys index page.
+ */
+typedef struct l2p_page_t
+{
+ /* number of entries in the OFFSETS array */
+ apr_uint32_t entry_count;
+
+ /* global file offsets (item index is the array index) within the
+ * packed or non-packed rev file. Offset will be -1 for unused /
+ * invalid item index values. */
+ apr_uint64_t *offsets;
+} l2p_page_t;
+
+/* All of the log-to-phys proto index file consist of entries of this type.
+ */
+typedef struct l2p_proto_entry_t
+{
+ /* phys offset + 1 of the data container. 0 for "new revision" entries. */
+ apr_uint64_t offset;
+
+ /* corresponding item index. 0 for "new revision" entries. */
+ apr_uint64_t item_index;
+} l2p_proto_entry_t;
+
+/* Master run-time data structure of an phys-to-log index. It contains
+ * an array with one offset value for each rev file cluster.
+ */
+typedef struct p2l_header_t
+{
+ /* first revision covered by the index (and rev file) */
+ svn_revnum_t first_revision;
+
+ /* number of bytes in the rev files covered by each p2l page */
+ apr_uint64_t page_size;
+
+ /* number of pages / clusters in that rev file */
+ apr_size_t page_count;
+
+ /* number of bytes in the rev file */
+ apr_uint64_t file_size;
+
+ /* offsets of the pages / cluster descriptions within the index file */
+ apr_off_t *offsets;
+} p2l_header_t;
+
+/*
+ * packed stream
+ *
+ * This is a utility object that will read files containing 7b/8b encoded
+ * unsigned integers. It decodes them in batches to minimize overhead
+ * and supports random access to random file locations.
+ */
+
+/* How many numbers we will pre-fetch and buffer in a packed number stream.
+ */
+enum { MAX_NUMBER_PREFETCH = 64 };
+
+/* Prefetched number entry in a packed number stream.
+ */
+typedef struct value_position_pair_t
+{
+ /* prefetched number */
+ apr_uint64_t value;
+
+ /* number of bytes read, *including* this number, since the buffer start */
+ apr_size_t total_len;
+} value_position_pair_t;
+
+/* State of a prefetching packed number stream. It will read compressed
+ * index data efficiently and present it as a series of non-packed uint64.
+ */
+struct svn_fs_fs__packed_number_stream_t
+{
+ /* underlying data file containing the packed values */
+ apr_file_t *file;
+
+ /* Offset within FILE at which the stream data starts
+ * (i.e. which offset will reported as offset 0 by packed_stream_offset). */
+ apr_off_t stream_start;
+
+ /* First offset within FILE after the stream data.
+ * Attempts to read beyond this will cause an "Unexpected End Of Stream"
+ * error. */
+ apr_off_t stream_end;
+
+ /* number of used entries in BUFFER (starting at index 0) */
+ apr_size_t used;
+
+ /* index of the next number to read from the BUFFER (0 .. USED).
+ * If CURRENT == USED, we need to read more data upon get() */
+ apr_size_t current;
+
+ /* offset in FILE from which the first entry in BUFFER has been read */
+ apr_off_t start_offset;
+
+ /* offset in FILE from which the next number has to be read */
+ apr_off_t next_offset;
+
+ /* read the file in chunks of this size */
+ apr_size_t block_size;
+
+ /* pool to be used for file ops etc. */
+ apr_pool_t *pool;
+
+ /* buffer for prefetched values */
+ value_position_pair_t buffer[MAX_NUMBER_PREFETCH];
+};
+
+/* Return an svn_error_t * object for error ERR on STREAM with the given
+ * MESSAGE string. The latter must have a placeholder for the index file
+ * name ("%s") and the current read offset (e.g. "0x%lx").
+ */
+static svn_error_t *
+stream_error_create(svn_fs_fs__packed_number_stream_t *stream,
+ apr_status_t err,
+ const char *message)
+{
+ const char *file_name;
+ apr_off_t offset;
+ SVN_ERR(svn_io_file_name_get(&file_name, stream->file,
+ stream->pool));
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, stream->file, stream->pool));
+
+ return svn_error_createf(err, NULL, message, file_name,
+ apr_psprintf(stream->pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)offset));
+}
+
+/* Read up to MAX_NUMBER_PREFETCH numbers from the STREAM->NEXT_OFFSET in
+ * STREAM->FILE and buffer them.
+ *
+ * We don't want GCC and others to inline this (infrequently called)
+ * function into packed_stream_get() because it prevents the latter from
+ * being inlined itself.
+ */
+SVN__PREVENT_INLINE
+static svn_error_t *
+packed_stream_read(svn_fs_fs__packed_number_stream_t *stream)
+{
+ unsigned char buffer[MAX_NUMBER_PREFETCH];
+ apr_size_t read = 0;
+ apr_size_t i;
+ value_position_pair_t *target;
+ apr_off_t block_start = 0;
+ apr_off_t block_left = 0;
+ apr_status_t err;
+
+ /* all buffered data will have been read starting here */
+ stream->start_offset = stream->next_offset;
+
+ /* packed numbers are usually not aligned to MAX_NUMBER_PREFETCH blocks,
+ * i.e. the last number has been incomplete (and not buffered in stream)
+ * and need to be re-read. Therefore, always correct the file pointer.
+ */
+ SVN_ERR(svn_io_file_aligned_seek(stream->file, stream->block_size,
+ &block_start, stream->next_offset,
+ stream->pool));
+
+ /* prefetch at least one number but, if feasible, don't cross block
+ * boundaries. This shall prevent jumping back and forth between two
+ * blocks because the extra data was not actually request _now_.
+ */
+ read = sizeof(buffer);
+ block_left = stream->block_size - (stream->next_offset - block_start);
+ if (block_left >= 10 && block_left < read)
+ read = (apr_size_t)block_left;
+
+ /* Don't read beyond the end of the file section that belongs to this
+ * index / stream. */
+ read = (apr_size_t)MIN(read, stream->stream_end - stream->next_offset);
+
+ err = apr_file_read(stream->file, buffer, &read);
+ if (err && !APR_STATUS_IS_EOF(err))
+ return stream_error_create(stream, err,
+ _("Can't read index file '%s' at offset 0x%s"));
+
+ /* if the last number is incomplete, trim it from the buffer */
+ while (read > 0 && buffer[read-1] >= 0x80)
+ --read;
+
+ /* we call read() only if get() requires more data. So, there must be
+ * at least *one* further number. */
+ if SVN__PREDICT_FALSE(read == 0)
+ return stream_error_create(stream, err,
+ _("Unexpected end of index file %s at offset 0x%s"));
+
+ /* parse file buffer and expand into stream buffer */
+ target = stream->buffer;
+ for (i = 0; i < read;)
+ {
+ if (buffer[i] < 0x80)
+ {
+ /* numbers < 128 are relatively frequent and particularly easy
+ * to decode. Give them special treatment. */
+ target->value = buffer[i];
+ ++i;
+ target->total_len = i;
+ ++target;
+ }
+ else
+ {
+ apr_uint64_t value = 0;
+ apr_uint64_t shift = 0;
+ while (buffer[i] >= 0x80)
+ {
+ value += ((apr_uint64_t)buffer[i] & 0x7f) << shift;
+ shift += 7;
+ ++i;
+ }
+
+ target->value = value + ((apr_uint64_t)buffer[i] << shift);
+ ++i;
+ target->total_len = i;
+ ++target;
+
+ /* let's catch corrupted data early. It would surely cause
+ * havoc further down the line. */
+ if SVN__PREDICT_FALSE(shift > 8 * sizeof(value))
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Corrupt index: number too large"));
+ }
+ }
+
+ /* update stream state */
+ stream->used = target - stream->buffer;
+ stream->next_offset = stream->start_offset + i;
+ stream->current = 0;
+
+ return SVN_NO_ERROR;
+}
+
+/* Create and open a packed number stream reading from offsets START to
+ * END in FILE and return it in *STREAM. Access the file in chunks of
+ * BLOCK_SIZE bytes. Expect the stream to be prefixed by STREAM_PREFIX.
+ * Allocate *STREAM in RESULT_POOL and use SCRATCH_POOL for temporaries.
+ */
+static svn_error_t *
+packed_stream_open(svn_fs_fs__packed_number_stream_t **stream,
+ apr_file_t *file,
+ apr_off_t start,
+ apr_off_t end,
+ const char *stream_prefix,
+ apr_size_t block_size,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char buffer[STREAM_PREFIX_LEN + 1] = { 0 };
+ apr_size_t len = strlen(stream_prefix);
+ svn_fs_fs__packed_number_stream_t *result;
+
+ /* If this is violated, we forgot to adjust STREAM_PREFIX_LEN after
+ * changing the index header prefixes. */
+ SVN_ERR_ASSERT(len < sizeof(buffer));
+
+ /* Read the header prefix and compare it with the expected prefix */
+ SVN_ERR(svn_io_file_aligned_seek(file, block_size, NULL, start,
+ scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(file, buffer, len, NULL, NULL,
+ scratch_pool));
+
+ if (strncmp(buffer, stream_prefix, len))
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Index stream header prefix mismatch.\n"
+ " expected: %s"
+ " found: %s"), stream_prefix, buffer);
+
+ /* Construct the actual stream object. */
+ result = apr_palloc(result_pool, sizeof(*result));
+
+ result->pool = result_pool;
+ result->file = file;
+ result->stream_start = start + len;
+ result->stream_end = end;
+
+ result->used = 0;
+ result->current = 0;
+ result->start_offset = result->stream_start;
+ result->next_offset = result->stream_start;
+ result->block_size = block_size;
+
+ *stream = result;
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * The forced inline is required for performance reasons: This is a very
+ * hot code path (called for every item we read) but e.g. GCC would rather
+ * chose to inline packed_stream_read() here, preventing packed_stream_get
+ * from being inlined itself.
+ */
+SVN__FORCE_INLINE
+static svn_error_t*
+packed_stream_get(apr_uint64_t *value,
+ svn_fs_fs__packed_number_stream_t *stream)
+{
+ if (stream->current == stream->used)
+ SVN_ERR(packed_stream_read(stream));
+
+ *value = stream->buffer[stream->current].value;
+ ++stream->current;
+
+ return SVN_NO_ERROR;
+}
+
+/* Navigate STREAM to packed stream offset OFFSET. There will be no checks
+ * whether the given OFFSET is valid.
+ */
+static void
+packed_stream_seek(svn_fs_fs__packed_number_stream_t *stream,
+ apr_off_t offset)
+{
+ apr_off_t file_offset = offset + stream->stream_start;
+
+ if ( stream->used == 0
+ || offset < stream->start_offset
+ || offset >= stream->next_offset)
+ {
+ /* outside buffered data. Next get() will read() from OFFSET. */
+ stream->start_offset = file_offset;
+ stream->next_offset = file_offset;
+ stream->current = 0;
+ stream->used = 0;
+ }
+ else
+ {
+ /* Find the suitable location in the stream buffer.
+ * Since our buffer is small, it is efficient enough to simply scan
+ * it for the desired position. */
+ apr_size_t i;
+ for (i = 0; i < stream->used; ++i)
+ if (stream->buffer[i].total_len > file_offset - stream->start_offset)
+ break;
+
+ stream->current = i;
+ }
+}
+
+/* Return the packed stream offset of at which the next number in the stream
+ * can be found.
+ */
+static apr_off_t
+packed_stream_offset(svn_fs_fs__packed_number_stream_t *stream)
+{
+ apr_off_t file_offset
+ = stream->current == 0
+ ? stream->start_offset
+ : stream->buffer[stream->current-1].total_len + stream->start_offset;
+
+ return file_offset - stream->stream_start;
+}
+
+/* Encode VALUE as 7/8b into P and return the number of bytes written.
+ * This will be used when _writing_ packed data. packed_stream_* is for
+ * read operations only.
+ */
+static apr_size_t
+encode_uint(unsigned char *p, apr_uint64_t value)
+{
+ unsigned char *start = p;
+ while (value >= 0x80)
+ {
+ *p = (unsigned char)((value % 0x80) + 0x80);
+ value /= 0x80;
+ ++p;
+ }
+
+ *p = (unsigned char)(value % 0x80);
+ return (p - start) + 1;
+}
+
+/* Encode VALUE as 7/8b into P and return the number of bytes written.
+ * This maps signed ints onto unsigned ones.
+ */
+static apr_size_t
+encode_int(unsigned char *p, apr_int64_t value)
+{
+ return encode_uint(p, (apr_uint64_t)(value < 0 ? -1 - 2*value : 2*value));
+}
+
+/* Append VALUE to STREAM in 7/8b encoding.
+ */
+static svn_error_t *
+stream_write_encoded(svn_stream_t *stream,
+ apr_uint64_t value)
+{
+ unsigned char encoded[ENCODED_INT_LENGTH];
+
+ apr_size_t len = encode_uint(encoded, value);
+ return svn_error_trace(svn_stream_write(stream, (char *)encoded, &len));
+}
+
+/* Map unsigned VALUE back to signed integer.
+ */
+static apr_int64_t
+decode_int(apr_uint64_t value)
+{
+ return (apr_int64_t)(value % 2 ? -1 - value / 2 : value / 2);
+}
+
+/* Write VALUE to the PROTO_INDEX file, using SCRATCH_POOL for temporary
+ * allocations.
+ *
+ * The point of this function is to ensure an architecture-independent
+ * proto-index file format. All data is written as unsigned 64 bits ints
+ * in little endian byte order. 64 bits is the largest portable integer
+ * we have and unsigned values have well-defined conversions in C.
+ */
+static svn_error_t *
+write_uint64_to_proto_index(apr_file_t *proto_index,
+ apr_uint64_t value,
+ apr_pool_t *scratch_pool)
+{
+ apr_byte_t buffer[sizeof(value)];
+ int i;
+ apr_size_t written;
+
+ /* Split VALUE into 8 bytes using LE ordering. */
+ for (i = 0; i < sizeof(buffer); ++i)
+ {
+ /* Unsigned conversions are well-defined ... */
+ buffer[i] = (apr_byte_t)value;
+ value >>= CHAR_BIT;
+ }
+
+ /* Write it all to disk. */
+ SVN_ERR(svn_io_file_write_full(proto_index, buffer, sizeof(buffer),
+ &written, scratch_pool));
+ SVN_ERR_ASSERT(written == sizeof(buffer));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read one unsigned 64 bit value from PROTO_INDEX file and return it in
+ * *VALUE_P. If EOF is NULL, error out when trying to read beyond EOF.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * This function is the inverse to write_uint64_to_proto_index (see there),
+ * reading the external LE byte order and convert it into host byte order.
+ */
+static svn_error_t *
+read_uint64_from_proto_index(apr_file_t *proto_index,
+ apr_uint64_t *value_p,
+ svn_boolean_t *eof,
+ apr_pool_t *scratch_pool)
+{
+ apr_byte_t buffer[sizeof(*value_p)];
+ apr_size_t read;
+
+ /* Read the full 8 bytes or our 64 bit value, unless we hit EOF.
+ * Assert that we never read partial values. */
+ SVN_ERR(svn_io_file_read_full2(proto_index, buffer, sizeof(buffer),
+ &read, eof, scratch_pool));
+ SVN_ERR_ASSERT((eof && *eof) || read == sizeof(buffer));
+
+ /* If we did not hit EOF, reconstruct the uint64 value and return it. */
+ if (!eof || !*eof)
+ {
+ int i;
+ apr_uint64_t value;
+
+ /* This could only overflow if CHAR_BIT had a value that is not
+ * a divisor of 64. */
+ value = 0;
+ for (i = sizeof(buffer) - 1; i >= 0; --i)
+ value = (value << CHAR_BIT) + buffer[i];
+
+ *value_p = value;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Convenience function similar to read_uint64_from_proto_index, but returns
+ * an uint32 value in VALUE_P. Return an error if the value does not fit.
+ */
+static svn_error_t *
+read_uint32_from_proto_index(apr_file_t *proto_index,
+ apr_uint32_t *value_p,
+ svn_boolean_t *eof,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t value;
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
+ scratch_pool));
+ if (!eof || !*eof)
+ {
+ if (value > APR_UINT32_MAX)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
+ _("UINT32 0x%s too large, max = 0x%s"),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ value),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)APR_UINT32_MAX));
+
+ /* This conversion is not lossy because the value can be represented
+ * in the target type. */
+ *value_p = (apr_uint32_t)value;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Convenience function similar to read_uint64_from_proto_index, but returns
+ * an off_t value in VALUE_P. Return an error if the value does not fit.
+ */
+static svn_error_t *
+read_off_t_from_proto_index(apr_file_t *proto_index,
+ apr_off_t *value_p,
+ svn_boolean_t *eof,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t value;
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
+ scratch_pool));
+ if (!eof || !*eof)
+ {
+ if (value > off_t_max)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
+ _("File offset 0x%s too large, max = 0x%s"),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ value),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ off_t_max));
+
+ /* Shortening conversion from unsigned to signed int is well-defined
+ * and not lossy in C because the value can be represented in the
+ * target type. */
+ *value_p = (apr_off_t)value;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * log-to-phys index
+ */
+
+/* Append ENTRY to log-to-phys PROTO_INDEX file.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+write_l2p_entry_to_proto_index(apr_file_t *proto_index,
+ l2p_proto_entry_t entry,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry.offset,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry.item_index,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read *ENTRY from log-to-phys PROTO_INDEX file and indicate end-of-file
+ * in *EOF, or error out in that case if EOF is NULL. *ENTRY is in an
+ * undefined state if an end-of-file occurred.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+read_l2p_entry_from_proto_index(apr_file_t *proto_index,
+ l2p_proto_entry_t *entry,
+ svn_boolean_t *eof,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->offset, eof,
+ scratch_pool));
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->item_index, eof,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the log-2-phys index page description for the l2p_page_entry_t
+ * array ENTRIES, starting with element START up to but not including END.
+ * Write the resulting representation into BUFFER. Use SCRATCH_POOL for
+ * temporary allocations.
+ */
+static svn_error_t *
+encode_l2p_page(apr_array_header_t *entries,
+ int start,
+ int end,
+ svn_spillbuf_t *buffer,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char encoded[ENCODED_INT_LENGTH];
+ int i;
+ const apr_uint64_t *values = (const apr_uint64_t *)entries->elts;
+ apr_uint64_t last_value = 0;
+
+ /* encode items */
+ for (i = start; i < end; ++i)
+ {
+ apr_int64_t diff = values[i] - last_value;
+ last_value = values[i];
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_int(encoded, diff), scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__l2p_proto_index_open(apr_file_t **proto_index,
+ const char *file_name,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
+ | APR_CREATE | APR_APPEND | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__l2p_proto_index_add_revision(apr_file_t *proto_index,
+ apr_pool_t *scratch_pool)
+{
+ l2p_proto_entry_t entry;
+ entry.offset = 0;
+ entry.item_index = 0;
+
+ return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_fs_fs__l2p_proto_index_add_entry(apr_file_t *proto_index,
+ apr_off_t offset,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool)
+{
+ l2p_proto_entry_t entry;
+
+ /* make sure the conversion to uint64 works */
+ SVN_ERR_ASSERT(offset >= -1);
+
+ /* we support offset '-1' as a "not used" indication */
+ entry.offset = (apr_uint64_t)offset + 1;
+
+ /* make sure we can use item_index as an array index when building the
+ * final index file */
+ SVN_ERR_ASSERT(item_index < UINT_MAX / 2);
+ entry.item_index = item_index;
+
+ return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_fs_fs__l2p_index_append(svn_checksum_t **checksum,
+ svn_fs_t *fs,
+ apr_file_t *index_file,
+ const char *proto_file_name,
+ svn_revnum_t revision,
+ apr_pool_t * result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *proto_index = NULL;
+ svn_stream_t *stream;
+ int i;
+ apr_uint64_t entry;
+ svn_boolean_t eof = FALSE;
+
+ int last_page_count = 0; /* total page count at the start of
+ the current revision */
+
+ /* temporary data structures that collect the data which will be moved
+ to the target file in a second step */
+ apr_pool_t *local_pool = svn_pool_create(scratch_pool);
+ apr_pool_t *iterpool = svn_pool_create(local_pool);
+ apr_array_header_t *page_counts
+ = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
+ apr_array_header_t *page_sizes
+ = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
+ apr_array_header_t *entry_counts
+ = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
+
+ /* collect the item offsets and sub-item value for the current revision */
+ apr_array_header_t *entries
+ = apr_array_make(local_pool, 256, sizeof(apr_uint64_t));
+
+ /* 64k blocks, spill after 16MB */
+ svn_spillbuf_t *buffer
+ = svn_spillbuf__create(0x10000, 0x1000000, local_pool);
+
+ /* Paranoia check that makes later casting to int32 safe.
+ * The current implementation is limited to 2G entries per page. */
+ if (ffd->l2p_page_size > APR_INT32_MAX)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("L2P index page size %s"
+ " exceeds current limit of 2G entries"),
+ apr_psprintf(local_pool, "%" APR_UINT64_T_FMT,
+ ffd->l2p_page_size));
+
+ /* start at the beginning of the source file */
+ SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
+ APR_READ | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+
+ /* process all entries until we fail due to EOF */
+ for (entry = 0; !eof; ++entry)
+ {
+ l2p_proto_entry_t proto_entry;
+
+ /* (attempt to) read the next entry from the source */
+ SVN_ERR(read_l2p_entry_from_proto_index(proto_index, &proto_entry,
+ &eof, local_pool));
+
+ /* handle new revision */
+ if ((entry > 0 && proto_entry.offset == 0) || eof)
+ {
+ /* dump entries, grouped into pages */
+
+ int entry_count = 0;
+ for (i = 0; i < entries->nelts; i += entry_count)
+ {
+ /* 1 page with up to L2P_PAGE_SIZE entries.
+ * fsfs.conf settings validation guarantees this to fit into
+ * our address space. */
+ apr_uint64_t last_buffer_size
+ = (apr_uint64_t)svn_spillbuf__get_size(buffer);
+
+ svn_pool_clear(iterpool);
+
+ entry_count = ffd->l2p_page_size < entries->nelts - i
+ ? (int)ffd->l2p_page_size
+ : entries->nelts - i;
+ SVN_ERR(encode_l2p_page(entries, i, i + entry_count,
+ buffer, iterpool));
+
+ APR_ARRAY_PUSH(entry_counts, apr_uint64_t) = entry_count;
+ APR_ARRAY_PUSH(page_sizes, apr_uint64_t)
+ = svn_spillbuf__get_size(buffer) - last_buffer_size;
+ }
+
+ apr_array_clear(entries);
+
+ /* store the number of pages in this revision */
+ APR_ARRAY_PUSH(page_counts, apr_uint64_t)
+ = page_sizes->nelts - last_page_count;
+
+ last_page_count = page_sizes->nelts;
+ }
+ else
+ {
+ int idx;
+
+ /* store the mapping in our array */
+ if (proto_entry.item_index > APR_INT32_MAX)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("Item index %s too large "
+ "in l2p proto index for revision %ld"),
+ apr_psprintf(local_pool, "%" APR_UINT64_T_FMT,
+ proto_entry.item_index),
+ revision + page_counts->nelts);
+
+ idx = (int)proto_entry.item_index;
+ while (idx >= entries->nelts)
+ APR_ARRAY_PUSH(entries, apr_uint64_t) = 0;
+
+ APR_ARRAY_IDX(entries, idx, apr_uint64_t) = proto_entry.offset;
+ }
+ }
+
+ /* close the source file */
+ SVN_ERR(svn_io_file_close(proto_index, local_pool));
+
+ /* Paranoia check that makes later casting to int32 safe.
+ * The current implementation is limited to 2G pages per index. */
+ if (page_counts->nelts > APR_INT32_MAX)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("L2P index page count %d"
+ " exceeds current limit of 2G pages"),
+ page_counts->nelts);
+
+ /* open target stream. */
+ stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
+ local_pool),
+ NULL, checksum, svn_checksum_md5, FALSE,
+ result_pool);
+
+
+ /* write header info */
+ SVN_ERR(svn_stream_puts(stream, L2P_STREAM_PREFIX));
+ SVN_ERR(stream_write_encoded(stream, revision));
+ SVN_ERR(stream_write_encoded(stream, ffd->l2p_page_size));
+ SVN_ERR(stream_write_encoded(stream, page_counts->nelts));
+ SVN_ERR(stream_write_encoded(stream, page_sizes->nelts));
+
+ /* write the revision table */
+ for (i = 0; i < page_counts->nelts; ++i)
+ {
+ apr_uint64_t value = APR_ARRAY_IDX(page_counts, i, apr_uint64_t);
+ SVN_ERR(stream_write_encoded(stream, value));
+ }
+
+ /* write the page table */
+ for (i = 0; i < page_sizes->nelts; ++i)
+ {
+ apr_uint64_t value = APR_ARRAY_IDX(page_sizes, i, apr_uint64_t);
+ SVN_ERR(stream_write_encoded(stream, value));
+ value = APR_ARRAY_IDX(entry_counts, i, apr_uint64_t);
+ SVN_ERR(stream_write_encoded(stream, value));
+ }
+
+ /* append page contents and implicitly close STREAM */
+ SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
+ stream, NULL, NULL, local_pool));
+
+ svn_pool_destroy(local_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* If REV_FILE->L2P_STREAM is NULL, create a new stream for the log-to-phys
+ * index for REVISION in FS and return it in REV_FILE.
+ */
+static svn_error_t *
+auto_open_l2p_index(svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision)
+{
+ if (rev_file->l2p_stream == NULL)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+ SVN_ERR(packed_stream_open(&rev_file->l2p_stream,
+ rev_file->file,
+ rev_file->l2p_offset,
+ rev_file->p2l_offset,
+ L2P_STREAM_PREFIX,
+ (apr_size_t)ffd->block_size,
+ rev_file->pool,
+ rev_file->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the header data structure of the log-to-phys index for REVISION
+ * in FS and return it in *HEADER, allocated in RESULT_POOL. Use REV_FILE
+ * to access on-disk data. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_l2p_header_body(l2p_header_t **header,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_uint64_t value;
+ apr_size_t i;
+ apr_size_t page, page_count;
+ apr_off_t offset;
+ l2p_header_t *result = apr_pcalloc(result_pool, sizeof(*result));
+ apr_size_t page_table_index;
+ svn_revnum_t next_rev;
+
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+
+ SVN_ERR(auto_open_l2p_index(rev_file, fs, revision));
+ packed_stream_seek(rev_file->l2p_stream, 0);
+
+ /* Read the table sizes. Check the data for plausibility and
+ * consistency with other bits. */
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ result->first_revision = (svn_revnum_t)value;
+ if (result->first_revision != rev_file->start_revision)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Index rev / pack file revision numbers do not match"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ result->page_size = (apr_uint32_t)value;
+ if (!result->page_size || (result->page_size & (result->page_size - 1)))
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("L2P index page size is not a power of two"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ result->revision_count = (int)value;
+ if ( result->revision_count != 1
+ && result->revision_count != (apr_uint64_t)ffd->max_files_per_dir)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Invalid number of revisions in L2P index"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ page_count = (apr_size_t)value;
+ if (page_count < result->revision_count)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Fewer L2P index pages than revisions"));
+ if (page_count > (rev_file->p2l_offset - rev_file->l2p_offset) / 2)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("L2P index page count implausibly large"));
+
+ next_rev = result->first_revision + (svn_revnum_t)result->revision_count;
+ if (result->first_revision > revision || next_rev <= revision)
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Corrupt L2P index for r%ld only covers r%ld:%ld"),
+ revision, result->first_revision, next_rev);
+
+ /* allocate the page tables */
+ result->page_table
+ = apr_pcalloc(result_pool, page_count * sizeof(*result->page_table));
+ result->page_table_index
+ = apr_pcalloc(result_pool, (result->revision_count + 1)
+ * sizeof(*result->page_table_index));
+
+ /* read per-revision page table sizes (i.e. number of pages per rev) */
+ page_table_index = 0;
+ result->page_table_index[0] = page_table_index;
+
+ for (i = 0; i < result->revision_count; ++i)
+ {
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ if (value == 0)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Revision with no L2P index pages"));
+
+ page_table_index += (apr_size_t)value;
+ if (page_table_index > page_count)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("L2P page table exceeded"));
+
+ result->page_table_index[i+1] = page_table_index;
+ }
+
+ if (page_table_index != page_count)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Revisions do not cover the full L2P index page table"));
+
+ /* read actual page tables */
+ for (page = 0; page < page_count; ++page)
+ {
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ if (value == 0)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Empty L2P index page"));
+
+ result->page_table[page].size = (apr_uint32_t)value;
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ if (value > result->page_size)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Page exceeds L2P index page size"));
+
+ result->page_table[page].entry_count = (apr_uint32_t)value;
+ }
+
+ /* correct the page description offsets */
+ offset = packed_stream_offset(rev_file->l2p_stream);
+ for (page = 0; page < page_count; ++page)
+ {
+ result->page_table[page].offset = offset;
+ offset += result->page_table[page].size;
+ }
+
+ /* return and cache the header */
+ *header = result;
+ SVN_ERR(svn_cache__set(ffd->l2p_header_cache, &key, result, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Data structure that describes which l2p page info shall be extracted
+ * from the cache and contains the fields that receive the result.
+ */
+typedef struct l2p_page_info_baton_t
+{
+ /* input data: we want the page covering (REVISION,ITEM_INDEX) */
+ svn_revnum_t revision;
+ apr_uint64_t item_index;
+
+ /* out data */
+ /* page location and size of the page within the l2p index file */
+ l2p_page_table_entry_t entry;
+
+ /* page number within the pages for REVISION (not l2p index global!) */
+ apr_uint32_t page_no;
+
+ /* offset of ITEM_INDEX within that page */
+ apr_uint32_t page_offset;
+
+ /* revision identifying the l2p index file, also the first rev in that */
+ svn_revnum_t first_revision;
+} l2p_page_info_baton_t;
+
+
+/* Utility function that copies the info requested by BATON->REVISION and
+ * BATON->ITEM_INDEX and from HEADER and PAGE_TABLE into the output fields
+ * of *BATON. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+l2p_page_info_copy(l2p_page_info_baton_t *baton,
+ const l2p_header_t *header,
+ const l2p_page_table_entry_t *page_table,
+ const apr_size_t *page_table_index,
+ apr_pool_t *scratch_pool)
+{
+ /* revision offset within the index file */
+ apr_size_t rel_revision = baton->revision - header->first_revision;
+ if (rel_revision >= header->revision_count)
+ return svn_error_createf(SVN_ERR_FS_INDEX_REVISION , NULL,
+ _("Revision %ld not covered by item index"),
+ baton->revision);
+
+ /* select the relevant page */
+ if (baton->item_index < header->page_size)
+ {
+ /* most revs fit well into a single page */
+ baton->page_offset = (apr_uint32_t)baton->item_index;
+ baton->page_no = 0;
+ baton->entry = page_table[page_table_index[rel_revision]];
+ }
+ else
+ {
+ const l2p_page_table_entry_t *first_entry;
+ const l2p_page_table_entry_t *last_entry;
+ apr_uint64_t max_item_index;
+
+ /* range of pages for this rev */
+ first_entry = page_table + page_table_index[rel_revision];
+ last_entry = page_table + page_table_index[rel_revision + 1];
+
+ /* do we hit a valid index page? */
+ max_item_index = (apr_uint64_t)header->page_size
+ * (last_entry - first_entry);
+ if (baton->item_index >= max_item_index)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("Item index %s exceeds l2p limit "
+ "of %s for revision %ld"),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_FMT,
+ baton->item_index),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_FMT,
+ max_item_index),
+ baton->revision);
+
+ /* all pages are of the same size and full, except for the last one */
+ baton->page_offset = (apr_uint32_t)(baton->item_index % header->page_size);
+ baton->page_no = (apr_uint32_t)(baton->item_index / header->page_size);
+ baton->entry = first_entry[baton->page_no];
+ }
+
+ baton->first_revision = header->first_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__partial_getter_func_t: copy the data requested in
+ * l2p_page_info_baton_t *BATON from l2p_header_t *DATA into the output
+ * fields in *BATON.
+ */
+static svn_error_t *
+l2p_page_info_access_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ /* resolve all pointer values of in-cache data */
+ const l2p_header_t *header = data;
+ const l2p_page_table_entry_t *page_table
+ = svn_temp_deserializer__ptr(header,
+ (const void *const *)&header->page_table);
+ const apr_size_t *page_table_index
+ = svn_temp_deserializer__ptr(header,
+ (const void *const *)&header->page_table_index);
+
+ /* copy the info */
+ return l2p_page_info_copy(baton, header, page_table, page_table_index,
+ result_pool);
+}
+
+/* Get the page info requested in *BATON from FS and set the output fields
+ * in *BATON. Use REV_FILE for on-disk file access.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_l2p_page_info(l2p_page_info_baton_t *baton,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ l2p_header_t *result;
+ svn_boolean_t is_cached = FALSE;
+ void *dummy = NULL;
+
+ /* try to find the info in the cache */
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+ SVN_ERR(svn_cache__get_partial((void**)&dummy, &is_cached,
+ ffd->l2p_header_cache, &key,
+ l2p_page_info_access_func, baton,
+ scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* read from disk, cache and copy the result */
+ SVN_ERR(get_l2p_header_body(&result, rev_file, fs, baton->revision,
+ scratch_pool, scratch_pool));
+ SVN_ERR(l2p_page_info_copy(baton, result, result->page_table,
+ result->page_table_index, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Data request structure used by l2p_page_table_access_func.
+ */
+typedef struct l2p_page_table_baton_t
+{
+ /* revision for which to read the page table */
+ svn_revnum_t revision;
+
+ /* page table entries (of type l2p_page_table_entry_t).
+ * Must be created by caller and will be filled by callee. */
+ apr_array_header_t *pages;
+} l2p_page_table_baton_t;
+
+/* Implement svn_cache__partial_getter_func_t: copy the data requested in
+ * l2p_page_baton_t *BATON from l2p_page_t *DATA into BATON->PAGES and *OUT.
+ */
+static svn_error_t *
+l2p_page_table_access_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ /* resolve in-cache pointers */
+ l2p_page_table_baton_t *table_baton = baton;
+ const l2p_header_t *header = (const l2p_header_t *)data;
+ const l2p_page_table_entry_t *page_table
+ = svn_temp_deserializer__ptr(header,
+ (const void *const *)&header->page_table);
+ const apr_size_t *page_table_index
+ = svn_temp_deserializer__ptr(header,
+ (const void *const *)&header->page_table_index);
+
+ /* copy the revision's page table into BATON */
+ apr_size_t rel_revision = table_baton->revision - header->first_revision;
+ if (rel_revision < header->revision_count)
+ {
+ const l2p_page_table_entry_t *entry
+ = page_table + page_table_index[rel_revision];
+ const l2p_page_table_entry_t *last_entry
+ = page_table + page_table_index[rel_revision + 1];
+
+ for (; entry < last_entry; ++entry)
+ APR_ARRAY_PUSH(table_baton->pages, l2p_page_table_entry_t)
+ = *entry;
+ }
+
+ /* set output as a courtesy to the caller */
+ *out = table_baton->pages;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the l2p index page table for REVISION in FS from cache and return
+ * it in PAGES. The later must be provided by the caller (and can be
+ * re-used); existing entries will be removed before writing the result.
+ * If the data cannot be found in the cache, the result will be empty
+ * (it never can be empty for a valid REVISION if the data is cached).
+ * Use the info from REV_FILE to determine pack / rev file properties.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_l2p_page_table(apr_array_header_t *pages,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t is_cached = FALSE;
+ l2p_page_table_baton_t baton;
+
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+
+ apr_array_clear(pages);
+ baton.revision = revision;
+ baton.pages = pages;
+ SVN_ERR(svn_cache__get_partial((void**)&pages, &is_cached,
+ ffd->l2p_header_cache, &key,
+ l2p_page_table_access_func, &baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* From the log-to-phys index file starting at START_REVISION in FS, read
+ * the mapping page identified by TABLE_ENTRY and return it in *PAGE.
+ * Use REV_FILE to access on-disk files.
+ * Use RESULT_POOL for allocations.
+ */
+static svn_error_t *
+get_l2p_page(l2p_page_t **page,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t start_revision,
+ l2p_page_table_entry_t *table_entry,
+ apr_pool_t *result_pool)
+{
+ apr_uint32_t i;
+ l2p_page_t *result = apr_pcalloc(result_pool, sizeof(*result));
+ apr_uint64_t last_value = 0;
+
+ /* open index file and select page */
+ SVN_ERR(auto_open_l2p_index(rev_file, fs, start_revision));
+ packed_stream_seek(rev_file->l2p_stream, table_entry->offset);
+
+ /* initialize the page content */
+ result->entry_count = table_entry->entry_count;
+ result->offsets = apr_pcalloc(result_pool, result->entry_count
+ * sizeof(*result->offsets));
+
+ /* read all page entries (offsets in rev file and container sub-items) */
+ for (i = 0; i < result->entry_count; ++i)
+ {
+ apr_uint64_t value = 0;
+ SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
+ last_value += decode_int(value);
+ result->offsets[i] = last_value - 1;
+ }
+
+ /* After reading all page entries, the read cursor must have moved by
+ * TABLE_ENTRY->SIZE bytes. */
+ if ( packed_stream_offset(rev_file->l2p_stream)
+ != table_entry->offset + table_entry->size)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("L2P actual page size does not match page table value."));
+
+ *page = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility function. Read the l2p index pages for REVISION in FS from
+ * REV_FILE and put them into the cache. Skip page number EXLCUDED_PAGE_NO
+ * (use -1 for 'skip none') and pages outside the MIN_OFFSET, MAX_OFFSET
+ * range in the l2p index file. The index is being identified by
+ * FIRST_REVISION. PAGES is a scratch container provided by the caller.
+ * SCRATCH_POOL is used for temporary allocations.
+ *
+ * This function may be a no-op if the header cache lookup fails / misses.
+ */
+static svn_error_t *
+prefetch_l2p_pages(svn_boolean_t *end,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t first_revision,
+ svn_revnum_t revision,
+ apr_array_header_t *pages,
+ int exlcuded_page_no,
+ apr_off_t min_offset,
+ apr_off_t max_offset,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int i;
+ apr_pool_t *iterpool;
+ svn_fs_fs__page_cache_key_t key = { 0 };
+
+ /* Parameter check. */
+ if (min_offset < 0)
+ min_offset = 0;
+
+ if (max_offset <= 0)
+ {
+ /* Nothing to do */
+ *end = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* get the page table for REVISION from cache */
+ *end = FALSE;
+ SVN_ERR(get_l2p_page_table(pages, fs, rev_file, revision, scratch_pool));
+ if (pages->nelts == 0 || rev_file->l2p_stream == NULL)
+ {
+ /* not found -> we can't continue without hitting the disk again */
+ *end = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* prefetch pages individually until all are done or we found one in
+ * the cache */
+ iterpool = svn_pool_create(scratch_pool);
+ assert(revision <= APR_UINT32_MAX);
+ key.revision = (apr_uint32_t)revision;
+ key.is_packed = rev_file->is_packed;
+
+ for (i = 0; i < pages->nelts && !*end; ++i)
+ {
+ svn_boolean_t is_cached;
+
+ l2p_page_table_entry_t *entry
+ = &APR_ARRAY_IDX(pages, i, l2p_page_table_entry_t);
+ svn_pool_clear(iterpool);
+
+ if (i == exlcuded_page_no)
+ continue;
+
+ /* skip pages outside the specified index file range */
+ if ( entry->offset < (apr_uint64_t)min_offset
+ || entry->offset + entry->size > (apr_uint64_t)max_offset)
+ {
+ *end = TRUE;
+ continue;
+ }
+
+ /* page already in cache? */
+ key.page = i;
+ SVN_ERR(svn_cache__has_key(&is_cached, ffd->l2p_page_cache,
+ &key, iterpool));
+ if (!is_cached)
+ {
+ /* no in cache -> read from stream (data already buffered in APR)
+ * and cache the result */
+ l2p_page_t *page = NULL;
+ SVN_ERR(get_l2p_page(&page, rev_file, fs, first_revision, entry,
+ iterpool));
+
+ SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Request data structure for l2p_entry_access_func.
+ */
+typedef struct l2p_entry_baton_t
+{
+ /* in data */
+ /* revision. Used for error messages only */
+ svn_revnum_t revision;
+
+ /* item index to look up. Used for error messages only */
+ apr_uint64_t item_index;
+
+ /* offset within the cached page */
+ apr_uint32_t page_offset;
+
+ /* out data */
+ /* absolute item or container offset in rev / pack file */
+ apr_uint64_t offset;
+} l2p_entry_baton_t;
+
+/* Return the rev / pack file offset of the item at BATON->PAGE_OFFSET in
+ * OFFSETS of PAGE and write it to *OFFSET.
+ */
+static svn_error_t *
+l2p_page_get_entry(l2p_entry_baton_t *baton,
+ const l2p_page_t *page,
+ const apr_uint64_t *offsets,
+ apr_pool_t *scratch_pool)
+{
+ /* overflow check */
+ if (page->entry_count <= baton->page_offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("Item index %s"
+ " too large in revision %ld"),
+ apr_psprintf(scratch_pool, "%" APR_UINT64_T_FMT,
+ baton->item_index),
+ baton->revision);
+
+ /* return the result */
+ baton->offset = offsets[baton->page_offset];
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__partial_getter_func_t: copy the data requested in
+ * l2p_entry_baton_t *BATON from l2p_page_t *DATA into BATON->OFFSET.
+ * *OUT remains unchanged.
+ */
+static svn_error_t *
+l2p_entry_access_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ /* resolve all in-cache pointers */
+ const l2p_page_t *page = data;
+ const apr_uint64_t *offsets
+ = svn_temp_deserializer__ptr(page, (const void *const *)&page->offsets);
+
+ /* return the requested data */
+ return l2p_page_get_entry(baton, page, offsets, result_pool);
+}
+
+/* Using the log-to-phys indexes in FS, find the absolute offset in the
+ * rev file for (REVISION, ITEM_INDEX) and return it in *OFFSET.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+l2p_index_lookup(apr_off_t *offset,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ l2p_page_info_baton_t info_baton;
+ l2p_entry_baton_t page_baton;
+ l2p_page_t *page = NULL;
+ svn_fs_fs__page_cache_key_t key = { 0 };
+ svn_boolean_t is_cached = FALSE;
+ void *dummy = NULL;
+
+ /* read index master data structure and extract the info required to
+ * access the l2p index page for (REVISION,ITEM_INDEX)*/
+ info_baton.revision = revision;
+ info_baton.item_index = item_index;
+ SVN_ERR(get_l2p_page_info(&info_baton, rev_file, fs, scratch_pool));
+
+ /* try to find the page in the cache and get the OFFSET from it */
+ page_baton.revision = revision;
+ page_baton.item_index = item_index;
+ page_baton.page_offset = info_baton.page_offset;
+
+ assert(revision <= APR_UINT32_MAX);
+ key.revision = (apr_uint32_t)revision;
+ key.is_packed = svn_fs_fs__is_packed_rev(fs, revision);
+ key.page = info_baton.page_no;
+
+ SVN_ERR(svn_cache__get_partial(&dummy, &is_cached,
+ ffd->l2p_page_cache, &key,
+ l2p_entry_access_func, &page_baton,
+ scratch_pool));
+
+ if (!is_cached)
+ {
+ /* we need to read the info from disk (might already be in the
+ * APR file buffer, though) */
+ apr_array_header_t *pages;
+ svn_revnum_t prefetch_revision;
+ svn_revnum_t last_revision
+ = info_baton.first_revision
+ + (key.is_packed ? ffd->max_files_per_dir : 1);
+ svn_boolean_t end;
+ apr_off_t max_offset
+ = APR_ALIGN(info_baton.entry.offset + info_baton.entry.size,
+ ffd->block_size);
+ apr_off_t min_offset = max_offset - ffd->block_size;
+
+ /* read the relevant page */
+ SVN_ERR(get_l2p_page(&page, rev_file, fs, info_baton.first_revision,
+ &info_baton.entry, scratch_pool));
+
+ /* cache the page and extract the result we need */
+ SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page, scratch_pool));
+ SVN_ERR(l2p_page_get_entry(&page_baton, page, page->offsets,
+ scratch_pool));
+
+ if (ffd->use_block_read)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* prefetch pages from following and preceding revisions */
+ pages = apr_array_make(scratch_pool, 16,
+ sizeof(l2p_page_table_entry_t));
+ end = FALSE;
+ for (prefetch_revision = revision;
+ prefetch_revision < last_revision && !end;
+ ++prefetch_revision)
+ {
+ int excluded_page_no = prefetch_revision == revision
+ ? info_baton.page_no
+ : -1;
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
+ info_baton.first_revision,
+ prefetch_revision, pages,
+ excluded_page_no, min_offset,
+ max_offset, iterpool));
+ }
+
+ end = FALSE;
+ for (prefetch_revision = revision-1;
+ prefetch_revision >= info_baton.first_revision && !end;
+ --prefetch_revision)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
+ info_baton.first_revision,
+ prefetch_revision, pages, -1,
+ min_offset, max_offset, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ }
+
+ *offset = page_baton.offset;
+
+ return SVN_NO_ERROR;
+}
+
+/* Using the log-to-phys proto index in transaction TXN_ID in FS, find the
+ * absolute offset in the proto rev file for the given ITEM_INDEX and return
+ * it in *OFFSET. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+l2p_proto_index_lookup(apr_off_t *offset,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t eof = FALSE;
+ apr_file_t *file = NULL;
+ SVN_ERR(svn_io_file_open(&file,
+ svn_fs_fs__path_l2p_proto_index(fs, txn_id,
+ scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ scratch_pool));
+
+ /* process all entries until we fail due to EOF */
+ *offset = -1;
+ while (!eof)
+ {
+ l2p_proto_entry_t entry;
+
+ /* (attempt to) read the next entry from the source */
+ SVN_ERR(read_l2p_entry_from_proto_index(file, &entry, &eof,
+ scratch_pool));
+
+ /* handle new revision */
+ if (!eof && entry.item_index == item_index)
+ {
+ *offset = (apr_off_t)entry.offset - 1;
+ break;
+ }
+ }
+
+ SVN_ERR(svn_io_file_close(file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the log-to-phys header info of the index covering REVISION from FS
+ * and return it in *HEADER. REV_FILE provides the pack / rev status.
+ * Allocate *HEADER in RESULT_POOL, use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static svn_error_t *
+get_l2p_header(l2p_header_t **header,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t is_cached = FALSE;
+
+ /* first, try cache lookop */
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+ SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->l2p_header_cache,
+ &key, result_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* read from disk and cache the result */
+ SVN_ERR(get_l2p_header_body(header, rev_file, fs, revision, result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__l2p_get_max_ids(apr_array_header_t **max_ids,
+ svn_fs_t *fs,
+ svn_revnum_t start_rev,
+ apr_size_t count,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ l2p_header_t *header = NULL;
+ svn_revnum_t revision;
+ svn_revnum_t last_rev = (svn_revnum_t)(start_rev + count);
+ svn_fs_fs__revision_file_t *rev_file;
+ apr_pool_t *header_pool = svn_pool_create(scratch_pool);
+
+ /* read index master data structure for the index covering START_REV */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start_rev,
+ header_pool, header_pool));
+ SVN_ERR(get_l2p_header(&header, rev_file, fs, start_rev, header_pool,
+ header_pool));
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ /* Determine the length of the item index list for each rev.
+ * Read new index headers as required. */
+ *max_ids = apr_array_make(result_pool, (int)count, sizeof(apr_uint64_t));
+ for (revision = start_rev; revision < last_rev; ++revision)
+ {
+ apr_uint64_t full_page_count;
+ apr_uint64_t item_count;
+ apr_size_t first_page_index, last_page_index;
+
+ if (revision >= header->first_revision + header->revision_count)
+ {
+ /* need to read the next index. Clear up memory used for the
+ * previous one. Note that intermittent pack runs do not change
+ * the number of items in a revision, i.e. there is no consistency
+ * issue here. */
+ svn_pool_clear(header_pool);
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision,
+ header_pool, header_pool));
+ SVN_ERR(get_l2p_header(&header, rev_file, fs, revision,
+ header_pool, header_pool));
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+ }
+
+ /* in a revision with N index pages, the first N-1 index pages are
+ * "full", i.e. contain HEADER->PAGE_SIZE entries */
+ first_page_index
+ = header->page_table_index[revision - header->first_revision];
+ last_page_index
+ = header->page_table_index[revision - header->first_revision + 1];
+ full_page_count = last_page_index - first_page_index - 1;
+ item_count = full_page_count * header->page_size
+ + header->page_table[last_page_index - 1].entry_count;
+
+ APR_ARRAY_PUSH(*max_ids, apr_uint64_t) = item_count;
+ }
+
+ svn_pool_destroy(header_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__item_offset(apr_off_t *absolute_position,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ if (txn_id)
+ {
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ /* the txn is going to produce a rev with logical addressing.
+ So, we need to get our info from the (proto) index file. */
+ SVN_ERR(l2p_proto_index_lookup(absolute_position, fs, txn_id,
+ item_index, scratch_pool));
+ }
+ else
+ {
+ /* for data in txns, item_index *is* the offset */
+ *absolute_position = item_index;
+ }
+ }
+ else if (svn_fs_fs__use_log_addressing(fs))
+ {
+ /* ordinary index lookup */
+ SVN_ERR(l2p_index_lookup(absolute_position, fs, rev_file, revision,
+ item_index, scratch_pool));
+ }
+ else if (rev_file->is_packed)
+ {
+ /* pack file with physical addressing */
+ apr_off_t rev_offset;
+ SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, revision,
+ scratch_pool));
+ *absolute_position = rev_offset + item_index;
+ }
+ else
+ {
+ /* for non-packed revs with physical addressing,
+ item_index *is* the offset */
+ *absolute_position = item_index;
+ }
+
+ return svn_error_trace(err);
+}
+
+/*
+ * phys-to-log index
+ */
+svn_error_t *
+svn_fs_fs__p2l_proto_index_open(apr_file_t **proto_index,
+ const char *file_name,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
+ | APR_CREATE | APR_APPEND | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__p2l_proto_index_add_entry(apr_file_t *proto_index,
+ const svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t revision;
+
+ /* Make sure all signed elements of ENTRY have non-negative values.
+ *
+ * For file offsets and sizes, this is a given as we use them to describe
+ * absolute positions and sizes. For revisions, SVN_INVALID_REVNUM is
+ * valid, hence we have to shift it by 1.
+ */
+ SVN_ERR_ASSERT(entry->offset >= 0);
+ SVN_ERR_ASSERT(entry->size >= 0);
+ SVN_ERR_ASSERT( entry->item.revision >= 0
+ || entry->item.revision == SVN_INVALID_REVNUM);
+
+ revision = entry->item.revision == SVN_INVALID_REVNUM
+ ? 0
+ : ((apr_uint64_t)entry->item.revision + 1);
+
+ /* Now, all values will nicely convert to uint64. */
+ /* Make sure to keep P2L_PROTO_INDEX_ENTRY_SIZE consistent with this: */
+
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry->offset,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry->size,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry->type,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry->fnv1_checksum,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, revision,
+ scratch_pool));
+ SVN_ERR(write_uint64_to_proto_index(proto_index, entry->item.number,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read *ENTRY from log-to-phys PROTO_INDEX file and indicate end-of-file
+ * in *EOF, or error out in that case if EOF is NULL. *ENTRY is in an
+ * undefined state if an end-of-file occurred.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+read_p2l_entry_from_proto_index(apr_file_t *proto_index,
+ svn_fs_fs__p2l_entry_t *entry,
+ svn_boolean_t *eof,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t revision;
+
+ SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->offset,
+ eof, scratch_pool));
+ SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->size,
+ eof, scratch_pool));
+ SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->type,
+ eof, scratch_pool));
+ SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->fnv1_checksum,
+ eof, scratch_pool));
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &revision,
+ eof, scratch_pool));
+ SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->item.number,
+ eof, scratch_pool));
+
+ /* Do the inverse REVSION number conversion (see
+ * svn_fs_fs__p2l_proto_index_add_entry), if we actually read a complete
+ * record.
+ */
+ if (!eof || !*eof)
+ {
+ /* Be careful with the arithmetics here (overflows and wrap-around): */
+ if (revision > 0 && revision - 1 > LONG_MAX)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
+ _("Revision 0x%s too large, max = 0x%s"),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ revision),
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)LONG_MAX));
+
+ /* Shortening conversion from unsigned to signed int is well-defined
+ * and not lossy in C because the value can be represented in the
+ * target type. Also, cast to 'long' instead of 'svn_revnum_t' here
+ * to provoke a compiler warning if those types should differ and we
+ * would need to change the overflow checking logic.
+ */
+ entry->item.revision = revision == 0
+ ? SVN_INVALID_REVNUM
+ : (long)(revision - 1);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__p2l_proto_index_next_offset(apr_off_t *next_offset,
+ apr_file_t *proto_index,
+ apr_pool_t *scratch_pool)
+{
+ apr_off_t offset = 0;
+
+ /* Empty index file? */
+ SVN_ERR(svn_io_file_seek(proto_index, APR_END, &offset, scratch_pool));
+ if (offset == 0)
+ {
+ *next_offset = 0;
+ }
+ else
+ {
+ /* At least one entry. Read last entry. */
+ svn_fs_fs__p2l_entry_t entry;
+ offset -= P2L_PROTO_INDEX_ENTRY_SIZE;
+
+ SVN_ERR(svn_io_file_seek(proto_index, APR_SET, &offset, scratch_pool));
+ SVN_ERR(read_p2l_entry_from_proto_index(proto_index, &entry,
+ NULL, scratch_pool));
+
+ /* Return next offset. */
+ *next_offset = entry.offset + entry.size;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__p2l_index_append(svn_checksum_t **checksum,
+ svn_fs_t *fs,
+ apr_file_t *index_file,
+ const char *proto_file_name,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_uint64_t page_size = ffd->p2l_page_size;
+ apr_file_t *proto_index = NULL;
+ svn_stream_t *stream;
+ int i;
+ svn_boolean_t eof = FALSE;
+ unsigned char encoded[ENCODED_INT_LENGTH];
+ svn_revnum_t last_revision = revision;
+ apr_uint64_t last_compound = 0;
+
+ apr_uint64_t last_entry_end = 0;
+ apr_uint64_t last_page_end = 0;
+ apr_uint64_t last_buffer_size = 0; /* byte offset in the spill buffer at
+ the begin of the current revision */
+ apr_uint64_t file_size = 0;
+
+ /* temporary data structures that collect the data which will be moved
+ to the target file in a second step */
+ apr_pool_t *local_pool = svn_pool_create(scratch_pool);
+ apr_array_header_t *table_sizes
+ = apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
+
+ /* 64k blocks, spill after 16MB */
+ svn_spillbuf_t *buffer
+ = svn_spillbuf__create(0x10000, 0x1000000, local_pool);
+
+ /* for loop temps ... */
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* start at the beginning of the source file */
+ SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
+ APR_READ | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+
+ /* process all entries until we fail due to EOF */
+ while (!eof)
+ {
+ svn_fs_fs__p2l_entry_t entry;
+ apr_uint64_t entry_end;
+ svn_boolean_t new_page = svn_spillbuf__get_size(buffer) == 0;
+ apr_uint64_t compound;
+ apr_int64_t rev_diff, compound_diff;
+
+ svn_pool_clear(iterpool);
+
+ /* (attempt to) read the next entry from the source */
+ SVN_ERR(read_p2l_entry_from_proto_index(proto_index, &entry,
+ &eof, iterpool));
+
+ /* "unused" (and usually non-existent) section to cover the offsets
+ at the end the of the last page. */
+ if (eof)
+ {
+ file_size = last_entry_end;
+
+ entry.offset = last_entry_end;
+ entry.size = APR_ALIGN(entry.offset, page_size) - entry.offset;
+ entry.type = SVN_FS_FS__ITEM_TYPE_UNUSED;
+ entry.fnv1_checksum = 0;
+ entry.item.revision = last_revision;
+ entry.item.number = 0;
+ }
+ else
+ {
+ /* fix-up items created when the txn's target rev was unknown */
+ if (entry.item.revision == SVN_INVALID_REVNUM)
+ entry.item.revision = revision;
+ }
+
+ /* end pages if entry is extending beyond their boundaries */
+ entry_end = entry.offset + entry.size;
+ while (entry_end - last_page_end > page_size)
+ {
+ apr_uint64_t buffer_size = svn_spillbuf__get_size(buffer);
+ APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
+ = buffer_size - last_buffer_size;
+
+ last_buffer_size = buffer_size;
+ last_page_end += page_size;
+ new_page = TRUE;
+ }
+
+ /* this entry starts a new table -> store its offset
+ (all following entries in the same table will store sizes only) */
+ if (new_page)
+ {
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_uint(encoded, entry.offset),
+ iterpool));
+ last_revision = revision;
+ last_compound = 0;
+ }
+
+ /* write simple item entry */
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_uint(encoded, entry.size),
+ iterpool));
+
+ rev_diff = entry.item.revision - last_revision;
+ last_revision = entry.item.revision;
+
+ compound = entry.item.number * 8 + entry.type;
+ compound_diff = compound - last_compound;
+ last_compound = compound;
+
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_int(encoded, compound_diff),
+ iterpool));
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_int(encoded, rev_diff),
+ iterpool));
+ SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
+ encode_uint(encoded, entry.fnv1_checksum),
+ iterpool));
+
+ last_entry_end = entry_end;
+ }
+
+ /* close the source file */
+ SVN_ERR(svn_io_file_close(proto_index, local_pool));
+
+ /* store length of last table */
+ APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
+ = svn_spillbuf__get_size(buffer) - last_buffer_size;
+
+ /* Open target stream. */
+ stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
+ local_pool),
+ NULL, checksum, svn_checksum_md5, FALSE,
+ result_pool);
+
+ /* write the start revision, file size and page size */
+ SVN_ERR(svn_stream_puts(stream, P2L_STREAM_PREFIX));
+ SVN_ERR(stream_write_encoded(stream, revision));
+ SVN_ERR(stream_write_encoded(stream, file_size));
+ SVN_ERR(stream_write_encoded(stream, page_size));
+
+ /* write the page table (actually, the sizes of each page description) */
+ SVN_ERR(stream_write_encoded(stream, table_sizes->nelts));
+ for (i = 0; i < table_sizes->nelts; ++i)
+ {
+ apr_uint64_t value = APR_ARRAY_IDX(table_sizes, i, apr_uint64_t);
+ SVN_ERR(stream_write_encoded(stream, value));
+ }
+
+ /* append page contents and implicitly close STREAM */
+ SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
+ stream, NULL, NULL, local_pool));
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(local_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* If REV_FILE->P2L_STREAM is NULL, create a new stream for the phys-to-log
+ * index for REVISION in FS using the rev / pack file provided by REV_FILE.
+ */
+static svn_error_t *
+auto_open_p2l_index(svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision)
+{
+ if (rev_file->p2l_stream == NULL)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+ SVN_ERR(packed_stream_open(&rev_file->p2l_stream,
+ rev_file->file,
+ rev_file->p2l_offset,
+ rev_file->footer_offset,
+ P2L_STREAM_PREFIX,
+ (apr_size_t)ffd->block_size,
+ rev_file->pool,
+ rev_file->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read the header data structure of the phys-to-log index for REVISION in
+ * FS and return it in *HEADER, allocated in RESULT_POOL. Use REV_FILE to
+ * access on-disk data. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_p2l_header(p2l_header_t **header,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_uint64_t value;
+ apr_size_t i;
+ apr_off_t offset;
+ p2l_header_t *result;
+ svn_boolean_t is_cached = FALSE;
+
+ /* look for the header data in our cache */
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+
+ SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->p2l_header_cache,
+ &key, result_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* not found -> must read it from disk.
+ * Open index file or position read pointer to the begin of the file */
+ if (rev_file->p2l_stream == NULL)
+ SVN_ERR(auto_open_p2l_index(rev_file, fs, rev_file->start_revision));
+ else
+ packed_stream_seek(rev_file->p2l_stream, 0);
+
+ /* allocate result data structure */
+ result = apr_pcalloc(result_pool, sizeof(*result));
+
+ /* Read table sizes, check them for plausibility and allocate page array. */
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ result->first_revision = (svn_revnum_t)value;
+ if (result->first_revision != rev_file->start_revision)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Index rev / pack file revision numbers do not match"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ result->file_size = value;
+ if (result->file_size != (apr_uint64_t)rev_file->l2p_offset)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Index offset and rev / pack file size do not match"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ result->page_size = value;
+ if (!result->page_size || (result->page_size & (result->page_size - 1)))
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("P2L index page size is not a power of two"));
+
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ result->page_count = (apr_size_t)value;
+ if (result->page_count != (result->file_size - 1) / result->page_size + 1)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("P2L page count does not match rev / pack file size"));
+
+ result->offsets
+ = apr_pcalloc(result_pool, (result->page_count + 1) * sizeof(*result->offsets));
+
+ /* read page sizes and derive page description offsets from them */
+ result->offsets[0] = 0;
+ for (i = 0; i < result->page_count; ++i)
+ {
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ result->offsets[i+1] = result->offsets[i] + (apr_off_t)value;
+ }
+
+ /* correct the offset values */
+ offset = packed_stream_offset(rev_file->p2l_stream);
+ for (i = 0; i <= result->page_count; ++i)
+ result->offsets[i] += offset;
+
+ /* cache the header data */
+ SVN_ERR(svn_cache__set(ffd->p2l_header_cache, &key, result, scratch_pool));
+
+ /* return the result */
+ *header = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* Data structure that describes which p2l page info shall be extracted
+ * from the cache and contains the fields that receive the result.
+ */
+typedef struct p2l_page_info_baton_t
+{
+ /* input variables */
+ /* revision identifying the index file */
+ svn_revnum_t revision;
+
+ /* offset within the page in rev / pack file */
+ apr_off_t offset;
+
+ /* output variables */
+ /* page containing OFFSET */
+ apr_size_t page_no;
+
+ /* first revision in this p2l index */
+ svn_revnum_t first_revision;
+
+ /* offset within the p2l index file describing this page */
+ apr_off_t start_offset;
+
+ /* offset within the p2l index file describing the following page */
+ apr_off_t next_offset;
+
+ /* PAGE_NO * PAGE_SIZE (if <= OFFSET) */
+ apr_off_t page_start;
+
+ /* total number of pages indexed */
+ apr_size_t page_count;
+
+ /* size of each page in pack / rev file */
+ apr_uint64_t page_size;
+} p2l_page_info_baton_t;
+
+/* From HEADER and the list of all OFFSETS, fill BATON with the page info
+ * requested by BATON->OFFSET.
+ */
+static void
+p2l_page_info_copy(p2l_page_info_baton_t *baton,
+ const p2l_header_t *header,
+ const apr_off_t *offsets)
+{
+ /* if the requested offset is out of bounds, return info for
+ * a zero-sized empty page right behind the last page.
+ */
+ if (baton->offset / header->page_size < header->page_count)
+ {
+ /* This cast is safe because the value is < header->page_count. */
+ baton->page_no = (apr_size_t)(baton->offset / header->page_size);
+ baton->start_offset = offsets[baton->page_no];
+ baton->next_offset = offsets[baton->page_no + 1];
+ baton->page_size = header->page_size;
+ }
+ else
+ {
+ /* Beyond the last page. */
+ baton->page_no = header->page_count;
+ baton->start_offset = offsets[baton->page_no];
+ baton->next_offset = offsets[baton->page_no];
+ baton->page_size = 0;
+ }
+
+ baton->first_revision = header->first_revision;
+ baton->page_start = (apr_off_t)(header->page_size * baton->page_no);
+ baton->page_count = header->page_count;
+}
+
+/* Implement svn_cache__partial_getter_func_t: extract the p2l page info
+ * requested by BATON and return it in BATON.
+ */
+static svn_error_t *
+p2l_page_info_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ /* all the pointers to cached data we need */
+ const p2l_header_t *header = data;
+ const apr_off_t *offsets
+ = svn_temp_deserializer__ptr(header,
+ (const void *const *)&header->offsets);
+
+ /* copy data from cache to BATON */
+ p2l_page_info_copy(baton, header, offsets);
+ return SVN_NO_ERROR;
+}
+
+/* Read the header data structure of the phys-to-log index for revision
+ * BATON->REVISION in FS. Return in *BATON all info relevant to read the
+ * index page for the rev / pack file offset BATON->OFFSET. Use REV_FILE
+ * to access on-disk data. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_p2l_page_info(p2l_page_info_baton_t *baton,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ p2l_header_t *header;
+ svn_boolean_t is_cached = FALSE;
+ void *dummy = NULL;
+
+ /* look for the header data in our cache */
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+
+ SVN_ERR(svn_cache__get_partial(&dummy, &is_cached, ffd->p2l_header_cache,
+ &key, p2l_page_info_func, baton,
+ scratch_pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_p2l_header(&header, rev_file, fs, baton->revision,
+ scratch_pool, scratch_pool));
+
+ /* copy the requested info into *BATON */
+ p2l_page_info_copy(baton, header, header->offsets);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a mapping entry from the phys-to-log index STREAM and append it to
+ * RESULT. *ITEM_INDEX contains the phys offset for the entry and will
+ * be moved forward by the size of entry.
+ */
+static svn_error_t *
+read_entry(svn_fs_fs__packed_number_stream_t *stream,
+ apr_off_t *item_offset,
+ svn_revnum_t *last_revision,
+ apr_uint64_t *last_compound,
+ apr_array_header_t *result)
+{
+ apr_uint64_t value;
+
+ svn_fs_fs__p2l_entry_t entry;
+
+ entry.offset = *item_offset;
+ SVN_ERR(packed_stream_get(&value, stream));
+ entry.size = (apr_off_t)value;
+
+ SVN_ERR(packed_stream_get(&value, stream));
+ *last_compound += decode_int(value);
+
+ entry.type = *last_compound & 7;
+ entry.item.number = *last_compound / 8;
+
+ /* Verify item type. */
+ if (entry.type > SVN_FS_FS__ITEM_TYPE_CHANGES)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Invalid item type in P2L index"));
+ if ( entry.type == SVN_FS_FS__ITEM_TYPE_CHANGES
+ && entry.item.number != SVN_FS_FS__ITEM_INDEX_CHANGES)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Changed path list must have item number 1"));
+
+ SVN_ERR(packed_stream_get(&value, stream));
+ *last_revision += (svn_revnum_t)decode_int(value);
+ entry.item.revision = *last_revision;
+
+ SVN_ERR(packed_stream_get(&value, stream));
+ entry.fnv1_checksum = (apr_uint32_t)value;
+
+ /* Truncating the checksum to 32 bits may have hidden random data in the
+ * unused extra bits of the on-disk representation (7/8 bit representation
+ * uses 5 bytes on disk for the 32 bit value, leaving 3 bits unused). */
+ if (value > APR_UINT32_MAX)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Invalid FNV1 checksum in P2L index"));
+
+ /* Some of the index data for empty rev / pack file sections will not be
+ * used during normal operation. Thus, we have strict rules for the
+ * contents of those unused fields. */
+ if (entry.type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ if ( entry.item.number != SVN_FS_FS__ITEM_INDEX_UNUSED
+ || entry.fnv1_checksum != 0)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("Empty regions must have item number 0 and checksum 0"));
+
+ APR_ARRAY_PUSH(result, svn_fs_fs__p2l_entry_t) = entry;
+ *item_offset += entry.size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the phys-to-log mappings for the cluster beginning at rev file
+ * offset PAGE_START from the index for START_REVISION in FS. The data
+ * can be found in the index page beginning at START_OFFSET with the next
+ * page beginning at NEXT_OFFSET. PAGE_SIZE is the L2P index page size.
+ * Return the relevant index entries in *ENTRIES. Use REV_FILE to access
+ * on-disk data. Allocate *ENTRIES in RESULT_POOL.
+ */
+static svn_error_t *
+get_p2l_page(apr_array_header_t **entries,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t start_revision,
+ apr_off_t start_offset,
+ apr_off_t next_offset,
+ apr_off_t page_start,
+ apr_uint64_t page_size,
+ apr_pool_t *result_pool)
+{
+ apr_uint64_t value;
+ apr_array_header_t *result
+ = apr_array_make(result_pool, 16, sizeof(svn_fs_fs__p2l_entry_t));
+ apr_off_t item_offset;
+ apr_off_t offset;
+ svn_revnum_t last_revision;
+ apr_uint64_t last_compound;
+
+ /* open index and navigate to page start */
+ SVN_ERR(auto_open_p2l_index(rev_file, fs, start_revision));
+ packed_stream_seek(rev_file->p2l_stream, start_offset);
+
+ /* read rev file offset of the first page entry (all page entries will
+ * only store their sizes). */
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ item_offset = (apr_off_t)value;
+
+ /* read all entries of this page */
+ last_revision = start_revision;
+ last_compound = 0;
+
+ /* Special case: empty pages. */
+ if (start_offset == next_offset)
+ {
+ /* Empty page. This only happens if the first entry of the next page
+ * also covers this page (and possibly more) completely. */
+ SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
+ &last_revision, &last_compound, result));
+ }
+ else
+ {
+ /* Read non-empty page. */
+ do
+ {
+ SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
+ &last_revision, &last_compound, result));
+ offset = packed_stream_offset(rev_file->p2l_stream);
+ }
+ while (offset < next_offset);
+
+ /* We should now be exactly at the next offset, i.e. the numbers in
+ * the stream cannot overlap into the next page description. */
+ if (offset != next_offset)
+ return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
+ _("P2L page description overlaps with next page description"));
+
+ /* if we haven't covered the cluster end yet, we must read the first
+ * entry of the next page */
+ if (item_offset < page_start + page_size)
+ {
+ SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
+ item_offset = (apr_off_t)value;
+ last_revision = start_revision;
+ last_compound = 0;
+ SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
+ &last_revision, &last_compound, result));
+ }
+ }
+
+ *entries = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* If it cannot be found in FS's caches, read the p2l index page selected
+ * by BATON->OFFSET from REV_FILE. Don't read the page if it precedes
+ * MIN_OFFSET. Set *END to TRUE if the caller should stop refeching.
+ *
+ * *BATON will be updated with the selected page's info and SCRATCH_POOL
+ * will be used for temporary allocations. If the data is alread in the
+ * cache, descrease *LEAKING_BUCKET and increase it otherwise. With that
+ * pattern we will still read all pages from the block even if some of
+ * them survived in the cached.
+ */
+static svn_error_t *
+prefetch_p2l_page(svn_boolean_t *end,
+ int *leaking_bucket,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ p2l_page_info_baton_t *baton,
+ apr_off_t min_offset,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t already_cached;
+ apr_array_header_t *page;
+ svn_fs_fs__page_cache_key_t key = { 0 };
+
+ /* fetch the page info */
+ *end = FALSE;
+ baton->revision = baton->first_revision;
+ SVN_ERR(get_p2l_page_info(baton, rev_file, fs, scratch_pool));
+ if (baton->start_offset < min_offset || !rev_file->p2l_stream)
+ {
+ /* page outside limits -> stop prefetching */
+ *end = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* do we have that page in our caches already? */
+ assert(baton->first_revision <= APR_UINT32_MAX);
+ key.revision = (apr_uint32_t)baton->first_revision;
+ key.is_packed = svn_fs_fs__is_packed_rev(fs, baton->first_revision);
+ key.page = baton->page_no;
+ SVN_ERR(svn_cache__has_key(&already_cached, ffd->p2l_page_cache,
+ &key, scratch_pool));
+
+ /* yes, already cached */
+ if (already_cached)
+ {
+ /* stop prefetching if most pages are already cached. */
+ if (!--*leaking_bucket)
+ *end = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ ++*leaking_bucket;
+
+ /* read from disk */
+ SVN_ERR(get_p2l_page(&page, rev_file, fs,
+ baton->first_revision,
+ baton->start_offset,
+ baton->next_offset,
+ baton->page_start,
+ baton->page_size,
+ scratch_pool));
+
+ /* and put it into our cache */
+ SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Lookup & construct the baton and key information that we will need for
+ * a P2L page cache lookup. We want the page covering OFFSET in the rev /
+ * pack file containing REVSION in FS. Return the results in *PAGE_INFO_P
+ * and *KEY_P. Read data through REV_FILE. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static svn_error_t *
+get_p2l_keys(p2l_page_info_baton_t *page_info_p,
+ svn_fs_fs__page_cache_key_t *key_p,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ p2l_page_info_baton_t page_info;
+
+ /* request info for the index pages that describes the pack / rev file
+ * contents at pack / rev file position OFFSET. */
+ page_info.offset = offset;
+ page_info.revision = revision;
+ SVN_ERR(get_p2l_page_info(&page_info, rev_file, fs, scratch_pool));
+
+ /* if the offset refers to a non-existent page, bail out */
+ if (page_info.page_count <= page_info.page_no)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("Offset %s too large in revision %ld"),
+ apr_off_t_toa(scratch_pool, offset), revision);
+
+ /* return results */
+ if (page_info_p)
+ *page_info_p = page_info;
+
+ /* construct cache key */
+ if (key_p)
+ {
+ svn_fs_fs__page_cache_key_t key = { 0 };
+ assert(page_info.first_revision <= APR_UINT32_MAX);
+ key.revision = (apr_uint32_t)page_info.first_revision;
+ key.is_packed = rev_file->is_packed;
+ key.page = page_info.page_no;
+
+ *key_p = key;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* qsort-compatible compare function that compares the OFFSET of the
+ * svn_fs_fs__p2l_entry_t in *LHS with the apr_off_t in *RHS. */
+static int
+compare_start_p2l_entry(const void *lhs,
+ const void *rhs)
+{
+ const svn_fs_fs__p2l_entry_t *entry = lhs;
+ apr_off_t start = *(const apr_off_t*)rhs;
+ apr_off_t diff = entry->offset - start;
+
+ /* restrict result to int */
+ return diff < 0 ? -1 : (diff == 0 ? 0 : 1);
+}
+
+/* From the PAGE_ENTRIES array of svn_fs_fs__p2l_entry_t, ordered
+ * by their OFFSET member, copy all elements overlapping the range
+ * [BLOCK_START, BLOCK_END) to ENTRIES. */
+static void
+append_p2l_entries(apr_array_header_t *entries,
+ apr_array_header_t *page_entries,
+ apr_off_t block_start,
+ apr_off_t block_end)
+{
+ const svn_fs_fs__p2l_entry_t *entry;
+ int idx = svn_sort__bsearch_lower_bound(page_entries, &block_start,
+ compare_start_p2l_entry);
+
+ /* start at the first entry that overlaps with BLOCK_START */
+ if (idx > 0)
+ {
+ entry = &APR_ARRAY_IDX(page_entries, idx - 1, svn_fs_fs__p2l_entry_t);
+ if (entry->offset + entry->size > block_start)
+ --idx;
+ }
+
+ /* copy all entries covering the requested range */
+ for ( ; idx < page_entries->nelts; ++idx)
+ {
+ entry = &APR_ARRAY_IDX(page_entries, idx, svn_fs_fs__p2l_entry_t);
+ if (entry->offset >= block_end)
+ break;
+
+ APR_ARRAY_PUSH(entries, svn_fs_fs__p2l_entry_t) = *entry;
+ }
+}
+
+/* Auxilliary struct passed to p2l_entries_func selecting the relevant
+ * data range. */
+typedef struct p2l_entries_baton_t
+{
+ apr_off_t start;
+ apr_off_t end;
+} p2l_entries_baton_t;
+
+/* Implement svn_cache__partial_getter_func_t: extract p2l entries from
+ * the page in DATA which overlap the p2l_entries_baton_t in BATON.
+ * The target array is already provided in *OUT.
+ */
+static svn_error_t *
+p2l_entries_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *entries = *(apr_array_header_t **)out;
+ const apr_array_header_t *raw_page = data;
+ p2l_entries_baton_t *block = baton;
+
+ /* Make PAGE a readable APR array. */
+ apr_array_header_t page = *raw_page;
+ page.elts = (void *)svn_temp_deserializer__ptr(raw_page,
+ (const void * const *)&raw_page->elts);
+
+ /* append relevant information to result */
+ append_p2l_entries(entries, &page, block->start, block->end);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Body of svn_fs_fs__p2l_index_lookup. However, do a single index page
+ * lookup and append the result to the ENTRIES array provided by the caller.
+ * Use successive calls to cover larger ranges.
+ */
+static svn_error_t *
+p2l_index_lookup(apr_array_header_t *entries,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_off_t block_start,
+ apr_off_t block_end,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_fs__page_cache_key_t key;
+ svn_boolean_t is_cached = FALSE;
+ p2l_page_info_baton_t page_info;
+ apr_array_header_t *local_result = entries;
+
+ /* baton selecting the relevant entries from the one page we access */
+ p2l_entries_baton_t block;
+ block.start = block_start;
+ block.end = block_end;
+
+ /* if we requested an empty range, the result would be empty */
+ SVN_ERR_ASSERT(block_start < block_end);
+
+ /* look for the fist page of the range in our cache */
+ SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, block_start,
+ scratch_pool));
+ SVN_ERR(svn_cache__get_partial((void**)&local_result, &is_cached,
+ ffd->p2l_page_cache, &key, p2l_entries_func,
+ &block, scratch_pool));
+
+ if (!is_cached)
+ {
+ svn_boolean_t end;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_off_t original_page_start = page_info.page_start;
+ int leaking_bucket = 4;
+ p2l_page_info_baton_t prefetch_info = page_info;
+ apr_array_header_t *page_entries;
+
+ apr_off_t max_offset
+ = APR_ALIGN(page_info.next_offset, ffd->block_size);
+ apr_off_t min_offset
+ = APR_ALIGN(page_info.start_offset, ffd->block_size) - ffd->block_size;
+
+ /* Since we read index data in larger chunks, we probably got more
+ * page data than we requested. Parse & cache that until either we
+ * encounter pages already cached or reach the end of the buffer.
+ */
+
+ /* pre-fetch preceding pages */
+ if (ffd->use_block_read)
+ {
+ end = FALSE;
+ prefetch_info.offset = original_page_start;
+ while (prefetch_info.offset >= prefetch_info.page_size && !end)
+ {
+ svn_pool_clear(iterpool);
+
+ prefetch_info.offset -= prefetch_info.page_size;
+ SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
+ &prefetch_info, min_offset,
+ iterpool));
+ }
+ }
+
+ /* fetch page from disk and put it into the cache */
+ SVN_ERR(get_p2l_page(&page_entries, rev_file, fs,
+ page_info.first_revision,
+ page_info.start_offset,
+ page_info.next_offset,
+ page_info.page_start,
+ page_info.page_size, iterpool));
+
+ /* The last cache entry must not end beyond the range covered by
+ * this index. The same applies for any subset of entries. */
+ if (page_entries->nelts)
+ {
+ const svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(page_entries, page_entries->nelts - 1,
+ svn_fs_fs__p2l_entry_t);
+ if ( entry->offset + entry->size
+ > page_info.page_size * page_info.page_count)
+ return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("Last P2L index entry extends beyond "
+ "the last page in revision %ld."),
+ revision);
+ }
+
+ SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page_entries,
+ iterpool));
+
+ /* append relevant information to result */
+ append_p2l_entries(entries, page_entries, block_start, block_end);
+
+ /* pre-fetch following pages */
+ if (ffd->use_block_read)
+ {
+ end = FALSE;
+ leaking_bucket = 4;
+ prefetch_info = page_info;
+ prefetch_info.offset = original_page_start;
+ while ( prefetch_info.next_offset < max_offset
+ && prefetch_info.page_no + 1 < prefetch_info.page_count
+ && !end)
+ {
+ svn_pool_clear(iterpool);
+
+ prefetch_info.offset += prefetch_info.page_size;
+ SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
+ &prefetch_info, min_offset,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* We access a valid page (otherwise, we had seen an error in the
+ * get_p2l_keys request). Hence, at least one entry must be found. */
+ SVN_ERR_ASSERT(entries->nelts > 0);
+
+ /* Add an "unused" entry if it extends beyond the end of the data file.
+ * Since the index page size might be smaller than the current data
+ * read block size, the trailing "unused" entry in this index may not
+ * fully cover the end of the last block. */
+ if (page_info.page_no + 1 >= page_info.page_count)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_fs__p2l_entry_t);
+
+ apr_off_t entry_end = entry->offset + entry->size;
+ if (entry_end < block_end)
+ {
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ {
+ /* extend the terminal filler */
+ entry->size = block_end - entry->offset;
+ }
+ else
+ {
+ /* No terminal filler. Add one. */
+ entry = apr_array_push(entries);
+ entry->offset = entry_end;
+ entry->size = block_end - entry_end;
+ entry->type = SVN_FS_FS__ITEM_TYPE_UNUSED;
+ entry->fnv1_checksum = 0;
+ entry->item.revision = SVN_INVALID_REVNUM;
+ entry->item.number = SVN_FS_FS__ITEM_INDEX_UNUSED;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__p2l_index_lookup(apr_array_header_t **entries,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_off_t block_start,
+ apr_off_t block_size,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_off_t block_end = block_start + block_size;
+
+ /* the receiving container */
+ int last_count = 0;
+ apr_array_header_t *result = apr_array_make(result_pool, 16,
+ sizeof(svn_fs_fs__p2l_entry_t));
+
+ /* Fetch entries page-by-page. Since the p2l index is supposed to cover
+ * every single byte in the rev / pack file - even unused sections -
+ * every iteration must result in some progress. */
+ while (block_start < block_end)
+ {
+ svn_fs_fs__p2l_entry_t *entry;
+ SVN_ERR(p2l_index_lookup(result, rev_file, fs, revision, block_start,
+ block_end, scratch_pool));
+ SVN_ERR_ASSERT(result->nelts > 0);
+
+ /* continue directly behind last item */
+ entry = &APR_ARRAY_IDX(result, result->nelts-1, svn_fs_fs__p2l_entry_t);
+ block_start = entry->offset + entry->size;
+
+ /* Some paranoia check. Successive iterations should never return
+ * duplicates but if it did, we might get into trouble later on. */
+ if (last_count > 0 && last_count < result->nelts)
+ {
+ entry = &APR_ARRAY_IDX(result, last_count - 1,
+ svn_fs_fs__p2l_entry_t);
+ SVN_ERR_ASSERT(APR_ARRAY_IDX(result, last_count,
+ svn_fs_fs__p2l_entry_t).offset
+ >= entry->offset + entry->size);
+ }
+
+ last_count = result->nelts;
+ }
+
+ *entries = result;
+ return SVN_NO_ERROR;
+}
+
+/* compare_fn_t comparing a svn_fs_fs__p2l_entry_t at LHS with an offset
+ * RHS.
+ */
+static int
+compare_p2l_entry_offsets(const void *lhs, const void *rhs)
+{
+ const svn_fs_fs__p2l_entry_t *entry = (const svn_fs_fs__p2l_entry_t *)lhs;
+ apr_off_t offset = *(const apr_off_t *)rhs;
+
+ return entry->offset < offset ? -1 : (entry->offset == offset ? 0 : 1);
+}
+
+/* Cached data extraction utility. DATA is a P2L index page, e.g. an APR
+ * array of svn_fs_fs__p2l_entry_t elements. Return the entry for the item,
+ * allocated in RESULT_POOL, starting at OFFSET or NULL if that's not an
+ * the start offset of any item. Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_fs_fs__p2l_entry_t *
+get_p2l_entry_from_cached_page(const void *data,
+ apr_uint64_t offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* resolve all pointer values of in-cache data */
+ const apr_array_header_t *page = data;
+ apr_array_header_t *entries = apr_pmemdup(scratch_pool, page,
+ sizeof(*page));
+ svn_fs_fs__p2l_entry_t *entry;
+
+ entries->elts = (char *)svn_temp_deserializer__ptr(page,
+ (const void *const *)&page->elts);
+
+ /* search of the offset we want */
+ entry = svn_sort__array_lookup(entries, &offset, NULL,
+ (int (*)(const void *, const void *))compare_p2l_entry_offsets);
+
+ /* return it, if it is a perfect match */
+ return entry ? apr_pmemdup(result_pool, entry, sizeof(*entry)) : NULL;
+}
+
+/* Implements svn_cache__partial_getter_func_t for P2L index pages, copying
+ * the entry for the apr_off_t at BATON into *OUT. *OUT will be NULL if
+ * there is no matching entry in the index page at DATA.
+ */
+static svn_error_t *
+p2l_entry_lookup_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_fs_fs__p2l_entry_t *entry
+ = get_p2l_entry_from_cached_page(data, *(apr_off_t *)baton, result_pool,
+ result_pool);
+
+ *out = entry && entry->offset == *(apr_off_t *)baton
+ ? apr_pmemdup(result_pool, entry, sizeof(*entry))
+ : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__p2l_entry_lookup(svn_fs_fs__p2l_entry_t **entry_p,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_off_t offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_fs__page_cache_key_t key = { 0 };
+ svn_boolean_t is_cached = FALSE;
+ p2l_page_info_baton_t page_info;
+
+ *entry_p = NULL;
+
+ /* look for this info in our cache */
+ SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, offset,
+ scratch_pool));
+ SVN_ERR(svn_cache__get_partial((void**)entry_p, &is_cached,
+ ffd->p2l_page_cache, &key,
+ p2l_entry_lookup_func, &offset,
+ result_pool));
+ if (!is_cached)
+ {
+ /* do a standard index lookup. This is will automatically prefetch
+ * data to speed up future lookups. */
+ apr_array_header_t *entries = apr_array_make(result_pool, 1,
+ sizeof(**entry_p));
+ SVN_ERR(p2l_index_lookup(entries, rev_file, fs, revision, offset,
+ offset + 1, scratch_pool));
+
+ /* Find the entry that we want. */
+ *entry_p = svn_sort__array_lookup(entries, &offset, NULL,
+ (int (*)(const void *, const void *))compare_p2l_entry_offsets);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_cache__partial_getter_func_t for P2L headers, setting *OUT
+ * to the largest the first offset not covered by this P2L index.
+ */
+static svn_error_t *
+p2l_get_max_offset_func(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ const p2l_header_t *header = data;
+ apr_off_t max_offset = header->file_size;
+ *out = apr_pmemdup(result_pool, &max_offset, sizeof(max_offset));
+
+ return SVN_NO_ERROR;
+}
+
+/* Core functionality of to svn_fs_fs__p2l_get_max_offset with identical
+ * signature. */
+static svn_error_t *
+p2l_get_max_offset(apr_off_t *offset,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ p2l_header_t *header;
+ svn_boolean_t is_cached = FALSE;
+ apr_off_t *offset_p;
+
+ /* look for the header data in our cache */
+ pair_cache_key_t key;
+ key.revision = rev_file->start_revision;
+ key.second = rev_file->is_packed;
+
+ SVN_ERR(svn_cache__get_partial((void **)&offset_p, &is_cached,
+ ffd->p2l_header_cache, &key,
+ p2l_get_max_offset_func, NULL,
+ scratch_pool));
+ if (is_cached)
+ {
+ *offset = *offset_p;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(get_p2l_header(&header, rev_file, fs, revision, scratch_pool,
+ scratch_pool));
+ *offset = header->file_size;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__p2l_get_max_offset(apr_off_t *offset,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(p2l_get_max_offset(offset, fs, rev_file, revision,
+ scratch_pool));
+}
+
+/* Calculate the FNV1 checksum over the offset range in REV_FILE, covered by
+ * ENTRY. Store the result in ENTRY->FNV1_CHECKSUM. Use SCRATCH_POOL for
+ * temporary allocations. */
+static svn_error_t *
+calc_fnv1(svn_fs_fs__p2l_entry_t *entry,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char buffer[4096];
+ svn_checksum_t *checksum;
+ svn_checksum_ctx_t *context
+ = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, scratch_pool);
+ apr_off_t size = entry->size;
+
+ /* Special rules apply to unused sections / items. The data must be a
+ * sequence of NUL bytes (not checked here) and the checksum is fixed to 0.
+ */
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ {
+ entry->fnv1_checksum = 0;
+ return SVN_NO_ERROR;
+ }
+
+ /* Read the block and feed it to the checksum calculator. */
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &entry->offset,
+ scratch_pool));
+ while (size > 0)
+ {
+ apr_size_t to_read = size > sizeof(buffer)
+ ? sizeof(buffer)
+ : (apr_size_t)size;
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, to_read, NULL,
+ NULL, scratch_pool));
+ SVN_ERR(svn_checksum_update(context, buffer, to_read));
+ size -= to_read;
+ }
+
+ /* Store final checksum in ENTRY. */
+ SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
+ entry->fnv1_checksum = ntohl(*(const apr_uint32_t *)checksum->digest);
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Index (re-)creation utilities.
+ */
+
+svn_error_t *
+svn_fs_fs__p2l_index_from_p2l_entries(const char **protoname,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_array_header_t *entries,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *proto_index;
+
+ /* Use a subpool for immediate temp file cleanup at the end of this
+ * function. */
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* Create a proto-index file. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_open(&proto_index, *protoname,
+ scratch_pool));
+
+ /* Write ENTRIES to proto-index file and calculate checksums as we go. */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t *);
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(calc_fnv1(entry, rev_file, iterpool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(proto_index, entry,
+ iterpool));
+ }
+
+ /* Convert proto-index into final index and move it into position.
+ * Note that REV_FILE contains the start revision of the shard file if it
+ * has been packed while REVISION may be somewhere in the middle. For
+ * non-packed shards, they will have identical values. */
+ SVN_ERR(svn_io_file_close(proto_index, iterpool));
+
+ /* Temp file cleanup. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* A svn_sort__array compatible comparator function, sorting the
+ * svn_fs_fs__p2l_entry_t** given in LHS, RHS by revision. */
+static int
+compare_p2l_entry_revision(const void *lhs,
+ const void *rhs)
+{
+ const svn_fs_fs__p2l_entry_t *lhs_entry
+ =*(const svn_fs_fs__p2l_entry_t **)lhs;
+ const svn_fs_fs__p2l_entry_t *rhs_entry
+ =*(const svn_fs_fs__p2l_entry_t **)rhs;
+
+ if (lhs_entry->item.revision < rhs_entry->item.revision)
+ return -1;
+
+ return lhs_entry->item.revision == rhs_entry->item.revision ? 0 : 1;
+}
+
+svn_error_t *
+svn_fs_fs__l2p_index_from_p2l_entries(const char **protoname,
+ svn_fs_t *fs,
+ apr_array_header_t *entries,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *proto_index;
+
+ /* Use a subpool for immediate temp file cleanup at the end of this
+ * function. */
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ svn_revnum_t last_revision = SVN_INVALID_REVNUM;
+ svn_revnum_t revision = SVN_INVALID_REVNUM;
+
+ /* L2P index must be written in revision order.
+ * Sort ENTRIES accordingly. */
+ svn_sort__array(entries, compare_p2l_entry_revision);
+
+ /* Find the first revision in the index
+ * (must exist since no truly empty revs are allowed). */
+ for (i = 0; i < entries->nelts && !SVN_IS_VALID_REVNUM(revision); ++i)
+ revision = APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t *)
+ ->item.revision;
+
+ /* Create the temporary proto-rev file. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_fs_fs__l2p_proto_index_open(&proto_index, *protoname,
+ scratch_pool));
+
+ /* Write all entries. */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ const svn_fs_fs__p2l_entry_t *entry
+ = APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t *);
+ svn_pool_clear(iterpool);
+
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ continue;
+
+ if (last_revision != entry->item.revision)
+ {
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_revision(proto_index,
+ scratch_pool));
+ last_revision = entry->item.revision;
+ }
+
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(proto_index,
+ entry->offset,
+ entry->item.number,
+ iterpool));
+ }
+
+ /* Convert proto-index into final index and move it into position. */
+ SVN_ERR(svn_io_file_close(proto_index, iterpool));
+
+ /* Temp file cleanup. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * Standard (de-)serialization functions
+ */
+
+svn_error_t *
+svn_fs_fs__serialize_l2p_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ l2p_header_t *header = in;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_size_t page_count = header->page_table_index[header->revision_count];
+ apr_size_t page_table_size = page_count * sizeof(*header->page_table);
+ apr_size_t index_size
+ = (header->revision_count + 1) * sizeof(*header->page_table_index);
+ apr_size_t data_size = sizeof(*header) + index_size + page_table_size;
+
+ /* serialize header and all its elements */
+ context = svn_temp_serializer__init(header,
+ sizeof(*header),
+ data_size + 32,
+ pool);
+
+ /* page table index array */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&header->page_table_index,
+ index_size);
+
+ /* page table array */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&header->page_table,
+ page_table_size);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_l2p_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ l2p_header_t *header = (l2p_header_t *)data;
+
+ /* resolve the pointers in the struct */
+ svn_temp_deserializer__resolve(header, (void**)&header->page_table_index);
+ svn_temp_deserializer__resolve(header, (void**)&header->page_table);
+
+ /* done */
+ *out = header;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_l2p_page(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ l2p_page_t *page = in;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_size_t of_table_size = page->entry_count * sizeof(*page->offsets);
+
+ /* serialize struct and all its elements */
+ context = svn_temp_serializer__init(page,
+ sizeof(*page),
+ of_table_size + sizeof(*page) + 32,
+ pool);
+
+ /* offsets and sub_items arrays */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&page->offsets,
+ of_table_size);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_l2p_page(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ l2p_page_t *page = data;
+
+ /* resolve the pointers in the struct */
+ svn_temp_deserializer__resolve(page, (void**)&page->offsets);
+
+ /* done */
+ *out = page;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_p2l_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ p2l_header_t *header = in;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_size_t table_size = (header->page_count + 1) * sizeof(*header->offsets);
+
+ /* serialize header and all its elements */
+ context = svn_temp_serializer__init(header,
+ sizeof(*header),
+ table_size + sizeof(*header) + 32,
+ pool);
+
+ /* offsets array */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&header->offsets,
+ table_size);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_p2l_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ p2l_header_t *header = data;
+
+ /* resolve the only pointer in the struct */
+ svn_temp_deserializer__resolve(header, (void**)&header->offsets);
+
+ /* done */
+ *out = header;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_p2l_page(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *page = in;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_size_t table_size = page->elt_size * page->nelts;
+
+ /* serialize array header and all its elements */
+ context = svn_temp_serializer__init(page,
+ sizeof(*page),
+ table_size + sizeof(*page) + 32,
+ pool);
+
+ /* items in the array */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&page->elts,
+ table_size);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_p2l_page(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *page = (apr_array_header_t *)data;
+
+ /* resolve the only pointer in the struct */
+ svn_temp_deserializer__resolve(page, (void**)&page->elts);
+
+ /* patch up members */
+ page->pool = pool;
+ page->nalloc = page->nelts;
+
+ /* done */
+ *out = page;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/index.h b/subversion/libsvn_fs_fs/index.h
new file mode 100644
index 0000000..1b696f2
--- /dev/null
+++ b/subversion/libsvn_fs_fs/index.h
@@ -0,0 +1,356 @@
+/* index.h : interface to FSFS indexing functionality
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__INDEX_H
+#define SVN_LIBSVN_FS__INDEX_H
+
+#include "fs.h"
+#include "rev_file.h"
+
+/* Per-defined item index values. They are used to identify empty or
+ * mandatory items.
+ */
+#define SVN_FS_FS__ITEM_INDEX_UNUSED 0 /* invalid / reserved value */
+#define SVN_FS_FS__ITEM_INDEX_CHANGES 1 /* list of changed paths */
+#define SVN_FS_FS__ITEM_INDEX_ROOT_NODE 2 /* the root noderev */
+#define SVN_FS_FS__ITEM_INDEX_FIRST_USER 3 /* first noderev to be freely
+ assigned */
+
+/* Data / item types as stored in the phys-to-log index.
+ */
+#define SVN_FS_FS__ITEM_TYPE_UNUSED 0 /* file section not used */
+#define SVN_FS_FS__ITEM_TYPE_FILE_REP 1 /* item is a file representation */
+#define SVN_FS_FS__ITEM_TYPE_DIR_REP 2 /* item is a directory rep. */
+#define SVN_FS_FS__ITEM_TYPE_FILE_PROPS 3 /* item is a file property rep. */
+#define SVN_FS_FS__ITEM_TYPE_DIR_PROPS 4 /* item is a directory prop rep */
+#define SVN_FS_FS__ITEM_TYPE_NODEREV 5 /* item is a noderev */
+#define SVN_FS_FS__ITEM_TYPE_CHANGES 6 /* item is a changed paths list */
+
+#define SVN_FS_FS__ITEM_TYPE_ANY_REP 7 /* item is any representation.
+ Only used in pre-format7. */
+
+/* Open / create a log-to-phys index file with the full file path name
+ * FILE_NAME. Return the open file in *PROTO_INDEX allocated in
+ * RESULT_POOL.
+ */
+svn_error_t *
+svn_fs_fs__l2p_proto_index_open(apr_file_t **proto_index,
+ const char *file_name,
+ apr_pool_t *result_pool);
+
+/* Call this function before adding entries for the next revision to the
+ * log-to-phys index file in PROTO_INDEX. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+svn_error_t *
+svn_fs_fs__l2p_proto_index_add_revision(apr_file_t *proto_index,
+ apr_pool_t *scratch_pool);
+
+/* Add a new mapping, ITEM_INDEX to the OFFSET, to log-to-phys index file
+ * in PROTO_INDEX. Please note that mappings may be added in any order
+ * but duplicate entries for the same ITEM_INDEX are not supported.
+ * Not all possible index values need to be used. OFFSET may be -1 to
+ * mark 'invalid' item indexes but that is already implied for all item
+ * indexes not explicitly given a mapping.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__l2p_proto_index_add_entry(apr_file_t *proto_index,
+ apr_off_t offset,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool);
+
+/* Use the proto index file stored at PROTO_FILE_NAME, construct the final
+ * log-to-phys index and append it to INDEX_FILE. The first revision will
+ * be REVISION, entries to the next revision will be assigned to REVISION+1
+ * and so forth.
+ *
+ * Return the MD5 checksum of the on-disk index data in *CHECKSUM, allocated
+ * in RESULT_POOL. Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__l2p_index_append(svn_checksum_t **checksum,
+ svn_fs_t *fs,
+ apr_file_t *index_file,
+ const char *proto_file_name,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Open / create a phys-to-log index file with the full file path name
+ * FILE_NAME. Return the open file in *PROTO_INDEX allocated in
+ * RESULT_POOL.
+ */
+svn_error_t *
+svn_fs_fs__p2l_proto_index_open(apr_file_t **proto_index,
+ const char *file_name,
+ apr_pool_t *result_pool);
+
+/* Add a new mapping ENTRY to the phys-to-log index file in PROTO_INDEX.
+ * The entries must be added in ascending offset order and must not leave
+ * intermittent ranges uncovered. The revision value in ENTRY may be
+ * SVN_INVALID_REVISION. Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_proto_index_add_entry(apr_file_t *proto_index,
+ const svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *scratch_pool);
+
+/* Set *NEXT_OFFSET to the first offset behind the last entry in the
+ * phys-to-log proto index file PROTO_INDEX. This will be 0 for empty
+ * index files. Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_proto_index_next_offset(apr_off_t *next_offset,
+ apr_file_t *proto_index,
+ apr_pool_t *scratch_pool);
+
+/* Use the proto index file stored at PROTO_FILE_NAME, construct the final
+ * phys-to-log index and append it to INDEX_FILE. Entries without a valid
+ * revision will be assigned to the REVISION given here.
+ *
+ * Return the MD5 checksum of the on-disk index data in *CHECKSUM, allocated
+ * in RESULT_POOL. Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_index_append(svn_checksum_t **checksum,
+ svn_fs_t *fs,
+ apr_file_t *index_file,
+ const char *proto_file_name,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Use the phys-to-log mapping files in FS to build a list of entries
+ * that (at least partly) overlap with the range given by BLOCK_START
+ * offset and BLOCK_SIZE in the rep / pack file containing REVISION.
+ * Return the array in *ENTRIES with svn_fs_fs__p2l_entry_t as elements,
+ * allocated in RESULT_POOL. REV_FILE determines whether to access single
+ * rev or pack file data. If that is not available anymore (neither in
+ * cache nor on disk), return an error. Use SCRATCH_POOL for temporary
+ * allocations.
+ *
+ * Note that (only) the first and the last mapping may cross a cluster
+ * boundary.
+ */
+svn_error_t *
+svn_fs_fs__p2l_index_lookup(apr_array_header_t **entries,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_off_t block_start,
+ apr_off_t block_size,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Use the phys-to-log mapping files in FS to return the entry for the
+ * item starting at global OFFSET in the rep file containing REVISION in
+ * *ENTRY, allocated in RESULT_POOL. Sets *ENTRY to NULL if no item starts
+ * at exactly that offset. REV_FILE determines whether to access single
+ * rev or pack file data. If that is not available anymore (neither in
+ * cache nor on disk), return an error. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_entry_lookup(svn_fs_fs__p2l_entry_t **entry,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_off_t offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* For ITEM_INDEX within REV in FS, return the position in the respective
+ * rev or pack file in *ABSOLUTE_POSITION. If TXN_ID is not NULL, return
+ * the file offset within that transaction and REV should be given as
+ * SVN_INVALID_REVNUM in that case.
+ *
+ * REV_FILE determines whether to access single rev or pack file data.
+ * If that is not available anymore (neither in cache nor on disk), re-open
+ * the rev / pack file and retry to open the index file. For anything but
+ * committed log addressed revisions, REV_FILE may be NULL.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__item_offset(apr_off_t *absolute_position,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_uint64_t item_index,
+ apr_pool_t *scratch_pool);
+
+/* Use the log-to-phys indexes in FS to determine the maximum item indexes
+ * assigned to revision START_REV to START_REV + COUNT - 1. That is a
+ * close upper limit to the actual number of items in the respective revs.
+ * Return the results in *MAX_IDS, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__l2p_get_max_ids(apr_array_header_t **max_ids,
+ svn_fs_t *fs,
+ svn_revnum_t start_rev,
+ apr_size_t count,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* In *OFFSET, return the last OFFSET in the pack / rev file containing.
+ * REV_FILE determines whether to access single rev or pack file data.
+ * If that is not available anymore (neither in cache nor on disk), re-open
+ * the rev / pack file and retry to open the index file.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_get_max_offset(apr_off_t *offset,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool);
+
+/* Index (re-)creation utilities.
+ */
+
+/* For FS, create a new L2P auto-deleting proto index file in POOL and return
+ * its name in *PROTONAME. All entries to write are given in ENTRIES and
+ * entries are of type svn_fs_fs__p2l_entry_t* (sic!). The ENTRIES array
+ * will be reordered. Give the proto index file the lifetime of RESULT_POOL
+ * and use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__l2p_index_from_p2l_entries(const char **protoname,
+ svn_fs_t *fs,
+ apr_array_header_t *entries,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* For FS, create a new P2L auto-deleting proto index file in POOL and return
+ * its name in *PROTONAME. All entries to write are given in ENTRIES and
+ * of type svn_fs_fs__p2l_entry_t*. The FVN1 checksums are not taken from
+ * ENTRIES but are begin calculated from the current contents of REV_FILE
+ * as we go. Give the proto index file the lifetime of RESULT_POOL and use
+ * SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__p2l_index_from_p2l_entries(const char **protoname,
+ svn_fs_t *fs,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_array_header_t *entries,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Serialization and caching interface
+ */
+
+/* We use this key type to address individual pages from both index types.
+ */
+typedef struct svn_fs_fs__page_cache_key_t
+{
+ /* in l2p: this is the revision of the items being mapped
+ in p2l: this is the start revision identifying the pack / rev file */
+ apr_uint32_t revision;
+
+ /* if TRUE, this is the index to a pack file
+ */
+ svn_boolean_t is_packed;
+
+ /* in l2p: page number within the revision
+ * in p2l: page number with the rev / pack file
+ */
+ apr_uint64_t page;
+} svn_fs_fs__page_cache_key_t;
+
+/*
+ * Implements svn_cache__serialize_func_t for l2p_header_t objects.
+ */
+svn_error_t *
+svn_fs_fs__serialize_l2p_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__deserialize_func_t for l2p_header_t objects.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_l2p_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__serialize_func_t for l2p_page_t objects.
+ */
+svn_error_t *
+svn_fs_fs__serialize_l2p_page(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__deserialize_func_t for l2p_page_t objects.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_l2p_page(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__serialize_func_t for p2l_header_t objects.
+ */
+svn_error_t *
+svn_fs_fs__serialize_p2l_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__deserialize_func_t for p2l_header_t objects.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_p2l_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__serialize_func_t for apr_array_header_t objects
+ * with elements of type svn_fs_fs__p2l_entry_t.
+ */
+svn_error_t *
+svn_fs_fs__serialize_p2l_page(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/*
+ * Implements svn_cache__deserialize_func_t for apr_array_header_t objects
+ * with elements of type svn_fs_fs__p2l_entry_t.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_p2l_page(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/key-gen.c b/subversion/libsvn_fs_fs/key-gen.c
deleted file mode 100644
index a65c59d..0000000
--- a/subversion/libsvn_fs_fs/key-gen.c
+++ /dev/null
@@ -1,159 +0,0 @@
-/* key-gen.c --- manufacturing sequential keys for some db tables
- *
- * ====================================================================
- * 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 <assert.h>
-#include <string.h>
-#include <stdlib.h>
-#include <apr.h>
-#include <apr_network_io.h>
-#include "private/svn_fs_private.h"
-#include "key-gen.h"
-
-/* The Berkeley DB backend uses a key as a transaction name and the
- maximum key size must be less than the maximum transaction name
- length. */
-#if MAX_KEY_SIZE > SVN_FS__TXN_MAX_LEN
-#error The MAX_KEY_SIZE used for BDB txn names is greater than SVN_FS__TXN_MAX_LEN.
-#endif
-
-
-/*** Keys for reps and strings. ***/
-
-void
-svn_fs_fs__add_keys(const char *key1, const char *key2, char *result)
-{
- apr_ssize_t i1 = strlen(key1) - 1;
- apr_ssize_t i2 = strlen(key2) - 1;
- int i3 = 0;
- int val;
- int carry = 0;
- char buf[MAX_KEY_SIZE + 2];
-
- while ((i1 >= 0) || (i2 >= 0) || (carry > 0))
- {
- val = carry;
- if (i1>=0)
- val += (key1[i1] <= '9') ? (key1[i1] - '0') : (key1[i1] - 'a' + 10);
-
- if (i2>=0)
- val += (key2[i2] <= '9') ? (key2[i2] - '0') : (key2[i2] - 'a' + 10);
-
- carry = val / 36;
- val = val % 36;
-
- buf[i3++] = (char)((val <= 9) ? (val + '0') : (val - 10 + 'a'));
-
- if (i1>=0)
- i1--;
- if (i2>=0)
- i2--;
- }
-
- /* Now reverse the resulting string and NULL terminate it. */
- for (i1 = 0; i1 < i3; i1++)
- result[i1] = buf[i3 - i1 - 1];
-
- result[i1] = '\0';
-}
-
-
-void
-svn_fs_fs__next_key(const char *this, apr_size_t *len, char *next)
-{
- apr_ssize_t i;
- apr_size_t olen = *len; /* remember the first length */
- char c; /* current char */
- svn_boolean_t carry = TRUE; /* boolean: do we have a carry or not?
- We start with a carry, because we're
- incrementing the number, after all. */
-
- /* Leading zeros are not allowed, except for the string "0". */
- if ((*len > 1) && (this[0] == '0'))
- {
- *len = 0;
- return;
- }
-
- for (i = (olen - 1); i >= 0; i--)
- {
- c = this[i];
-
- /* Validate as we go. */
- if (! (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z'))))
- {
- *len = 0;
- return;
- }
-
- if (carry)
- {
- if (c == 'z')
- next[i] = '0';
- else
- {
- carry = FALSE;
-
- if (c == '9')
- next[i] = 'a';
- else
- next[i] = ++c;
- }
- }
- else
- next[i] = c;
- }
-
- /* The new length is OLEN, plus 1 if there's a carry out of the
- leftmost digit. */
- *len = olen + (carry ? 1 : 0);
-
- /* Ensure that we haven't overrun the (ludicrous) bound on key length.
- Note that MAX_KEY_SIZE is a bound on the size *including*
- the trailing null byte. */
- assert(*len < MAX_KEY_SIZE);
-
- /* Now we know it's safe to add the null terminator. */
- next[*len] = '\0';
-
- /* Handle any leftover carry. */
- if (carry)
- {
- memmove(next+1, next, olen);
- next[0] = '1';
- }
-}
-
-
-int
-svn_fs_fs__key_compare(const char *a, const char *b)
-{
- apr_size_t a_len = strlen(a);
- apr_size_t b_len = strlen(b);
- int cmp;
-
- if (a_len > b_len)
- return 1;
- if (b_len > a_len)
- return -1;
- cmp = strcmp(a, b);
- return (cmp ? (cmp / abs(cmp)) : 0);
-}
diff --git a/subversion/libsvn_fs_fs/key-gen.h b/subversion/libsvn_fs_fs/key-gen.h
deleted file mode 100644
index e1b3858..0000000
--- a/subversion/libsvn_fs_fs/key-gen.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/* key-gen.c --- manufacturing sequential keys for some db tables
- *
- * ====================================================================
- * 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.
- * ====================================================================
- */
-
-#ifndef SVN_LIBSVN_FS_KEY_GEN_H
-#define SVN_LIBSVN_FS_KEY_GEN_H
-
-#include <apr.h>
-
-#include "svn_types.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-
-/* The alphanumeric keys passed in and out of svn_fs_fs__next_key
- are guaranteed never to be longer than this many bytes,
- *including* the trailing null byte. It is therefore safe
- to declare a key as "char key[MAX_KEY_SIZE]".
-
- Note that this limit will be a problem if the number of
- keys in a table ever exceeds
-
- 18217977168218728251394687124089371267338971528174
- 76066745969754933395997209053270030282678007662838
- 67331479599455916367452421574456059646801054954062
- 15017704234999886990788594743994796171248406730973
- 80736524850563115569208508785942830080999927310762
- 50733948404739350551934565743979678824151197232629
- 947748581376,
-
- but that's a risk we'll live with for now. */
-#define MAX_KEY_SIZE 200
-
-
-/* Generate the next key after a given alphanumeric key.
- *
- * The first *LEN bytes of THIS are an ascii representation of a
- * number in base 36: digits 0-9 have their usual values, and a-z have
- * values 10-35.
- *
- * The new key is stored in NEXT, null-terminated. NEXT must be at
- * least *LEN + 2 bytes long -- one extra byte to hold a possible
- * overflow column, and one for null termination. On return, *LEN
- * will be set to the length of the new key, not counting the null
- * terminator. In other words, the outgoing *LEN will be either equal
- * to the incoming, or to the incoming + 1.
- *
- * If THIS contains anything other than digits and lower-case
- * alphabetic characters, or if it starts with `0' but is not the
- * string "0", then *LEN is set to zero and the effect on NEXT
- * is undefined.
- */
-void svn_fs_fs__next_key(const char *this, apr_size_t *len, char *next);
-
-
-/* Compare two strings A and B as base-36 alphanumeric keys.
- *
- * Return -1, 0, or 1 if A is less than, equal to, or greater than B,
- * respectively.
- */
-int svn_fs_fs__key_compare(const char *a, const char *b);
-
-/* Add two base-36 alphanumeric keys to get a third, the result. */
-void svn_fs_fs__add_keys(const char *key1, const char *key2, char *result);
-
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* SVN_LIBSVN_FS_KEY_GEN_H */
diff --git a/subversion/libsvn_fs_fs/libsvn_fs_fs.pc.in b/subversion/libsvn_fs_fs/libsvn_fs_fs.pc.in
new file mode 100644
index 0000000..324a709
--- /dev/null
+++ b/subversion/libsvn_fs_fs/libsvn_fs_fs.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libsvn_fs_fs
+Description: Subversion FSFS Repository Filesystem Library
+Version: @PACKAGE_VERSION@
+Requires: apr-util-@SVN_APR_MAJOR_VERSION@ apr-@SVN_APR_MAJOR_VERSION@
+Requires.private: libsvn_delta libsvn_subr libsvn_fs_util
+Libs: -L${libdir} -lsvn_fs_fs
+Cflags: -I${includedir}
diff --git a/subversion/libsvn_fs_fs/load-index.c b/subversion/libsvn_fs_fs/load-index.c
new file mode 100644
index 0000000..3142e8e
--- /dev/null
+++ b/subversion/libsvn_fs_fs/load-index.c
@@ -0,0 +1,98 @@
+/* load-index-cmd.c -- implements the dump-index sub-command.
+ *
+ * ====================================================================
+ * 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_pools.h"
+
+#include "private/svn_fs_fs_private.h"
+#include "private/svn_sorts_private.h"
+
+#include "index.h"
+#include "util.h"
+#include "transaction.h"
+
+/* A svn_sort__array compatible comparator function, sorting the
+ * svn_fs_fs__p2l_entry_t** given in LHS, RHS by offset. */
+static int
+compare_p2l_entry_revision(const void *lhs,
+ const void *rhs)
+{
+ const svn_fs_fs__p2l_entry_t *lhs_entry
+ =*(const svn_fs_fs__p2l_entry_t **)lhs;
+ const svn_fs_fs__p2l_entry_t *rhs_entry
+ =*(const svn_fs_fs__p2l_entry_t **)rhs;
+
+ if (lhs_entry->offset < rhs_entry->offset)
+ return -1;
+
+ return lhs_entry->offset == rhs_entry->offset ? 0 : 1;
+}
+
+svn_error_t *
+svn_fs_fs__load_index(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_array_header_t *entries,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Check the FS format number. */
+ if (! svn_fs_fs__use_log_addressing(fs))
+ return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, NULL);
+
+ /* P2L index must be written in offset order.
+ * Sort ENTRIES accordingly. */
+ svn_sort__array(entries, compare_p2l_entry_revision);
+
+ /* Treat an empty array as a no-op instead error. */
+ if (entries->nelts != 0)
+ {
+ const char *l2p_proto_index;
+ const char *p2l_proto_index;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ /* Open rev / pack file & trim indexes + footer off it. */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs,
+ revision, iterpool,
+ iterpool));
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+ SVN_ERR(svn_io_file_trunc(rev_file->file, rev_file->l2p_offset,
+ iterpool));
+
+ /* Create proto index files for the new index data
+ * (will be cleaned up automatically with iterpool). */
+ SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
+ rev_file, entries,
+ iterpool, iterpool));
+ SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
+ entries, iterpool,
+ iterpool));
+
+ /* Combine rev data with new index data. */
+ SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
+ p2l_proto_index,
+ rev_file->start_revision, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/lock.c b/subversion/libsvn_fs_fs/lock.c
index 95bd943..c852025 100644
--- a/subversion/libsvn_fs_fs/lock.c
+++ b/subversion/libsvn_fs_fs/lock.c
@@ -20,7 +20,6 @@
* ====================================================================
*/
-
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
@@ -37,10 +36,12 @@
#include "lock.h"
#include "tree.h"
#include "fs_fs.h"
+#include "util.h"
#include "../libsvn_fs/fs-loader.h"
#include "private/svn_fs_util.h"
#include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
#include "svn_private_config.h"
/* Names of hash keys used to store a lock for writing to disk. */
@@ -102,8 +103,7 @@ hash_store(apr_hash_t *hash,
of that value (if it exists). */
static const char *
hash_fetch(apr_hash_t *hash,
- const char *key,
- apr_pool_t *pool)
+ const char *key)
{
svn_string_t *str = svn_hash_gets(hash, key);
return str ? str->data : NULL;
@@ -133,7 +133,7 @@ digest_path_from_digest(const char *fs_path,
{
return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
- digest, NULL);
+ digest, SVN_VA_NULL);
}
@@ -151,7 +151,7 @@ digest_path_from_path(const char **digest_path,
*digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
apr_pstrmemdup(pool, digest,
DIGEST_SUBDIR_LEN),
- digest, NULL);
+ digest, SVN_VA_NULL);
return SVN_NO_ERROR;
}
@@ -209,8 +209,8 @@ write_digest_file(apr_hash_t *children,
for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
{
svn_stringbuf_appendbytes(children_list,
- svn__apr_hash_index_key(hi),
- svn__apr_hash_index_klen(hi));
+ apr_hash_this_key(hi),
+ apr_hash_this_key_len(hi));
svn_stringbuf_appendbyte(children_list, '\n');
}
hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
@@ -252,24 +252,23 @@ read_digest_file(apr_hash_t **children_p,
apr_hash_t *hash;
svn_stream_t *stream;
const char *val;
+ svn_node_kind_t kind;
if (lock_p)
*lock_p = NULL;
if (children_p)
*children_p = apr_hash_make(pool);
- err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- SVN_ERR(err);
+ SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+ if (kind == svn_node_none)
+ return SVN_NO_ERROR;
/* If our caller doesn't care about anything but the presence of the
file... whatever. */
- if (! (lock_p || children_p))
- return svn_stream_close(stream);
+ if (kind == svn_node_file && !lock_p && !children_p)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
hash = apr_hash_make(pool);
if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
@@ -284,7 +283,7 @@ read_digest_file(apr_hash_t **children_p,
/* If our caller cares, see if we have a lock path in our hash. If
so, we'll assume we have a lock here. */
- val = hash_fetch(hash, PATH_KEY, pool);
+ val = hash_fetch(hash, PATH_KEY);
if (val && lock_p)
{
const char *path = val;
@@ -293,30 +292,30 @@ read_digest_file(apr_hash_t **children_p,
lock = svn_lock_create(pool);
lock->path = path;
- if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
+ if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
return svn_error_trace(err_corrupt_lockfile(fs_path, path));
- if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
+ if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
return svn_error_trace(err_corrupt_lockfile(fs_path, path));
- if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
+ if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
return svn_error_trace(err_corrupt_lockfile(fs_path, path));
lock->is_dav_comment = (val[0] == '1');
- if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
+ if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
return svn_error_trace(err_corrupt_lockfile(fs_path, path));
SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
- if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
+ if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
- lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
+ lock->comment = hash_fetch(hash, COMMENT_KEY);
*lock_p = lock;
}
/* If our caller cares, see if we have any children for this path. */
- val = hash_fetch(hash, CHILDREN_KEY, pool);
+ val = hash_fetch(hash, CHILDREN_KEY);
if (val && children_p)
{
apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
@@ -340,8 +339,6 @@ read_digest_file(apr_hash_t **children_p,
/* Write LOCK in FS to the actual OS filesystem.
Use PERMS_REFERENCE for the permissions of any digest files.
-
- Note: this takes an FS_PATH because it's called from the hotcopy logic.
*/
static svn_error_t *
set_lock(const char *fs_path,
@@ -349,130 +346,117 @@ set_lock(const char *fs_path,
const char *perms_reference,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *lock_digest_path = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
+ apr_hash_t *children;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
- /* Iterate in reverse, creating the lock for LOCK->path, and then
- just adding entries for its parent, until we reach a parent
- that's already listed in *its* parent. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ /* We could get away without reading the file as children should
+ always come back empty. */
+ SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
- svn_pool_clear(subpool);
+ SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
+ perms_reference, pool));
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
+ return SVN_NO_ERROR;
+}
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
- digest_path, subpool));
+static svn_error_t *
+delete_lock(const char *fs_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *digest_path;
- /* We're either writing a new lock (first time through only) or
- a new entry (every time but the first). */
- if (lock)
- {
- this_lock = lock;
- lock = NULL;
- lock_digest_path = apr_pstrdup(pool, digest_file);
- }
- else
- {
- /* If we already have an entry for this path, we're done. */
- if (svn_hash_gets(this_children, lock_digest_path))
- break;
- svn_hash_sets(this_children, lock_digest_path, (void *)1);
- }
- SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
- digest_path, perms_reference, subpool));
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+
+ SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
-/* Delete LOCK from FS in the actual OS filesystem. */
static svn_error_t *
-delete_lock(svn_fs_t *fs,
- svn_lock_t *lock,
- apr_pool_t *pool)
+add_to_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *child_to_kill = NULL;
- apr_pool_t *subpool;
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+ unsigned int original_count;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
- /* Iterate in reverse, deleting the lock for LOCK->path, and then
- deleting its entry as it appears in each of its parents. */
- subpool = svn_pool_create(pool);
- while (1729)
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+ original_count = apr_hash_count(children);
+
+ for (i = 0; i < paths->nelts; ++i)
{
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
- svn_pool_clear(subpool);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, (void *)1);
+ }
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
+ if (apr_hash_count(children) != original_count)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
- digest_path, subpool));
+ return SVN_NO_ERROR;
+}
- /* Delete the lock (first time through only). */
- if (lock)
- {
- this_lock = NULL;
- lock = NULL;
- child_to_kill = apr_pstrdup(pool, digest_file);
- }
+static svn_error_t *
+delete_from_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
- if (child_to_kill)
- svn_hash_sets(this_children, child_to_kill, NULL);
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
- if (! (this_lock || apr_hash_count(this_children) != 0))
- {
- /* Special case: no goodz, no file. And remember to nix
- the entry for it in its parent. */
- SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
- }
- else
- {
- const char *rev_0_path;
- SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
- SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
- digest_path, rev_0_path, subpool));
- }
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, NULL);
}
- svn_pool_destroy(subpool);
+ if (apr_hash_count(children) || lock)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+ else
+ SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
+
return SVN_NO_ERROR;
}
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
+/* Check if LOCK has been already expired. */
+static svn_boolean_t lock_expired(const svn_lock_t *lock)
+{
+ return lock->expiration_date && (apr_time_now() > lock->expiration_date);
+}
+
/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
TRUE if the caller (or one of its callers) has taken out the
repository-wide write lock, FALSE otherwise. If MUST_EXIST is
@@ -502,12 +486,12 @@ get_lock(svn_lock_t **lock_p,
return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
/* Don't return an expired lock. */
- if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
+ if (lock_expired(lock))
{
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(fs, lock, pool));
+ SVN_ERR(unlock_single(fs, lock, pool));
return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
}
@@ -549,69 +533,17 @@ get_lock_helper(svn_fs_t *fs,
}
-/* Baton for locks_walker(). */
-struct walk_locks_baton {
- svn_fs_get_locks_callback_t get_locks_func;
- void *get_locks_baton;
- svn_fs_t *fs;
-};
-
-/* Implements walk_digests_callback_t. */
-static svn_error_t *
-locks_walker(void *baton,
- const char *fs_path,
- const char *digest_path,
- apr_hash_t *children,
- svn_lock_t *lock,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool)
-{
- struct walk_locks_baton *wlb = baton;
-
- if (lock)
- {
- /* Don't report an expired lock. */
- if (lock->expiration_date == 0
- || (apr_time_now() <= lock->expiration_date))
- {
- if (wlb->get_locks_func)
- SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
- }
- else
- {
- /* Only remove the lock if we have the write lock.
- Read operations shouldn't change the filesystem. */
- if (have_write_lock)
- SVN_ERR(delete_lock(wlb->fs, lock, pool));
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Callback type for walk_digest_files().
- *
- * CHILDREN and LOCK come from a read_digest_file(digest_path) call.
- */
-typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
- const char *fs_path,
- const char *digest_path,
- apr_hash_t *children,
- svn_lock_t *lock,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool);
-
-/* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
- all lock digest files in and under PATH in FS.
+/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
+ all locks in and under PATH in FS.
HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
has the FS write lock. */
static svn_error_t *
-walk_digest_files(const char *fs_path,
- const char *digest_path,
- walk_digests_callback_t walk_digests_func,
- void *walk_digests_baton,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool)
+walk_locks(svn_fs_t *fs,
+ const char *digest_path,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
{
apr_hash_index_t *hi;
apr_hash_t *children;
@@ -619,47 +551,46 @@ walk_digest_files(const char *fs_path,
svn_lock_t *lock;
/* First, send up any locks in the current digest file. */
- SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
+ SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
- SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
- children, lock,
- have_write_lock, pool));
+ if (lock && lock_expired(lock))
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(unlock_single(fs, lock, pool));
+ }
+ else if (lock)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+ }
- /* Now, recurse on this thing's child entries (if any; bail otherwise). */
+ /* Now, report all the child entries (if any; bail otherwise). */
if (! apr_hash_count(children))
return SVN_NO_ERROR;
subpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
{
- const char *digest = svn__apr_hash_index_key(hi);
+ const char *digest = apr_hash_this_key(hi);
svn_pool_clear(subpool);
- SVN_ERR(walk_digest_files
- (fs_path, digest_path_from_digest(fs_path, digest, subpool),
- walk_digests_func, walk_digests_baton, have_write_lock, subpool));
- }
- svn_pool_destroy(subpool);
- return SVN_NO_ERROR;
-}
-/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
- all locks in and under PATH in FS.
- HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
- has the FS write lock. */
-static svn_error_t *
-walk_locks(svn_fs_t *fs,
- const char *digest_path,
- svn_fs_get_locks_callback_t get_locks_func,
- void *get_locks_baton,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool)
-{
- struct walk_locks_baton wlb;
+ SVN_ERR(read_digest_file
+ (NULL, &lock, fs->path,
+ digest_path_from_digest(fs->path, digest, subpool), subpool));
- wlb.get_locks_func = get_locks_func;
- wlb.get_locks_baton = get_locks_baton;
- wlb.fs = fs;
- SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
- have_write_lock, pool));
+ if (lock && lock_expired(lock))
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(unlock_single(fs, lock, pool));
+ }
+ else if (lock)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+ }
+ }
+ svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
@@ -737,70 +668,100 @@ svn_fs_fs__allow_locked_operation(const char *path,
return SVN_NO_ERROR;
}
-/* Baton used for lock_body below. */
+/* Helper function called from the lock and unlock code.
+ UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
+ arrays of child paths. For all of the parent paths of PATH this function
+ adds PATH to the corresponding array of child paths. */
+static void
+schedule_index_update(apr_hash_t *updates,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *hashpool = apr_hash_pool_get(updates);
+ const char *parent_path = path;
+
+ while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
+ {
+ apr_array_header_t *children;
+
+ parent_path = svn_fspath__dirname(parent_path, scratch_pool);
+ children = svn_hash_gets(updates, parent_path);
+
+ if (! children)
+ {
+ children = apr_array_make(hashpool, 8, sizeof(const char *));
+ svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
+ }
+
+ APR_ARRAY_PUSH(children, const char *) = path;
+ }
+}
+
+/* The effective arguments for lock_body() below. */
struct lock_baton {
- svn_lock_t **lock_p;
svn_fs_t *fs;
- const char *path;
- const char *token;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
const char *comment;
svn_boolean_t is_dav_comment;
apr_time_t expiration_date;
- svn_revnum_t current_rev;
svn_boolean_t steal_lock;
- apr_pool_t *pool;
+ apr_pool_t *result_pool;
};
-
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
- type, and assumes that the write lock is held.
- BATON is a 'struct lock_baton *'. */
static svn_error_t *
-lock_body(void *baton, apr_pool_t *pool)
+check_lock(svn_error_t **fs_err,
+ const char *path,
+ const svn_fs_lock_target_t *target,
+ struct lock_baton *lb,
+ svn_fs_root_t *root,
+ svn_revnum_t youngest_rev,
+ apr_pool_t *pool)
{
- struct lock_baton *lb = baton;
svn_node_kind_t kind;
svn_lock_t *existing_lock;
- svn_lock_t *lock;
- svn_fs_root_t *root;
- svn_revnum_t youngest;
- const char *rev_0_path;
- /* Until we implement directory locks someday, we only allow locks
- on files or non-existent paths. */
- /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
- library dependencies, which are not portable. */
- SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
- SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
- SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
+ *fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
if (kind == svn_node_dir)
- return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+ {
+ *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
+ return SVN_NO_ERROR;
+ }
/* While our locking implementation easily supports the locking of
nonexistent paths, we deliberately choose not to allow such madness. */
if (kind == svn_node_none)
{
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
- return svn_error_createf(
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
+ *fs_err = svn_error_createf(
SVN_ERR_FS_OUT_OF_DATE, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
+ path);
else
- return svn_error_createf(
+ *fs_err = svn_error_createf(
SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
- }
+ path);
- /* We need to have a username attached to the fs. */
- if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
- return SVN_FS__ERR_NO_USER(lb->fs);
+ return SVN_NO_ERROR;
+ }
/* Is the caller attempting to lock an out-of-date working file? */
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
{
svn_revnum_t created_rev;
- SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
+
+ if (target->current_rev > youngest_rev)
+ {
+ *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"),
+ target->current_rev);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
pool));
/* SVN_INVALID_REVNUM means the path doesn't exist. So
@@ -808,14 +769,22 @@ lock_body(void *baton, apr_pool_t *pool)
working copy, but somebody else has deleted the thing
from HEAD. That counts as being 'out of date'. */
if (! SVN_IS_VALID_REVNUM(created_rev))
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Path '%s' doesn't exist in HEAD revision"), lb->path);
-
- if (lb->current_rev < created_rev)
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Lock failed: newer version of '%s' exists"), lb->path);
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"), path);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (target->current_rev < created_rev)
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Lock failed: newer version of '%s' exists"), path);
+
+ return SVN_NO_ERROR;
+ }
}
/* If the caller provided a TOKEN, we *really* need to see
@@ -834,116 +803,380 @@ lock_body(void *baton, apr_pool_t *pool)
acceptable to ignore; it means that the path is now free and
clear for locking, because the fsfs funcs just cleared out both
of the tables for us. */
- SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+ SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
if (existing_lock)
{
if (! lb->steal_lock)
{
/* Sorry, the path is already locked. */
- return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ return SVN_NO_ERROR;
}
- else
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct lock_info_t {
+ const char *path;
+ svn_lock_t *lock;
+ svn_error_t *fs_err;
+};
+
+/* The body of svn_fs_fs__lock(), which see.
+
+ BATON is a 'struct lock_baton *' holding the effective arguments.
+ BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
+ path, mapping canonical path to 'svn_fs_lock_target_t'. Set
+ BATON->infos to an array of 'lock_info_t' holding the results. For
+ the other arguments, see svn_fs_lock_many().
+
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type, and assumes that the write lock is held.
+ */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+ struct lock_baton *lb = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i;
+ apr_hash_t *index_updates = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+ library dependencies, which are not portable. */
+ SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+ SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+
+ for (i = 0; i < lb->targets->nelts; ++i)
+ {
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ struct lock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ info.lock = NULL;
+ info.fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
+ youngest, iterpool));
+
+ /* If no error occurred while pre-checking, schedule the index updates for
+ this path. */
+ if (!info.fs_err)
+ schedule_index_update(index_updates, info.path, iterpool);
+
+ APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
+ }
+
+ rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
+
+ /* We apply the scheduled index updates before writing the actual locks.
+
+ Writing indices before locks is correct: if interrupted it leaves
+ indices without locks rather than locks without indices. An
+ index without a lock is consistent in that it always shows up as
+ unlocked in svn_fs_fs__allow_locked_operation. A lock without an
+ index is inconsistent, svn_fs_fs__allow_locked_operation will
+ show locked on the file but unlocked on the parent. */
+
+ for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ apr_array_header_t *children = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
+ iterpool));
+ }
+
+ for (i = 0; i < lb->infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
+ struct lock_info_t);
+ svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
+ svn_fs_lock_target_t *target = item->value;
+
+ svn_pool_clear(iterpool);
+
+ if (! info->fs_err)
{
- /* STEAL_LOCK was passed, so fs_username is "stealing" the
- lock from lock->owner. Destroy the existing lock. */
- SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+ info->lock = svn_lock_create(lb->result_pool);
+ if (target->token)
+ info->lock->token = apr_pstrdup(lb->result_pool, target->token);
+ else
+ SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
+ lb->result_pool));
+
+ /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
+ of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
+ info->lock->path = info->path;
+ info->lock->owner = apr_pstrdup(lb->result_pool,
+ lb->fs->access_ctx->username);
+ info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
+ info->lock->is_dav_comment = lb->is_dav_comment;
+ info->lock->creation_date = apr_time_now();
+ info->lock->expiration_date = lb->expiration_date;
+
+ info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
+ iterpool);
}
}
- /* Create our new lock, and add it to the tables.
- Ensure that the lock is created in the correct pool. */
- lock = svn_lock_create(lb->pool);
- if (lb->token)
- lock->token = apr_pstrdup(lb->pool, lb->token);
- else
- SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
- lb->pool));
- lock->path = apr_pstrdup(lb->pool, lb->path);
- lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
- lock->comment = apr_pstrdup(lb->pool, lb->comment);
- lock->is_dav_comment = lb->is_dav_comment;
- lock->creation_date = apr_time_now();
- lock->expiration_date = lb->expiration_date;
- SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
- SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
- *lb->lock_p = lock;
-
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
-/* Baton used for unlock_body below. */
+/* The effective arguments for unlock_body() below. */
struct unlock_baton {
svn_fs_t *fs;
- const char *path;
- const char *token;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
+ /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
+ svn_boolean_t skip_check;
svn_boolean_t break_lock;
+ apr_pool_t *result_pool;
};
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+ const char *path,
+ const char *token,
+ struct unlock_baton *ub,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+
+ *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
+ if (!*fs_err && !ub->break_lock)
+ {
+ if (strcmp(token, lock->token) != 0)
+ *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
+ else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+ *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
+ ub->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_info_t {
+ const char *path;
+ svn_error_t *fs_err;
+ svn_boolean_t done;
+};
+
+/* The body of svn_fs_fs__unlock(), which see.
+
+ BATON is a 'struct unlock_baton *' holding the effective arguments.
+ BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
+ path, mapping canonical path to (const char *) token. Set
+ BATON->infos to an array of 'unlock_info_t' results. For the other
+ arguments, see svn_fs_unlock_many().
+
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
type, and assumes that the write lock is held.
- BATON is a 'struct unlock_baton *'. */
+ */
static svn_error_t *
unlock_body(void *baton, apr_pool_t *pool)
{
struct unlock_baton *ub = baton;
- svn_lock_t *lock;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i;
+ apr_hash_t *indices_updates = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
- /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
- SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+ SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+ SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
- /* Unless breaking the lock, we do some checks. */
- if (! ub->break_lock)
+ for (i = 0; i < ub->targets->nelts; ++i)
{
- /* Sanity check: the incoming token should match lock->token. */
- if (strcmp(ub->token, lock->token) != 0)
- return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
-
- /* There better be a username attached to the fs. */
- if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
- return SVN_FS__ERR_NO_USER(ub->fs);
-
- /* And that username better be the same as the lock's owner. */
- if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
- return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
- ub->fs, ub->fs->access_ctx->username, lock->owner);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+ svn_sort__item_t);
+ const char *token = item->value;
+ struct unlock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ info.fs_err = SVN_NO_ERROR;
+ info.done = FALSE;
+
+ if (!ub->skip_check)
+ SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+ iterpool));
+
+ /* If no error occurred while pre-checking, schedule the index updates for
+ this path. */
+ if (!info.fs_err)
+ schedule_index_update(indices_updates, info.path, iterpool);
+
+ APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
}
- /* Remove lock and lock token files. */
- return delete_lock(ub->fs, lock, pool);
+ rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
+
+ /* Unlike the lock_body(), we need to delete locks *before* we start to
+ update indices. */
+
+ for (i = 0; i < ub->infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
+ struct unlock_info_t);
+
+ svn_pool_clear(iterpool);
+
+ if (! info->fs_err)
+ {
+ SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+ info->done = TRUE;
+ }
+ }
+
+ for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ apr_array_header_t *children = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the lock described by LOCK->path and LOCK->token in FS.
+
+ This assumes that the write lock is held.
+ */
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ struct unlock_baton ub;
+ svn_sort__item_t item;
+ apr_array_header_t *targets = apr_array_make(pool, 1,
+ sizeof(svn_sort__item_t));
+ item.key = lock->path;
+ item.klen = strlen(item.key);
+ item.value = (char*)lock->token;
+ APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
+
+ ub.fs = fs;
+ ub.targets = targets;
+ ub.infos = apr_array_make(pool, targets->nelts,
+ sizeof(struct unlock_info_t));
+ ub.skip_check = TRUE;
+ ub.result_pool = pool;
+
+ /* No ub.infos[].fs_err error because skip_check is TRUE. */
+ SVN_ERR(unlock_body(&ub, pool));
+
+ return SVN_NO_ERROR;
}
/*** Public API implementations ***/
svn_error_t *
-svn_fs_fs__lock(svn_lock_t **lock_p,
- svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_fs_fs__lock(svn_fs_t *fs,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool)
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct lock_baton lb;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
+ int i;
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
- lb.lock_p = lock_p;
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ /* The FS locking API allows both canonical and non-canonical
+ paths which means that the same canonical path could be
+ represented more than once in the TARGETS hash. We just keep
+ one, choosing one with a token if possible. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
+ const svn_fs_lock_target_t *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(canonical_targets, path);
+
+ if (!other || (!other->token && target->token))
+ svn_hash_sets(canonical_targets, path, target);
+ }
+
+ sorted_targets = svn_sort__hash(canonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+
lb.fs = fs;
- lb.path = path;
- lb.token = token;
+ lb.targets = sorted_targets;
+ lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
+ sizeof(struct lock_info_t));
lb.comment = comment;
lb.is_dav_comment = is_dav_comment;
lb.expiration_date = expiration_date;
- lb.current_rev = current_rev;
lb.steal_lock = steal_lock;
- lb.pool = pool;
+ lb.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool);
+ for (i = 0; i < lb.infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
+ struct lock_info_t);
+ svn_pool_clear(iterpool);
+ if (!cb_err && lock_callback)
+ {
+ if (!info->lock && !info->fs_err)
+ info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
+ 0, _("Failed to lock '%s'"),
+ info->path);
+
+ cb_err = lock_callback(lock_baton, info->path, info->lock,
+ info->fs_err, iterpool);
+ }
+ svn_error_clear(info->fs_err);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
- return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
+ return svn_error_trace(err);
}
@@ -959,29 +1192,84 @@ svn_fs_fs__generate_lock_token(const char **token,
generate a URI that matches the DAV RFC. We could change this to
some other URI scheme someday, if we wish. */
*token = apr_pstrcat(pool, "opaquelocktoken:",
- svn_uuid_generate(pool), (char *)NULL);
+ svn_uuid_generate(pool), SVN_VA_NULL);
return SVN_NO_ERROR;
}
-
svn_error_t *
svn_fs_fs__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool)
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct unlock_baton ub;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
+ int i;
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ const char *token = apr_hash_this_val(hi);
+ const char *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(canonical_targets, path);
+
+ if (!other)
+ svn_hash_sets(canonical_targets, path, token);
+ }
+
+ sorted_targets = svn_sort__hash(canonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
ub.fs = fs;
- ub.path = path;
- ub.token = token;
+ ub.targets = sorted_targets;
+ ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
+ sizeof(struct unlock_info_t));
+ ub.skip_check = FALSE;
ub.break_lock = break_lock;
+ ub.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
+ for (i = 0; i < ub.infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
+ struct unlock_info_t);
+ svn_pool_clear(iterpool);
+ if (!cb_err && lock_callback)
+ {
+ if (!info->done && !info->fs_err)
+ info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
+ 0, _("Failed to unlock '%s'"),
+ info->path);
+ cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
+ iterpool);
+ }
+ svn_error_clear(info->fs_err);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
- return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
+ return svn_error_trace(err);
}
diff --git a/subversion/libsvn_fs_fs/lock.h b/subversion/libsvn_fs_fs/lock.h
index 1acc79e..beaaa0d 100644
--- a/subversion/libsvn_fs_fs/lock.h
+++ b/subversion/libsvn_fs_fs/lock.h
@@ -32,32 +32,39 @@ extern "C" {
/* These functions implement some of the calls in the FS loader
library's fs vtables. */
-svn_error_t *svn_fs_fs__lock(svn_lock_t **lock,
- svn_fs_t *fs,
- const char *path,
- const char *token,
+/* See svn_fs_lock(), svn_fs_lock_many(). */
+svn_error_t *svn_fs_fs__lock(svn_fs_t *fs,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool);
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* See svn_fs_generate_lock_token(). */
svn_error_t *svn_fs_fs__generate_lock_token(const char **token,
svn_fs_t *fs,
apr_pool_t *pool);
+/* See svn_fs_unlock(), svn_fs_unlock_many(). */
svn_error_t *svn_fs_fs__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool);
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* See svn_fs_get_lock(). */
svn_error_t *svn_fs_fs__get_lock(svn_lock_t **lock,
svn_fs_t *fs,
const char *path,
apr_pool_t *pool);
+/* See svn_fs_get_locks2(). */
svn_error_t *svn_fs_fs__get_locks(svn_fs_t *fs,
const char *path,
svn_depth_t depth,
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));
+}
diff --git a/subversion/libsvn_fs_fs/low_level.h b/subversion/libsvn_fs_fs/low_level.h
new file mode 100644
index 0000000..35b9d0d
--- /dev/null
+++ b/subversion/libsvn_fs_fs/low_level.h
@@ -0,0 +1,230 @@
+/* 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 (up to format 6)
+ * - revision footer (since format 7)
+ * - 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 RESULT_POOL.
+ */
+svn_stringbuf_t *
+svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
+ apr_off_t changes_offset,
+ apr_pool_t *result_pool);
+
+/* Given the format 7+ revision / pack FOOTER, parse it destructively
+ * and return the start offsets of the index data in *L2P_OFFSET and
+ * *P2L_OFFSET, respectively. Also, return the expected checksums in
+ * in *L2P_CHECKSUM and *P2L_CHECKSUM.
+ *
+ * Note that REV is only used to construct nicer error objects that
+ * mention this revision. Allocate the checksums in RESULT_POOL.
+ */
+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);
+
+/* Given the offset of the L2P index data in L2P_OFFSET, the content
+ * checksum in L2P_CHECKSUM and the offset plus checksum of the P2L
+ * index data in P2L_OFFSET and P2L_CHECKSUM.
+ *
+ * Return the corresponding format 7+ revision / pack file footer.
+ * Allocate it in RESULT_POOL and use SCRATCH_POOL for temporary.
+ */
+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);
+
+/* Read all the changes from STREAM and store them in *CHANGES,
+ allocated in RESULT_POOL. Do temporary allocations in SCRATCH_POOL. */
+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);
+
+/* Callback function used by svn_fs_fs__read_changes_incrementally(),
+ * asking the receiver to process to process CHANGE using BATON. CHANGE
+ * and SCRATCH_POOL will not be valid beyond the current callback invocation.
+ */
+typedef svn_error_t *(*svn_fs_fs__change_receiver_t)(
+ void *baton,
+ change_t *change,
+ apr_pool_t *scratch_pool);
+
+/* Read all the changes from STREAM and invoke CHANGE_RECEIVER on each change.
+ Do all allocations in SCRATCH_POOL. */
+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);
+
+/* 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. If you are writing to a (proto-)revision file,
+ the last call must set TERMINATE_LIST to write an extra empty line
+ that marks the end of the changed paths list.
+ Perform temporary allocations in SCRATCH_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 *scratch_pool);
+
+/* Read a node-revision from STREAM. Set *NODEREV to the new structure,
+ allocated in RESULT_POOL. */
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_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 SCRATCH_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 *scratch_pool);
+
+/* Parse the description of a representation from TEXT and store it
+ into *REP_P. TEXT will be invalidated by this call. Allocate *REP_P in
+ RESULT_POOL and use SCRATCH_POOL for temporaries. */
+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);
+
+/* 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.
+ Allocate the result in RESULT_POOL and temporaries in SCRATCH_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);
+
+/* 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 item index within the base rep's revision. Should
+ * be 0 if there is no base rep. */
+ apr_off_t base_item_index;
+
+ /* 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 STREAM and parse it as a text
+ representation header. Return the parsed entry in *HEADER, allocated
+ in RESULT_POOL. Perform temporary allocations in SCRATCH_POOL. */
+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);
+
+/* Write the representation HEADER to STREAM.
+ * Use SCRATCH_POOL for temporary allocations. */
+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);
diff --git a/subversion/libsvn_fs_fs/pack.c b/subversion/libsvn_fs_fs/pack.c
new file mode 100644
index 0000000..8c4ef37
--- /dev/null
+++ b/subversion/libsvn_fs_fs/pack.c
@@ -0,0 +1,2061 @@
+/* pack.c --- FSFS shard packing functionality
+ *
+ * ====================================================================
+ * 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 <assert.h>
+#include <string.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_sorts.h"
+#include "private/svn_temp_serializer.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_io_private.h"
+
+#include "fs_fs.h"
+#include "pack.h"
+#include "util.h"
+#include "id.h"
+#include "index.h"
+#include "low_level.h"
+#include "revprops.h"
+#include "transaction.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+#include "temp_serializer.h"
+
+/* Logical addressing packing logic:
+ *
+ * We pack files on a pack file basis (e.g. 1000 revs) without changing
+ * existing pack files nor the revision files outside the range to pack.
+ *
+ * First, we will scan the revision file indexes to determine the number
+ * of items to "place" (i.e. determine their optimal position within the
+ * future pack file). For each item, we will need a constant amount of
+ * memory to track it. A MAX_MEM parameter sets a limit to the number of
+ * items we may place in one go. That means, we may not be able to add
+ * all revisions at once. Instead, we will run the placement for a subset
+ * of revisions at a time. The very unlikely worst case will simply append
+ * all revision data with just a little reshuffling inside each revision.
+ *
+ * In a second step, we read all revisions in the selected range, build
+ * the item tracking information and copy the items themselves from the
+ * revision files to temporary files. The latter serve as buckets for a
+ * very coarse bucket presort: Separate change lists, file properties,
+ * directory properties and noderevs + representations from one another.
+ *
+ * The third step will determine an optimized placement for the items in
+ * each of the 4 buckets separately. The first three will simply order
+ * their items by revision, starting with the newest once. Placing rep
+ * and noderev items is a more elaborate process documented in the code.
+ *
+ * In short, we store items in the following order:
+ * - changed paths lists
+ * - node property
+ * - directory properties
+ * - directory representations corresponding noderevs, lexical path order
+ * with special treatment of "trunk" and "branches"
+ * - same for file representations
+ *
+ * Step 4 copies the items from the temporary buckets into the final
+ * pack file and writes the temporary index files.
+ *
+ * Finally, after the last range of revisions, create the final indexes.
+ */
+
+/* Maximum amount of memory we allocate for placement information during
+ * the pack process.
+ */
+#define DEFAULT_MAX_MEM (64 * 1024 * 1024)
+
+/* Data structure describing a node change at PATH, REVISION.
+ * We will sort these instances by PATH and NODE_ID such that we can combine
+ * similar nodes in the same reps container and store containers in path
+ * major order.
+ */
+typedef struct path_order_t
+{
+ /* changed path */
+ svn_prefix_string__t *path;
+
+ /* node ID for this PATH in REVISION */
+ svn_fs_fs__id_part_t node_id;
+
+ /* when this change happened */
+ svn_revnum_t revision;
+
+ /* noderev predecessor count */
+ int predecessor_count;
+
+ /* this is a directory node */
+ svn_boolean_t is_dir;
+
+ /* length of the expanded representation content */
+ apr_int64_t expanded_size;
+
+ /* item ID of the noderev linked to the change. May be (0, 0). */
+ svn_fs_fs__id_part_t noderev_id;
+
+ /* item ID of the representation containing the new data. May be (0, 0). */
+ svn_fs_fs__id_part_t rep_id;
+} path_order_t;
+
+/* Represents a reference from item FROM to item TO. FROM may be a noderev
+ * or rep_id while TO is (currently) always a representation. We will sort
+ * them by TO which allows us to collect all dependent items.
+ */
+typedef struct reference_t
+{
+ svn_fs_fs__id_part_t to;
+ svn_fs_fs__id_part_t from;
+} reference_t;
+
+/* This structure keeps track of all the temporary data and status that
+ * needs to be kept around during the creation of one pack file. After
+ * each revision range (in case we can't process all revs at once due to
+ * memory restrictions), parts of the data will get re-initialized.
+ */
+typedef struct pack_context_t
+{
+ /* file system that we operate on */
+ svn_fs_t *fs;
+
+ /* cancel function to invoke at regular intervals. May be NULL */
+ svn_cancel_func_t cancel_func;
+
+ /* baton to pass to CANCEL_FUNC */
+ void *cancel_baton;
+
+ /* first revision in the shard (and future pack file) */
+ svn_revnum_t shard_rev;
+
+ /* first revision in the range to process (>= SHARD_REV) */
+ svn_revnum_t start_rev;
+
+ /* first revision after the range to process (<= SHARD_END_REV) */
+ svn_revnum_t end_rev;
+
+ /* first revision after the current shard */
+ svn_revnum_t shard_end_rev;
+
+ /* log-to-phys proto index for the whole pack file */
+ apr_file_t *proto_l2p_index;
+
+ /* phys-to-log proto index for the whole pack file */
+ apr_file_t *proto_p2l_index;
+
+ /* full shard directory path (containing the unpacked revisions) */
+ const char *shard_dir;
+
+ /* full packed shard directory path (containing the pack file + indexes) */
+ const char *pack_file_dir;
+
+ /* full pack file path (including PACK_FILE_DIR) */
+ const char *pack_file_path;
+
+ /* current write position (i.e. file length) in the pack file */
+ apr_off_t pack_offset;
+
+ /* the pack file to ultimately write all data to */
+ apr_file_t *pack_file;
+
+ /* array of svn_fs_fs__p2l_entry_t *, all referring to change lists.
+ * Will be filled in phase 2 and be cleared after each revision range. */
+ apr_array_header_t *changes;
+
+ /* temp file receiving all change list items (referenced by CHANGES).
+ * Will be filled in phase 2 and be cleared after each revision range. */
+ apr_file_t *changes_file;
+
+ /* array of svn_fs_fs__p2l_entry_t *, all referring to file properties.
+ * Will be filled in phase 2 and be cleared after each revision range. */
+ apr_array_header_t *file_props;
+
+ /* temp file receiving all file prop items (referenced by FILE_PROPS).
+ * Will be filled in phase 2 and be cleared after each revision range.*/
+ apr_file_t *file_props_file;
+
+ /* array of svn_fs_fs__p2l_entry_t *, all referring to directory properties.
+ * Will be filled in phase 2 and be cleared after each revision range. */
+ apr_array_header_t *dir_props;
+
+ /* temp file receiving all directory prop items (referenced by DIR_PROPS).
+ * Will be filled in phase 2 and be cleared after each revision range.*/
+ apr_file_t *dir_props_file;
+
+ /* container for all PATH members in PATH_ORDER. */
+ svn_prefix_tree__t *paths;
+
+ /* array of path_order_t *. Will be filled in phase 2 and be cleared
+ * after each revision range. Sorted by PATH, NODE_ID. */
+ apr_array_header_t *path_order;
+
+ /* array of reference_t* linking representations to their delta bases.
+ * Will be filled in phase 2 and be cleared after each revision range.
+ * It will be sorted by the FROM members (for rep->base rep lookup). */
+ apr_array_header_t *references;
+
+ /* array of svn_fs_fs__p2l_entry_t*. Will be filled in phase 2 and be
+ * cleared after each revision range. During phase 3, we will set items
+ * to NULL that we already processed. */
+ apr_array_header_t *reps;
+
+ /* array of int, marking for each revision, the which offset their items
+ * begin in REPS. Will be filled in phase 2 and be cleared after
+ * each revision range. */
+ apr_array_header_t *rev_offsets;
+
+ /* temp file receiving all items referenced by REPS.
+ * Will be filled in phase 2 and be cleared after each revision range.*/
+ apr_file_t *reps_file;
+
+ /* pool used for temporary data structures that will be cleaned up when
+ * the next range of revisions is being processed */
+ apr_pool_t *info_pool;
+} pack_context_t;
+
+/* Create and initialize a new pack context for packing shard SHARD_REV in
+ * SHARD_DIR into PACK_FILE_DIR within filesystem FS. Allocate it in POOL
+ * and return the structure in *CONTEXT.
+ *
+ * Limit the number of items being copied per iteration to MAX_ITEMS.
+ * Set CANCEL_FUNC and CANCEL_BATON as well.
+ */
+static svn_error_t *
+initialize_pack_context(pack_context_t *context,
+ svn_fs_t *fs,
+ const char *pack_file_dir,
+ const char *shard_dir,
+ svn_revnum_t shard_rev,
+ int max_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *temp_dir;
+ int max_revs = MIN(ffd->max_files_per_dir, max_items);
+
+ SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT);
+ SVN_ERR_ASSERT(shard_rev % ffd->max_files_per_dir == 0);
+
+ /* where we will place our various temp files */
+ SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
+
+ /* store parameters */
+ context->fs = fs;
+ context->cancel_func = cancel_func;
+ context->cancel_baton = cancel_baton;
+
+ context->shard_rev = shard_rev;
+ context->start_rev = shard_rev;
+ context->end_rev = shard_rev;
+ context->shard_end_rev = shard_rev + ffd->max_files_per_dir;
+
+ /* Create the new directory and pack file. */
+ context->shard_dir = shard_dir;
+ context->pack_file_dir = pack_file_dir;
+ context->pack_file_path
+ = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
+ SVN_ERR(svn_io_file_open(&context->pack_file, context->pack_file_path,
+ APR_WRITE | APR_BUFFERED | APR_BINARY | APR_EXCL
+ | APR_CREATE, APR_OS_DEFAULT, pool));
+
+ /* Proto index files */
+ SVN_ERR(svn_fs_fs__l2p_proto_index_open(
+ &context->proto_l2p_index,
+ svn_dirent_join(pack_file_dir,
+ PATH_INDEX PATH_EXT_L2P_INDEX,
+ pool),
+ pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_open(
+ &context->proto_p2l_index,
+ svn_dirent_join(pack_file_dir,
+ PATH_INDEX PATH_EXT_P2L_INDEX,
+ pool),
+ pool));
+
+ /* item buckets: one item info array and one temp file per bucket */
+ context->changes = apr_array_make(pool, max_items,
+ sizeof(svn_fs_fs__p2l_entry_t *));
+ SVN_ERR(svn_io_open_unique_file3(&context->changes_file, NULL, temp_dir,
+ svn_io_file_del_on_close, pool, pool));
+ context->file_props = apr_array_make(pool, max_items,
+ sizeof(svn_fs_fs__p2l_entry_t *));
+ SVN_ERR(svn_io_open_unique_file3(&context->file_props_file, NULL, temp_dir,
+ svn_io_file_del_on_close, pool, pool));
+ context->dir_props = apr_array_make(pool, max_items,
+ sizeof(svn_fs_fs__p2l_entry_t *));
+ SVN_ERR(svn_io_open_unique_file3(&context->dir_props_file, NULL, temp_dir,
+ svn_io_file_del_on_close, pool, pool));
+
+ /* noderev and representation item bucket */
+ context->rev_offsets = apr_array_make(pool, max_revs, sizeof(int));
+ context->path_order = apr_array_make(pool, max_items,
+ sizeof(path_order_t *));
+ context->references = apr_array_make(pool, max_items,
+ sizeof(reference_t *));
+ context->reps = apr_array_make(pool, max_items,
+ sizeof(svn_fs_fs__p2l_entry_t *));
+ SVN_ERR(svn_io_open_unique_file3(&context->reps_file, NULL, temp_dir,
+ svn_io_file_del_on_close, pool, pool));
+
+ /* the pool used for temp structures */
+ context->info_pool = svn_pool_create(pool);
+ context->paths = svn_prefix_tree__create(context->info_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Clean up / free all revision range specific data and files in CONTEXT.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+reset_pack_context(pack_context_t *context,
+ apr_pool_t *pool)
+{
+ const char *temp_dir;
+
+ apr_array_clear(context->changes);
+ SVN_ERR(svn_io_file_close(context->changes_file, pool));
+ apr_array_clear(context->file_props);
+ SVN_ERR(svn_io_file_close(context->file_props_file, pool));
+ apr_array_clear(context->dir_props);
+ SVN_ERR(svn_io_file_close(context->dir_props_file, pool));
+
+ apr_array_clear(context->rev_offsets);
+ apr_array_clear(context->path_order);
+ apr_array_clear(context->references);
+ apr_array_clear(context->reps);
+ SVN_ERR(svn_io_file_close(context->reps_file, pool));
+
+ svn_pool_clear(context->info_pool);
+
+ /* The new temporary files must live at least as long as any other info
+ * object in CONTEXT. */
+ SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
+ SVN_ERR(svn_io_open_unique_file3(&context->changes_file, NULL, temp_dir,
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
+ SVN_ERR(svn_io_open_unique_file3(&context->file_props_file, NULL, temp_dir,
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
+ SVN_ERR(svn_io_open_unique_file3(&context->dir_props_file, NULL, temp_dir,
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
+ SVN_ERR(svn_io_open_unique_file3(&context->reps_file, NULL, temp_dir,
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
+ context->paths = svn_prefix_tree__create(context->info_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Call this after the last revision range. It will finalize all index files
+ * for CONTEXT and close any open files. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+close_pack_context(pack_context_t *context,
+ apr_pool_t *pool)
+{
+ const char *proto_l2p_index_path;
+ const char *proto_p2l_index_path;
+
+ /* need the file names for the actual index creation call further down */
+ SVN_ERR(svn_io_file_name_get(&proto_l2p_index_path,
+ context->proto_l2p_index, pool));
+ SVN_ERR(svn_io_file_name_get(&proto_p2l_index_path,
+ context->proto_p2l_index, pool));
+
+ /* finalize proto index files */
+ SVN_ERR(svn_io_file_close(context->proto_l2p_index, pool));
+ SVN_ERR(svn_io_file_close(context->proto_p2l_index, pool));
+
+ /* Append the actual index data to the pack file. */
+ SVN_ERR(svn_fs_fs__add_index_data(context->fs, context->pack_file,
+ proto_l2p_index_path,
+ proto_p2l_index_path,
+ context->shard_rev,
+ pool));
+
+ /* remove proto index files */
+ SVN_ERR(svn_io_remove_file2(proto_l2p_index_path, FALSE, pool));
+ SVN_ERR(svn_io_remove_file2(proto_p2l_index_path, FALSE, pool));
+
+ /* Ensure that packed file is written to disk.*/
+ SVN_ERR(svn_io_file_flush_to_disk(context->pack_file, pool));
+ SVN_ERR(svn_io_file_close(context->pack_file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Efficiently copy SIZE bytes from SOURCE to DEST. Invoke the CANCEL_FUNC
+ * from CONTEXT at regular intervals. Use POOL for allocations.
+ */
+static svn_error_t *
+copy_file_data(pack_context_t *context,
+ apr_file_t *dest,
+ apr_file_t *source,
+ apr_off_t size,
+ apr_pool_t *pool)
+{
+ /* most non-representation items will be small. Minimize the buffer
+ * and infrastructure overhead in that case. */
+ enum { STACK_BUFFER_SIZE = 1024 };
+
+ if (size < STACK_BUFFER_SIZE)
+ {
+ /* copy small data using a fixed-size buffer on stack */
+ char buffer[STACK_BUFFER_SIZE];
+ SVN_ERR(svn_io_file_read_full2(source, buffer, (apr_size_t)size,
+ NULL, NULL, pool));
+ SVN_ERR(svn_io_file_write_full(dest, buffer, (apr_size_t)size,
+ NULL, pool));
+ }
+ else
+ {
+ /* use streaming copies for larger data blocks. That may require
+ * the allocation of larger buffers and we should make sure that
+ * this extra memory is released asap. */
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+ apr_pool_t *copypool = svn_pool_create(pool);
+ char *buffer = apr_palloc(copypool, ffd->block_size);
+
+ while (size)
+ {
+ apr_size_t to_copy = (apr_size_t)(MIN(size, ffd->block_size));
+ if (context->cancel_func)
+ SVN_ERR(context->cancel_func(context->cancel_baton));
+
+ SVN_ERR(svn_io_file_read_full2(source, buffer, to_copy,
+ NULL, NULL, pool));
+ SVN_ERR(svn_io_file_write_full(dest, buffer, to_copy,
+ NULL, pool));
+
+ size -= to_copy;
+ }
+
+ svn_pool_destroy(copypool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Writes SIZE bytes, all 0, to DEST. Uses POOL for allocations.
+ */
+static svn_error_t *
+write_null_bytes(apr_file_t *dest,
+ apr_off_t size,
+ apr_pool_t *pool)
+{
+ /* Have a collection of high-quality, easy to access NUL bytes handy. */
+ enum { BUFFER_SIZE = 1024 };
+ static const char buffer[BUFFER_SIZE] = { 0 };
+
+ /* copy SIZE of them into the file's buffer */
+ while (size)
+ {
+ apr_size_t to_write = MIN(size, BUFFER_SIZE);
+ SVN_ERR(svn_io_file_write_full(dest, buffer, to_write, NULL, pool));
+ size -= to_write;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy the "simple" item (changed paths list or property representation)
+ * from the current position in REV_FILE to TEMP_FILE using CONTEXT. Add
+ * a copy of ENTRY to ENTRIES but with an updated offset value that points
+ * to the copy destination in TEMP_FILE. Use POOL for allocations.
+ */
+static svn_error_t *
+copy_item_to_temp(pack_context_t *context,
+ apr_array_header_t *entries,
+ apr_file_t *temp_file,
+ apr_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__p2l_entry_t *new_entry
+ = apr_pmemdup(context->info_pool, entry, sizeof(*entry));
+
+ SVN_ERR(svn_fs_fs__get_file_offset(&new_entry->offset, temp_file, pool));
+ APR_ARRAY_PUSH(entries, svn_fs_fs__p2l_entry_t *) = new_entry;
+
+ SVN_ERR(copy_file_data(context, temp_file, rev_file, entry->size, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the offset within CONTEXT->REPS that corresponds to item
+ * ITEM_INDEX in REVISION.
+ */
+static int
+get_item_array_index(pack_context_t *context,
+ svn_revnum_t revision,
+ apr_int64_t item_index)
+{
+ assert(revision >= context->start_rev);
+ return (int)item_index + APR_ARRAY_IDX(context->rev_offsets,
+ revision - context->start_rev,
+ int);
+}
+
+/* Write INFO to the correct position in CONTEXT->REP_INFOS. The latter
+ * may need auto-expanding. Overwriting an array element is not allowed.
+ */
+static void
+add_item_rep_mapping(pack_context_t *context,
+ svn_fs_fs__p2l_entry_t *entry)
+{
+ int idx;
+
+ /* index of INFO */
+ idx = get_item_array_index(context,
+ entry->item.revision,
+ entry->item.number);
+
+ /* make sure the index exists in the array */
+ while (context->reps->nelts <= idx)
+ APR_ARRAY_PUSH(context->reps, void *) = NULL;
+
+ /* set the element. If there is already an entry, there are probably
+ * two items claiming to be the same -> bail out */
+ assert(!APR_ARRAY_IDX(context->reps, idx, void *));
+ APR_ARRAY_IDX(context->reps, idx, void *) = entry;
+}
+
+/* Return the P2L entry from CONTEXT->REPS for the given ID. If there is
+ * none (or not anymore), return NULL. If RESET has been specified, set
+ * the array entry to NULL after returning the entry.
+ */
+static svn_fs_fs__p2l_entry_t *
+get_item(pack_context_t *context,
+ const svn_fs_fs__id_part_t *id,
+ svn_boolean_t reset)
+{
+ svn_fs_fs__p2l_entry_t *result = NULL;
+ if (id->number && id->revision >= context->start_rev)
+ {
+ int idx = get_item_array_index(context, id->revision, id->number);
+ if (context->reps->nelts > idx)
+ {
+ result = APR_ARRAY_IDX(context->reps, idx, void *);
+ if (result && reset)
+ APR_ARRAY_IDX(context->reps, idx, void *) = NULL;
+ }
+ }
+
+ return result;
+}
+
+/* Copy representation item identified by ENTRY from the current position
+ * in REV_FILE into CONTEXT->REPS_FILE. Add all tracking into needed by
+ * our placement algorithm to CONTEXT. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+copy_rep_to_temp(pack_context_t *context,
+ apr_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__rep_header_t *rep_header;
+ svn_stream_t *stream;
+ apr_off_t source_offset = entry->offset;
+
+ /* create a copy of ENTRY, make it point to the copy destination and
+ * store it in CONTEXT */
+ entry = apr_pmemdup(context->info_pool, entry, sizeof(*entry));
+ SVN_ERR(svn_fs_fs__get_file_offset(&entry->offset, context->reps_file, pool));
+ add_item_rep_mapping(context, entry);
+
+ /* read & parse the representation header */
+ stream = svn_stream_from_aprfile2(rev_file, TRUE, pool);
+ SVN_ERR(svn_fs_fs__read_rep_header(&rep_header, stream, pool, pool));
+ svn_stream_close(stream);
+
+ /* if the representation is a delta against some other rep, link the two */
+ if ( rep_header->type == svn_fs_fs__rep_delta
+ && rep_header->base_revision >= context->start_rev)
+ {
+ reference_t *reference = apr_pcalloc(context->info_pool,
+ sizeof(*reference));
+ reference->from = entry->item;
+ reference->to.revision = rep_header->base_revision;
+ reference->to.number = rep_header->base_item_index;
+ APR_ARRAY_PUSH(context->references, reference_t *) = reference;
+ }
+
+ /* copy the whole rep (including header!) to our temp file */
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &source_offset, pool));
+ SVN_ERR(copy_file_data(context, context->reps_file, rev_file, entry->size,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Directories first, dirs / files sorted by name in reverse lexical order.
+ * This maximizes the chance of two items being located close to one another
+ * in *all* pack files independent of their change order. It also groups
+ * multi-project repos nicely according to their sub-projects. The reverse
+ * order aspect gives "trunk" preference over "tags" and "branches", so
+ * trunk-related items are more likely to be contiguous.
+ */
+static int
+compare_dir_entries_format7(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ const svn_fs_dirent_t *lhs = (const svn_fs_dirent_t *) a->value;
+ const svn_fs_dirent_t *rhs = (const svn_fs_dirent_t *) b->value;
+
+ if (lhs->kind != rhs->kind)
+ return lhs->kind == svn_node_dir ? -1 : 1;
+
+ return strcmp(lhs->name, rhs->name);
+}
+
+/* Directories entries sorted by revision (decreasing - to max cache hits)
+ * and offset (increasing - to max benefit from APR file buffering).
+ */
+static int
+compare_dir_entries_format6(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ const svn_fs_dirent_t *lhs = (const svn_fs_dirent_t *) a->value;
+ const svn_fs_dirent_t *rhs = (const svn_fs_dirent_t *) b->value;
+
+ const svn_fs_fs__id_part_t *lhs_rev_item
+ = svn_fs_fs__id_rev_item(lhs->id);
+ const svn_fs_fs__id_part_t *rhs_rev_item
+ = svn_fs_fs__id_rev_item(rhs->id);
+
+ /* decreasing ("reverse") order on revs */
+ if (lhs_rev_item->revision != rhs_rev_item->revision)
+ return lhs_rev_item->revision > rhs_rev_item->revision ? -1 : 1;
+
+ /* increasing order on offsets */
+ if (lhs_rev_item->number != rhs_rev_item->number)
+ return lhs_rev_item->number > rhs_rev_item->number ? 1 : -1;
+
+ return 0;
+}
+
+apr_array_header_t *
+svn_fs_fs__order_dir_entries(svn_fs_t *fs,
+ apr_hash_t *directory,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *ordered
+ = svn_sort__hash(directory,
+ svn_fs_fs__use_log_addressing(fs)
+ ? compare_dir_entries_format7
+ : compare_dir_entries_format6,
+ scratch_pool);
+
+ apr_array_header_t *result
+ = apr_array_make(result_pool, ordered->nelts, sizeof(svn_fs_dirent_t *));
+
+ int i;
+ for (i = 0; i < ordered->nelts; ++i)
+ APR_ARRAY_PUSH(result, svn_fs_dirent_t *)
+ = APR_ARRAY_IDX(ordered, i, svn_sort__item_t).value;
+
+ return result;
+}
+
+/* Return a duplicate of the the ORIGINAL path and with special sub-strins
+ * (e.g. "trunk") modified in such a way that have a lower lexicographic
+ * value than any other "normal" file name.
+ */
+static const char *
+tweak_path_for_ordering(const char *original,
+ apr_pool_t *pool)
+{
+ /* We may add further special cases as needed. */
+ enum {SPECIAL_COUNT = 2};
+ static const char *special[SPECIAL_COUNT] = {"trunk", "branch"};
+ char *pos;
+ char *path = apr_pstrdup(pool, original);
+ int i;
+
+ /* Replace the first char of any "special" sub-string we find by
+ * a control char, i.e. '\1' .. '\31'. In the rare event that this
+ * would clash with existing paths, no data will be lost but merely
+ * the node ordering will be sub-optimal.
+ */
+ for (i = 0; i < SPECIAL_COUNT; ++i)
+ for (pos = strstr(path, special[i]);
+ pos;
+ pos = strstr(pos + 1, special[i]))
+ {
+ *pos = (char)(i + '\1');
+ }
+
+ return path;
+}
+
+/* Copy node revision item identified by ENTRY from the current position
+ * in REV_FILE into CONTEXT->REPS_FILE. Add all tracking into needed by
+ * our placement algorithm to CONTEXT. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+copy_node_to_temp(pack_context_t *context,
+ apr_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ path_order_t *path_order = apr_pcalloc(context->info_pool,
+ sizeof(*path_order));
+ node_revision_t *noderev;
+ const char *sort_path;
+ svn_stream_t *stream;
+ apr_off_t source_offset = entry->offset;
+
+ /* read & parse noderev */
+ stream = svn_stream_from_aprfile2(rev_file, TRUE, pool);
+ SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, pool, pool));
+ svn_stream_close(stream);
+
+ /* create a copy of ENTRY, make it point to the copy destination and
+ * store it in CONTEXT */
+ entry = apr_pmemdup(context->info_pool, entry, sizeof(*entry));
+ SVN_ERR(svn_fs_fs__get_file_offset(&entry->offset, context->reps_file,
+ pool));
+ add_item_rep_mapping(context, entry);
+
+ /* copy the noderev to our temp file */
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &source_offset, pool));
+ SVN_ERR(copy_file_data(context, context->reps_file, rev_file, entry->size,
+ pool));
+
+ /* if the node has a data representation, make that the node's "base".
+ * This will (often) cause the noderev to be placed right in front of
+ * its data representation. */
+
+ if (noderev->data_rep && noderev->data_rep->revision >= context->start_rev)
+ {
+ path_order->rep_id.revision = noderev->data_rep->revision;
+ path_order->rep_id.number = noderev->data_rep->item_index;
+ path_order->expanded_size = noderev->data_rep->expanded_size
+ ? noderev->data_rep->expanded_size
+ : noderev->data_rep->size;
+ }
+
+ /* Sort path is the key used for ordering noderevs and associated reps.
+ * It will not be stored in the final pack file. */
+ sort_path = tweak_path_for_ordering(noderev->created_path, pool);
+ path_order->path = svn_prefix_string__create(context->paths, sort_path);
+ path_order->node_id = *svn_fs_fs__id_node_id(noderev->id);
+ path_order->revision = svn_fs_fs__id_rev(noderev->id);
+ path_order->predecessor_count = noderev->predecessor_count;
+ path_order->is_dir = noderev->kind == svn_node_dir;
+ path_order->noderev_id = *svn_fs_fs__id_rev_item(noderev->id);
+ APR_ARRAY_PUSH(context->path_order, path_order_t *) = path_order;
+
+ return SVN_NO_ERROR;
+}
+
+/* implements compare_fn_t. Bring all directories in front of the files
+ and sort descendingly by PATH, NODE_ID and REVISION.
+ */
+static int
+compare_path_order(const path_order_t * const * lhs_p,
+ const path_order_t * const * rhs_p)
+{
+ const path_order_t * lhs = *lhs_p;
+ const path_order_t * rhs = *rhs_p;
+
+ /* cluster all directories */
+ int diff = rhs->is_dir - lhs->is_dir;
+ if (diff)
+ return diff;
+
+ /* lexicographic order on path and node (i.e. latest first) */
+ diff = svn_prefix_string__compare(lhs->path, rhs->path);
+ if (diff)
+ return diff;
+
+ /* reverse order on node (i.e. latest first) */
+ diff = svn_fs_fs__id_part_compare(&rhs->node_id, &lhs->node_id);
+ if (diff)
+ return diff;
+
+ /* reverse order on revision (i.e. latest first) */
+ if (lhs->revision != rhs->revision)
+ return lhs->revision < rhs->revision ? 1 : -1;
+
+ return 0;
+}
+
+/* implements compare_fn_t. Sort ascendingly by FROM, TO.
+ */
+static int
+compare_references(const reference_t * const * lhs_p,
+ const reference_t * const * rhs_p)
+{
+ const reference_t * lhs = *lhs_p;
+ const reference_t * rhs = *rhs_p;
+
+ int diff = svn_fs_fs__id_part_compare(&lhs->from, &rhs->from);
+ return diff ? diff : svn_fs_fs__id_part_compare(&lhs->to, &rhs->to);
+}
+
+/* implements compare_fn_t. Assume ascending order by FROM.
+ */
+static int
+compare_ref_to_item(const reference_t * const * lhs_p,
+ const svn_fs_fs__id_part_t * rhs_p)
+{
+ return svn_fs_fs__id_part_compare(&(*lhs_p)->from, rhs_p);
+}
+
+/* implements compare_fn_t. Finds the DIR / FILE boundary.
+ */
+static int
+compare_is_dir(const path_order_t * const * lhs_p,
+ const void *unused)
+{
+ return (*lhs_p)->is_dir ? -1 : 0;
+}
+
+/* Look for the least significant bit set in VALUE and return the smallest
+ * number with the same property, i.e. the largest power of 2 that is a
+ * factor in VALUE. */
+static int
+roundness(int value)
+{
+ return value ? value - (value & (value - 1)) : INT_MAX;
+}
+
+/* Order a range of data collected in CONTEXT such that we can place them
+ * in the desired order. The input is taken from *PATH_ORDER, offsets FIRST
+ * to LAST and then written in the final order to the same range in *TEMP.
+ */
+static void
+sort_reps_range(pack_context_t *context,
+ const path_order_t **path_order,
+ const path_order_t **temp,
+ int first,
+ int last)
+{
+ const svn_prefix_string__t *path;
+ int i, dest, best;
+ svn_fs_fs__id_part_t rep_id;
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+
+ /* The logic below would fail for empty ranges. */
+ if (first == last)
+ return;
+
+ /* Re-order noderevs like this:
+ *
+ * (1) Most likely to be referenced by future pack files, in path order.
+ * (2) highest revision rep per path + dependency chain
+ * (3) Remaining reps in path, rev order
+ *
+ * We simply pick & chose from the existing path, rev order.
+ */
+ dest = first;
+ path = path_order[first]->path;
+ best = first;
+
+ /* (1) For each path, pick the "roundest" representation and put it in
+ * front of all other nodes in the pack file. The "roundest" rep is
+ * the one most likely to be referenced from future pack files, i.e. we
+ * concentrate those potential "foreign link targets" in one section of
+ * the pack file.
+ *
+ * And we only apply this to reps outside the linear deltification
+ * sections because references *into* linear deltification ranges are
+ * much less likely.
+ */
+ for (i = first; i < last; ++i)
+ {
+ /* Investigated all nodes for the current path? */
+ if (svn_prefix_string__compare(path, path_order[i]->path))
+ {
+ /* next path */
+ path = path_order[i]->path;
+
+ /* Pick roundest non-linear deltified node. */
+ if (roundness(path_order[best]->predecessor_count)
+ >= ffd->max_linear_deltification)
+ {
+ temp[dest++] = path_order[best];
+ path_order[best] = NULL;
+ best = i;
+ }
+ }
+
+ /* next entry */
+ if ( roundness(path_order[best]->predecessor_count)
+ < roundness(path_order[i]->predecessor_count))
+ best = i;
+ }
+
+ /* Treat the last path the same as all others. */
+ if (roundness(path_order[best]->predecessor_count)
+ >= ffd->max_linear_deltification)
+ {
+ temp[dest++] = path_order[best];
+ path_order[best] = NULL;
+ }
+
+ /* (2) For each (remaining) path, pick the nodes along the delta chain
+ * for the highest revision. Due to our ordering, this is the first
+ * node we encounter for any path.
+ *
+ * Most references that don't hit a delta base picked in (1), will
+ * access HEAD of the respective path. Keeping all its dependency chain
+ * in one place turns reconstruction into a linear scan of minimal length.
+ */
+ for (i = first; i < last; ++i)
+ if (path_order[i])
+ {
+ /* This is the first path we still have to handle. */
+ path = path_order[i]->path;
+ rep_id = path_order[i]->rep_id;
+ break;
+ }
+
+ for (i = first; i < last; ++i)
+ if (path_order[i])
+ {
+ /* New path? */
+ if (svn_prefix_string__compare(path, path_order[i]->path))
+ {
+ path = path_order[i]->path;
+ rep_id = path_order[i]->rep_id;
+ }
+
+ /* Pick nodes along the deltification chain. Skip side-branches. */
+ if (svn_fs_fs__id_part_eq(&path_order[i]->rep_id, &rep_id))
+ {
+ reference_t **reference;
+
+ temp[dest++] = path_order[i];
+ path_order[i] = NULL;
+
+ reference = svn_sort__array_lookup(context->references,
+ &rep_id, NULL,
+ (int (*)(const void *, const void *))compare_ref_to_item);
+ if (reference)
+ rep_id = (*reference)->to;
+ }
+ }
+
+ /* (3) All remaining nodes in path, rev order. Linear deltification
+ * makes HEAD delta chains from (2) cover all or most of their deltas
+ * in a given pack file. So, this is just a few remnants that we put
+ * at the end of the pack file.
+ */
+ for (i = first; i < last; ++i)
+ if (path_order[i])
+ temp[dest++] = path_order[i];
+
+ /* We now know the final ordering. */
+ assert(dest == last);
+}
+
+/* Order the data collected in CONTEXT such that we can place them in the
+ * desired order.
+ */
+static void
+sort_reps(pack_context_t *context)
+{
+ apr_pool_t *temp_pool;
+ const path_order_t **temp, **path_order;
+ int i, count, dir_count;
+
+ /* We will later assume that there is at least one node / path.
+ */
+ if (context->path_order->nelts == 0)
+ {
+ assert(context->references->nelts == 0);
+ return;
+ }
+
+ /* Sort containers by path and IDs, respectively.
+ */
+ svn_sort__array(context->path_order,
+ (int (*)(const void *, const void *))compare_path_order);
+ svn_sort__array(context->references,
+ (int (*)(const void *, const void *))compare_references);
+
+ /* Directories are already in front; sort directories section and files
+ * section separately but use the same heuristics (see sub-function).
+ */
+ temp_pool = svn_pool_create(context->info_pool);
+ count = context->path_order->nelts;
+ temp = apr_pcalloc(temp_pool, count * sizeof(*temp));
+ path_order = (void *)context->path_order->elts;
+
+ /* Find the boundary between DIR and FILE section. */
+ dir_count = svn_sort__bsearch_lower_bound(context->path_order, NULL,
+ (int (*)(const void *, const void *))compare_is_dir);
+
+ /* Sort those sub-sections separately. */
+ sort_reps_range(context, path_order, temp, 0, dir_count);
+ sort_reps_range(context, path_order, temp, dir_count, count);
+
+ /* We now know the final ordering. */
+ for (i = 0; i < count; ++i)
+ path_order[i] = temp[i];
+
+ svn_pool_destroy(temp_pool);
+}
+
+/* implements compare_fn_t. Place LHS before RHS, if the latter is older.
+ */
+static int
+compare_p2l_info(const svn_fs_fs__p2l_entry_t * const * lhs,
+ const svn_fs_fs__p2l_entry_t * const * rhs)
+{
+ assert(*lhs != *rhs);
+
+ if ((*lhs)->item.revision == (*rhs)->item.revision)
+ return (*lhs)->item.number > (*rhs)->item.number ? -1 : 1;
+
+ return (*lhs)->item.revision > (*rhs)->item.revision ? -1 : 1;
+}
+
+/* Sort svn_fs_fs__p2l_entry_t * array ENTRIES by age. Place the latest
+ * items first.
+ */
+static void
+sort_items(apr_array_header_t *entries)
+{
+ svn_sort__array(entries,
+ (int (*)(const void *, const void *))compare_p2l_info);
+}
+
+/* Return the remaining unused bytes in the current block in CONTEXT's
+ * pack file.
+ */
+static apr_ssize_t
+get_block_left(pack_context_t *context)
+{
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+ return ffd->block_size - (context->pack_offset % ffd->block_size);
+}
+
+/* To prevent items from overlapping a block boundary, we will usually
+ * put them into the next block and top up the old one with NUL bytes.
+ * Pad CONTEXT's pack file to the end of the current block, if TO_ADD does
+ * not fit into the current block and the padding is short enough.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+auto_pad_block(pack_context_t *context,
+ apr_off_t to_add,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+
+ /* This is the maximum number of bytes "wasted" that way per block.
+ * Larger items will cross the block boundaries. */
+ const apr_off_t max_padding = MAX(ffd->block_size / 50, 512);
+
+ /* Is wasted space small enough to align the current item to the next
+ * block? */
+ apr_off_t padding = get_block_left(context);
+
+ if (padding < to_add && padding < max_padding)
+ {
+ /* Yes. To up with NUL bytes and don't forget to create
+ * an P2L index entry marking this section as unused. */
+ svn_fs_fs__p2l_entry_t null_entry;
+
+ null_entry.offset = context->pack_offset;
+ null_entry.size = padding;
+ null_entry.type = SVN_FS_FS__ITEM_TYPE_UNUSED;
+ null_entry.item.revision = SVN_INVALID_REVNUM;
+ null_entry.item.number = SVN_FS_FS__ITEM_INDEX_UNUSED;
+ null_entry.fnv1_checksum = 0;
+
+ SVN_ERR(write_null_bytes(context->pack_file, padding, pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(
+ context->proto_p2l_index, &null_entry, pool));
+ context->pack_offset += padding;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the contents of ITEM, if not empty, from TEMP_FILE and write it
+ * to CONTEXT->PACK_FILE. Use POOL for allocations.
+ */
+static svn_error_t *
+store_item(pack_context_t *context,
+ apr_file_t *temp_file,
+ svn_fs_fs__p2l_entry_t *item,
+ apr_pool_t *pool)
+{
+ apr_off_t safety_margin;
+
+ /* skip empty entries */
+ if (item->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ return SVN_NO_ERROR;
+
+ /* If the next item does not fit into the current block, auto-pad it.
+ Take special care of textual noderevs since their parsers may
+ prefetch up to 80 bytes and we don't want them to cross block
+ boundaries. */
+ safety_margin = item->type == SVN_FS_FS__ITEM_TYPE_NODEREV
+ ? SVN__LINE_CHUNK_SIZE
+ : 0;
+ SVN_ERR(auto_pad_block(context, item->size + safety_margin, pool));
+
+ /* select the item in the source file and copy it into the target
+ * pack file */
+ SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &item->offset, pool));
+ SVN_ERR(copy_file_data(context, context->pack_file, temp_file,
+ item->size, pool));
+
+ /* write index entry and update current position */
+ item->offset = context->pack_offset;
+ context->pack_offset += item->size;
+
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(context->proto_p2l_index,
+ item, pool));
+
+ APR_ARRAY_PUSH(context->reps, svn_fs_fs__p2l_entry_t *) = item;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the contents of the non-empty items in ITEMS from TEMP_FILE and
+ * write them to CONTEXT->PACK_FILE. Use POOL for allocations.
+ */
+static svn_error_t *
+store_items(pack_context_t *context,
+ apr_file_t *temp_file,
+ apr_array_header_t *items,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* copy all items in strict order */
+ for (i = 0; i < items->nelts; ++i)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(store_item(context, temp_file,
+ APR_ARRAY_IDX(items, i, svn_fs_fs__p2l_entry_t *),
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy (append) the items identified by svn_fs_fs__p2l_entry_t * elements
+ * in ENTRIES strictly in order from TEMP_FILE into CONTEXT->PACK_FILE.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+copy_reps_from_temp(pack_context_t *context,
+ apr_file_t *temp_file,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_array_header_t *path_order = context->path_order;
+ int i;
+
+ /* copy items in path order. */
+ for (i = 0; i < path_order->nelts; ++i)
+ {
+ path_order_t *current_path;
+ svn_fs_fs__p2l_entry_t *node_part;
+ svn_fs_fs__p2l_entry_t *rep_part;
+
+ svn_pool_clear(iterpool);
+
+ current_path = APR_ARRAY_IDX(path_order, i, path_order_t *);
+ node_part = get_item(context, &current_path->noderev_id, TRUE);
+ rep_part = get_item(context, &current_path->rep_id, TRUE);
+
+ if (node_part)
+ SVN_ERR(store_item(context, temp_file, node_part, iterpool));
+ if (rep_part)
+ SVN_ERR(store_item(context, temp_file, rep_part, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* implements compare_fn_t. Place LHS before RHS, if the latter belongs to
+ * a newer revision.
+ */
+static int
+compare_p2l_info_rev(const svn_fs_fs__p2l_entry_t * const * lhs_p,
+ const svn_fs_fs__p2l_entry_t * const * rhs_p)
+{
+ const svn_fs_fs__p2l_entry_t * lhs = *lhs_p;
+ const svn_fs_fs__p2l_entry_t * rhs = *rhs_p;
+
+ if (lhs->item.revision == rhs->item.revision)
+ return 0;
+
+ return lhs->item.revision < rhs->item.revision ? -1 : 1;
+}
+
+/* Write the log-to-phys proto index file for CONTEXT and use POOL for
+ * temporary allocations. All items in all buckets must have been placed
+ * by now.
+ */
+static svn_error_t *
+write_l2p_index(pack_context_t *context,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_revnum_t prev_rev = SVN_INVALID_REVNUM;
+ int i, dest;
+
+ /* eliminate empty entries from CONTEXT->REPS */
+ for (i = 0, dest = 0; i < context->reps->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = APR_ARRAY_IDX(context->reps, i, svn_fs_fs__p2l_entry_t *);
+ if (entry)
+ APR_ARRAY_IDX(context->reps, dest++, svn_fs_fs__p2l_entry_t *)
+ = entry;
+ }
+ context->reps->nelts = dest;
+
+ /* we need to write the l2p index revision by revision */
+ svn_sort__array(context->reps,
+ (int (*)(const void *, const void *))compare_p2l_info_rev);
+
+ /* write index entries */
+ for (i = 0; i < context->reps->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *p2l_entry
+ = APR_ARRAY_IDX(context->reps, i, svn_fs_fs__p2l_entry_t *);
+ if (p2l_entry == NULL)
+ continue;
+
+ /* next revision? */
+ if (prev_rev != p2l_entry->item.revision)
+ {
+ prev_rev = p2l_entry->item.revision;
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_revision(
+ context->proto_l2p_index, iterpool));
+ }
+
+ /* add entry */
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(context->proto_l2p_index,
+ p2l_entry->offset,
+ p2l_entry->item.number,
+ iterpool));
+
+ /* keep memory usage in check */
+ if (i % 256 == 0)
+ svn_pool_clear(iterpool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Pack the current revision range of CONTEXT, i.e. this covers phases 2
+ * to 4. Use POOL for allocations.
+ */
+static svn_error_t *
+pack_range(pack_context_t *context,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+ apr_pool_t *revpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_pool_t *iterpool2 = svn_pool_create(pool);
+
+ /* Phase 2: Copy items into various buckets and build tracking info */
+ svn_revnum_t revision;
+ for (revision = context->start_rev; revision < context->end_rev; ++revision)
+ {
+ apr_off_t offset = 0;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ svn_pool_clear(revpool);
+
+ /* Get the rev file dimensions (mainly index locations). */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, context->fs,
+ revision, revpool, iterpool));
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+
+ /* store the indirect array index */
+ APR_ARRAY_PUSH(context->rev_offsets, int) = context->reps->nelts;
+
+ /* read the phys-to-log index file until we covered the whole rev file.
+ * That index contains enough info to build both target indexes from it. */
+ while (offset < rev_file->l2p_offset)
+ {
+ /* read one cluster */
+ int i;
+ apr_array_header_t *entries;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, context->fs,
+ rev_file, revision, offset,
+ ffd->p2l_page_size, iterpool,
+ iterpool));
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+
+ /* skip first entry if that was duplicated due crossing a
+ cluster boundary */
+ if (offset > entry->offset)
+ continue;
+
+ svn_pool_clear(iterpool2);
+
+ /* process entry while inside the rev file */
+ offset = entry->offset;
+ if (offset < rev_file->l2p_offset)
+ {
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset,
+ iterpool2));
+
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_CHANGES)
+ SVN_ERR(copy_item_to_temp(context,
+ context->changes,
+ context->changes_file,
+ rev_file->file, entry,
+ iterpool2));
+ else if (entry->type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
+ SVN_ERR(copy_item_to_temp(context,
+ context->file_props,
+ context->file_props_file,
+ rev_file->file, entry,
+ iterpool2));
+ else if (entry->type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS)
+ SVN_ERR(copy_item_to_temp(context,
+ context->dir_props,
+ context->dir_props_file,
+ rev_file->file, entry,
+ iterpool2));
+ else if ( entry->type == SVN_FS_FS__ITEM_TYPE_FILE_REP
+ || entry->type == SVN_FS_FS__ITEM_TYPE_DIR_REP)
+ SVN_ERR(copy_rep_to_temp(context, rev_file->file, entry,
+ iterpool2));
+ else if (entry->type == SVN_FS_FS__ITEM_TYPE_NODEREV)
+ SVN_ERR(copy_node_to_temp(context, rev_file->file, entry,
+ iterpool2));
+ else
+ SVN_ERR_ASSERT(entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED);
+
+ offset += entry->size;
+ }
+ }
+
+ if (context->cancel_func)
+ SVN_ERR(context->cancel_func(context->cancel_baton));
+ }
+ }
+
+ svn_pool_destroy(iterpool2);
+ svn_pool_destroy(iterpool);
+
+ /* phase 3: placement.
+ * Use "newest first" placement for simple items. */
+ sort_items(context->changes);
+ sort_items(context->file_props);
+ sort_items(context->dir_props);
+
+ /* follow dependencies recursively for noderevs and data representations */
+ sort_reps(context);
+
+ /* phase 4: copy bucket data to pack file. Write P2L index. */
+ SVN_ERR(store_items(context, context->changes_file, context->changes,
+ revpool));
+ svn_pool_clear(revpool);
+ SVN_ERR(store_items(context, context->file_props_file, context->file_props,
+ revpool));
+ svn_pool_clear(revpool);
+ SVN_ERR(store_items(context, context->dir_props_file, context->dir_props,
+ revpool));
+ svn_pool_clear(revpool);
+ SVN_ERR(copy_reps_from_temp(context, context->reps_file, revpool));
+ svn_pool_clear(revpool);
+
+ /* write L2P index as well (now that we know all target offsets) */
+ SVN_ERR(write_l2p_index(context, revpool));
+
+ svn_pool_destroy(revpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Append CONTEXT->START_REV to the context's pack file with no re-ordering.
+ * This function will only be used for very large revisions (>>100k changes).
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+append_revision(pack_context_t *context,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+ apr_off_t offset = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_fs_fs__revision_file_t *rev_file;
+ svn_filesize_t revdata_size;
+
+ /* Copy all non-index contents the rev file to the end of the pack file. */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, context->fs,
+ context->start_rev, pool,
+ iterpool));
+
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+ revdata_size = rev_file->l2p_offset;
+
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, ffd->block_size, NULL, 0,
+ iterpool));
+ SVN_ERR(copy_file_data(context, context->pack_file, rev_file->file,
+ revdata_size, iterpool));
+
+ /* mark the start of a new revision */
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_revision(context->proto_l2p_index,
+ pool));
+
+ /* read the phys-to-log index file until we covered the whole rev file.
+ * That index contains enough info to build both target indexes from it. */
+ while (offset < revdata_size)
+ {
+ /* read one cluster */
+ int i;
+ apr_array_header_t *entries;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, context->fs, rev_file,
+ context->start_rev, offset,
+ ffd->p2l_page_size, iterpool,
+ iterpool));
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+
+ /* skip first entry if that was duplicated due crossing a
+ cluster boundary */
+ if (offset > entry->offset)
+ continue;
+
+ /* process entry while inside the rev file */
+ offset = entry->offset;
+ if (offset < revdata_size)
+ {
+ entry->offset += context->pack_offset;
+ offset += entry->size;
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(
+ context->proto_l2p_index, entry->offset,
+ entry->item.number, iterpool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(
+ context->proto_p2l_index, entry, iterpool));
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ context->pack_offset += revdata_size;
+
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Logical addressing mode packing logic.
+ *
+ * Pack the revision shard starting at SHARD_REV in filesystem FS from
+ * SHARD_DIR into the PACK_FILE_DIR, using POOL for allocations. Limit
+ * the extra memory consumption to MAX_MEM bytes. CANCEL_FUNC and
+ * CANCEL_BATON are what you think they are.
+ */
+static svn_error_t *
+pack_log_addressed(svn_fs_t *fs,
+ const char *pack_file_dir,
+ const char *shard_dir,
+ svn_revnum_t shard_rev,
+ apr_size_t max_mem,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ enum
+ {
+ /* estimated amount of memory used to represent one item in memory
+ * during rev file packing */
+ PER_ITEM_MEM = APR_ALIGN_DEFAULT(sizeof(path_order_t))
+ + APR_ALIGN_DEFAULT(2 *sizeof(void*))
+ + APR_ALIGN_DEFAULT(sizeof(reference_t))
+ + APR_ALIGN_DEFAULT(sizeof(svn_fs_fs__p2l_entry_t))
+ + 6 * sizeof(void*)
+ };
+
+ int max_items;
+ apr_array_header_t *max_ids;
+ pack_context_t context = { 0 };
+ int i;
+ apr_size_t item_count = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Prevent integer overflow. We use apr arrays to process the items so
+ * the maximum number of items is INT_MAX. */
+ {
+ apr_size_t temp = max_mem / PER_ITEM_MEM;
+ SVN_ERR_ASSERT(temp <= INT_MAX);
+ max_items = (int)temp;
+ }
+
+ /* set up a pack context */
+ SVN_ERR(initialize_pack_context(&context, fs, pack_file_dir, shard_dir,
+ shard_rev, max_items, cancel_func,
+ cancel_baton, pool));
+
+ /* phase 1: determine the size of the revisions to pack */
+ SVN_ERR(svn_fs_fs__l2p_get_max_ids(&max_ids, fs, shard_rev,
+ context.shard_end_rev - shard_rev,
+ pool, pool));
+
+ /* pack revisions in ranges that don't exceed MAX_MEM */
+ for (i = 0; i < max_ids->nelts; ++i)
+ if (APR_ARRAY_IDX(max_ids, i, apr_uint64_t) + item_count <= max_items)
+ {
+ item_count += APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
+ context.end_rev++;
+ }
+ else
+ {
+ svn_pool_clear(iterpool);
+
+ /* some unpacked revisions before this one? */
+ if (context.start_rev < context.end_rev)
+ {
+ /* pack them intelligently (might be just 1 rev but still ...) */
+ SVN_ERR(pack_range(&context, iterpool));
+ SVN_ERR(reset_pack_context(&context, iterpool));
+ item_count = 0;
+ }
+
+ /* next revision range is to start with the current revision */
+ context.start_rev = i + context.shard_rev;
+ context.end_rev = context.start_rev + 1;
+
+ /* if this is a very large revision, we must place it as is */
+ if (APR_ARRAY_IDX(max_ids, i, apr_uint64_t) > max_items)
+ {
+ SVN_ERR(append_revision(&context, iterpool));
+ context.start_rev++;
+ }
+ else
+ item_count += (apr_size_t)APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
+ }
+
+ /* non-empty revision range at the end? */
+ if (context.start_rev < context.end_rev)
+ SVN_ERR(pack_range(&context, iterpool));
+
+ /* last phase: finalize indexes and clean up */
+ SVN_ERR(reset_pack_context(&context, iterpool));
+ SVN_ERR(close_pack_context(&context, iterpool));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
+ Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_packed_offset(apr_off_t *rev_offset,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *manifest_stream;
+ svn_boolean_t is_cached;
+ svn_revnum_t shard;
+ apr_int64_t shard_pos;
+ apr_array_header_t *manifest;
+ apr_pool_t *iterpool;
+
+ shard = rev / ffd->max_files_per_dir;
+
+ /* position of the shard within the manifest */
+ shard_pos = rev % ffd->max_files_per_dir;
+
+ /* fetch exactly that element into *rev_offset, if the manifest is found
+ in the cache */
+ SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
+ ffd->packed_offset_cache, &shard,
+ svn_fs_fs__get_sharded_offset, &shard_pos,
+ pool));
+
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* Open the manifest file. */
+ SVN_ERR(svn_stream_open_readonly(&manifest_stream,
+ svn_fs_fs__path_rev_packed(fs, rev,
+ PATH_MANIFEST,
+ pool),
+ pool, pool));
+
+ /* While we're here, let's just read the entire manifest file into an array,
+ so we can cache the entire thing. */
+ iterpool = svn_pool_create(pool);
+ manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
+ while (1)
+ {
+ svn_boolean_t eof;
+ apr_int64_t val;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__read_number_from_stream(&val, &eof, manifest_stream,
+ iterpool));
+ if (eof)
+ break;
+
+ APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
+ }
+ svn_pool_destroy(iterpool);
+
+ *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
+ apr_off_t);
+
+ /* Close up shop and cache the array. */
+ SVN_ERR(svn_stream_close(manifest_stream));
+ return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
+}
+
+/* Packing logic for physical addresssing mode:
+ * Simply concatenate all revision contents.
+ *
+ * Pack the revision shard starting at SHARD_REV containing exactly
+ * MAX_FILES_PER_DIR revisions from SHARD_PATH into the PACK_FILE_DIR,
+ * using POOL for allocations. CANCEL_FUNC and CANCEL_BATON are what you
+ * think they are.
+ */
+static svn_error_t *
+pack_phys_addressed(const char *pack_file_dir,
+ const char *shard_path,
+ svn_revnum_t start_rev,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *pack_file_path, *manifest_file_path;
+ apr_file_t *pack_file;
+ apr_file_t *manifest_file;
+ svn_stream_t *manifest_stream;
+ svn_revnum_t end_rev, rev;
+ apr_off_t next_offset;
+ apr_pool_t *iterpool;
+
+ /* Some useful paths. */
+ pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
+ manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
+
+ /* Create the new directory and pack file.
+ * Use unbuffered apr_file_t since we're going to write using 16kb
+ * chunks. */
+ SVN_ERR(svn_io_file_open(&pack_file, pack_file_path,
+ APR_WRITE | APR_CREATE | APR_EXCL,
+ APR_OS_DEFAULT, pool));
+
+ /* Create the manifest file. */
+ SVN_ERR(svn_io_file_open(&manifest_file, manifest_file_path,
+ APR_WRITE | APR_BUFFERED | APR_CREATE | APR_EXCL,
+ APR_OS_DEFAULT, pool));
+ manifest_stream = svn_stream_from_aprfile2(manifest_file, TRUE, pool);
+
+ end_rev = start_rev + max_files_per_dir - 1;
+ next_offset = 0;
+ iterpool = svn_pool_create(pool);
+
+ /* Iterate over the revisions in this shard, squashing them together. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ svn_stream_t *rev_stream;
+ apr_finfo_t finfo;
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the size of the file. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
+
+ /* build manifest */
+ SVN_ERR(svn_stream_printf(manifest_stream, iterpool,
+ "%" APR_OFF_T_FMT "\n", next_offset));
+ next_offset += finfo.size;
+
+ /* Copy all the bits from the rev file to the end of the pack file. */
+ SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
+ SVN_ERR(svn_stream_copy3(rev_stream,
+ svn_stream_from_aprfile2(pack_file, TRUE, pool),
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ /* Close stream over APR file. */
+ SVN_ERR(svn_stream_close(manifest_stream));
+
+ /* Ensure that pack file is written to disk. */
+ SVN_ERR(svn_io_file_flush_to_disk(manifest_file, pool));
+ SVN_ERR(svn_io_file_close(manifest_file, pool));
+
+ /* disallow write access to the manifest file */
+ SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
+
+ /* Ensure that pack file is written to disk. */
+ SVN_ERR(svn_io_file_flush_to_disk(pack_file, pool));
+ SVN_ERR(svn_io_file_close(pack_file, pool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* In filesystem FS, pack the revision SHARD containing exactly
+ * MAX_FILES_PER_DIR revisions from SHARD_PATH into the PACK_FILE_DIR,
+ * using POOL for allocations. Try to limit the amount of temporary
+ * memory needed to MAX_MEM bytes. CANCEL_FUNC and CANCEL_BATON are what
+ * you think they are.
+ *
+ * If for some reason we detect a partial packing already performed, we
+ * remove the pack file and start again.
+ *
+ * The actual packing will be done in a format-specific sub-function.
+ */
+static svn_error_t *
+pack_rev_shard(svn_fs_t *fs,
+ const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_size_t max_mem,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *pack_file_path;
+ svn_revnum_t shard_rev = (svn_revnum_t) (shard * max_files_per_dir);
+
+ /* Some useful paths. */
+ pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
+
+ /* Remove any existing pack file for this shard, since it is incomplete. */
+ SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
+ pool));
+
+ /* Create the new directory and pack file. */
+ SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
+
+ /* Index information files */
+ if (svn_fs_fs__use_log_addressing(fs))
+ SVN_ERR(pack_log_addressed(fs, pack_file_dir, shard_path, shard_rev,
+ max_mem, cancel_func, cancel_baton, pool));
+ else
+ SVN_ERR(pack_phys_addressed(pack_file_dir, shard_path, shard_rev,
+ max_files_per_dir, cancel_func,
+ cancel_baton, pool));
+
+ SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, pool));
+ SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton struct used by pack_body(), pack_shard() and synced_pack_shard().
+ These calls are nested and for every level additional fields will be
+ available. */
+struct pack_baton
+{
+ /* Valid when entering pack_body(). */
+ svn_fs_t *fs;
+ svn_fs_pack_notify_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ size_t max_mem;
+
+ /* Additional entries valid when entering pack_shard(). */
+ const char *revs_dir;
+ const char *revsprops_dir;
+ apr_int64_t shard;
+
+ /* Additional entries valid when entering synced_pack_shard(). */
+ const char *rev_shard_path;
+};
+
+
+/* Part of the pack process that requires global (write) synchronization.
+ * We pack the revision properties of the shard described by BATON and
+ * In the file system at FS_PATH, pack the SHARD in REVS_DIR and replace
+ * the non-packed revprop & rev shard folder(s) with the packed ones.
+ * The packed rev folder has been created prior to calling this function.
+ */
+static svn_error_t *
+synced_pack_shard(void *baton,
+ apr_pool_t *pool)
+{
+ struct pack_baton *pb = baton;
+ fs_fs_data_t *ffd = pb->fs->fsap_data;
+ const char *revprops_shard_path, *revprops_pack_file_dir;
+
+ /* if enabled, pack the revprops in an equivalent way */
+ if (pb->revsprops_dir)
+ {
+ revprops_pack_file_dir = svn_dirent_join(pb->revsprops_dir,
+ apr_psprintf(pool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ pb->shard),
+ pool);
+ revprops_shard_path = svn_dirent_join(pb->revsprops_dir,
+ apr_psprintf(pool, "%" APR_INT64_T_FMT, pb->shard),
+ pool);
+
+ SVN_ERR(svn_fs_fs__pack_revprops_shard(revprops_pack_file_dir,
+ revprops_shard_path,
+ pb->shard,
+ ffd->max_files_per_dir,
+ (int)(0.9*ffd->revprop_pack_size),
+ ffd->compress_packed_revprops
+ ? SVN__COMPRESSION_ZLIB_DEFAULT
+ : SVN__COMPRESSION_NONE,
+ pb->cancel_func,
+ pb->cancel_baton,
+ pool));
+ }
+
+ /* Update the min-unpacked-rev file to reflect our newly packed shard. */
+ SVN_ERR(svn_fs_fs__write_min_unpacked_rev(pb->fs,
+ (svn_revnum_t)((pb->shard + 1) * ffd->max_files_per_dir),
+ pool));
+ ffd->min_unpacked_rev
+ = (svn_revnum_t)((pb->shard + 1) * ffd->max_files_per_dir);
+
+ /* Finally, remove the existing shard directories.
+ * For revprops, clean up older obsolete shards as well as they might
+ * have been left over from an interrupted FS upgrade. */
+ SVN_ERR(svn_io_remove_dir2(pb->rev_shard_path, TRUE,
+ pb->cancel_func, pb->cancel_baton, pool));
+ if (pb->revsprops_dir)
+ {
+ svn_node_kind_t kind = svn_node_dir;
+ apr_int64_t to_cleanup = pb->shard;
+ do
+ {
+ SVN_ERR(svn_fs_fs__delete_revprops_shard(revprops_shard_path,
+ to_cleanup,
+ ffd->max_files_per_dir,
+ pb->cancel_func,
+ pb->cancel_baton,
+ pool));
+
+ /* If the previous shard exists, clean it up as well.
+ Don't try to clean up shard 0 as it we can't tell quickly
+ whether it actually needs cleaning up. */
+ revprops_shard_path = svn_dirent_join(pb->revsprops_dir,
+ apr_psprintf(pool, "%" APR_INT64_T_FMT, --to_cleanup),
+ pool);
+ SVN_ERR(svn_io_check_path(revprops_shard_path, &kind, pool));
+ }
+ while (kind == svn_node_dir && to_cleanup > 0);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Pack the shard described by BATON.
+ *
+ * If for some reason we detect a partial packing already performed,
+ * we remove the pack file and start again.
+ */
+static svn_error_t *
+pack_shard(struct pack_baton *baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = baton->fs->fsap_data;
+ const char *rev_pack_file_dir;
+
+ /* Notify caller we're starting to pack this shard. */
+ if (baton->notify_func)
+ SVN_ERR(baton->notify_func(baton->notify_baton, baton->shard,
+ svn_fs_pack_notify_start, pool));
+
+ /* Some useful paths. */
+ rev_pack_file_dir = svn_dirent_join(baton->revs_dir,
+ apr_psprintf(pool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ baton->shard),
+ pool);
+ baton->rev_shard_path = svn_dirent_join(baton->revs_dir,
+ apr_psprintf(pool,
+ "%" APR_INT64_T_FMT,
+ baton->shard),
+ pool);
+
+ /* pack the revision content */
+ SVN_ERR(pack_rev_shard(baton->fs, rev_pack_file_dir, baton->rev_shard_path,
+ baton->shard, ffd->max_files_per_dir,
+ baton->max_mem, baton->cancel_func,
+ baton->cancel_baton, pool));
+
+ /* For newer repo formats, we only acquired the pack lock so far.
+ Before modifying the repo state by switching over to the packed
+ data, we need to acquire the global (write) lock. */
+ if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
+ SVN_ERR(svn_fs_fs__with_write_lock(baton->fs, synced_pack_shard, baton,
+ pool));
+ else
+ SVN_ERR(synced_pack_shard(baton, pool));
+
+ /* Notify caller we're starting to pack this shard. */
+ if (baton->notify_func)
+ SVN_ERR(baton->notify_func(baton->notify_baton, baton->shard,
+ svn_fs_pack_notify_end, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type. BATON is a 'struct pack_baton *'.
+
+ WARNING: if you add a call to this function, please note:
+ The code currently assumes that any piece of code running with
+ the write-lock set can rely on the ffd->min_unpacked_rev and
+ ffd->min_unpacked_revprop caches to be up-to-date (and, by
+ extension, on not having to use a retry when calling
+ svn_fs_fs__path_rev_absolute() and friends). If you add a call
+ to this function, consider whether you have to call
+ svn_fs_fs__update_min_unpacked_rev().
+ See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
+ */
+static svn_error_t *
+pack_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct pack_baton *pb = baton;
+ fs_fs_data_t *ffd = pb->fs->fsap_data;
+ apr_int64_t completed_shards;
+ svn_revnum_t youngest;
+ apr_pool_t *iterpool;
+
+ /* If the repository isn't a new enough format, we don't support packing.
+ Return a friendly error to that effect. */
+ if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
+ ffd->format);
+
+ /* If we aren't using sharding, we can't do any packing, so quit. */
+ if (!ffd->max_files_per_dir)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev, pb->fs,
+ pool));
+
+ SVN_ERR(svn_fs_fs__youngest_rev(&youngest, pb->fs, pool));
+ completed_shards = (youngest + 1) / ffd->max_files_per_dir;
+
+ /* See if we've already completed all possible shards thus far. */
+ if (ffd->min_unpacked_rev == (completed_shards * ffd->max_files_per_dir))
+ return SVN_NO_ERROR;
+
+ pb->revs_dir = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ pb->revsprops_dir = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
+ pool);
+
+ iterpool = svn_pool_create(pool);
+ for (pb->shard = ffd->min_unpacked_rev / ffd->max_files_per_dir;
+ pb->shard < completed_shards;
+ pb->shard++)
+ {
+ svn_pool_clear(iterpool);
+
+ if (pb->cancel_func)
+ SVN_ERR(pb->cancel_func(pb->cancel_baton));
+
+ SVN_ERR(pack_shard(pb, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+ apr_size_t max_mem,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct pack_baton pb = { 0 };
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_error_t *err;
+
+ pb.fs = fs;
+ pb.notify_func = notify_func;
+ pb.notify_baton = notify_baton;
+ pb.cancel_func = cancel_func;
+ pb.cancel_baton = cancel_baton;
+ pb.max_mem = max_mem ? max_mem : DEFAULT_MAX_MEM;
+
+ if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
+ {
+ /* Newer repositories provide a pack operation specific lock.
+ Acquire it to prevent concurrent packs.
+
+ Since the file lock's lifetime is bound to a pool, we create a
+ separate subpool here to release the lock immediately after the
+ operation finished.
+ */
+ err = svn_fs_fs__with_pack_lock(fs, pack_body, &pb, pool);
+ }
+ else
+ {
+ /* Use the global write lock for older repos. */
+ err = svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/libsvn_fs_fs/pack.h b/subversion/libsvn_fs_fs/pack.h
new file mode 100644
index 0000000..b11f999
--- /dev/null
+++ b/subversion/libsvn_fs_fs/pack.h
@@ -0,0 +1,70 @@
+/* pack.h : interface FSFS pack functionality
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__PACK_H
+#define SVN_LIBSVN_FS__PACK_H
+
+#include "fs.h"
+
+/* Possibly pack the repository at PATH. This just take full shards, and
+ combines all the revision files into a single one, with a manifest header
+ when required by the repository format.
+
+ MAX_MEM limits the size of in-memory data structures needed for reordering
+ items in format 7 repositories. 0 means use the built-in default.
+
+ If given, NOTIFY_FUNC will be called with NOTIFY_BATON to report progress.
+ Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
+
+ Existing filesystem references need not change. */
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+ apr_size_t max_mem,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * For the packed revision @a rev in @a fs, determine the offset within
+ * the revision pack file and return it in @a rev_offset. Use @a pool for
+ * allocations.
+ */
+svn_error_t *
+svn_fs_fs__get_packed_offset(apr_off_t *rev_offset,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the svn_dir_entry_t* objects of DIRECTORY in an APR array
+ * allocated in RESULT_POOL with entries added in storage (on-disk) order.
+ * FS' format will be used to pick the optimal ordering strategy. Use
+ * SCRATCH_POOL for temporary allocations.
+ */
+apr_array_header_t *
+svn_fs_fs__order_dir_entries(svn_fs_t *fs,
+ apr_hash_t *directory,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/recovery.c b/subversion/libsvn_fs_fs/recovery.c
new file mode 100644
index 0000000..125d47a
--- /dev/null
+++ b/subversion/libsvn_fs_fs/recovery.c
@@ -0,0 +1,509 @@
+/* recovery.c --- FSFS recovery functionality
+*
+ * ====================================================================
+ * 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 "recovery.h"
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "private/svn_string_private.h"
+
+#include "index.h"
+#include "low_level.h"
+#include "rep-cache.h"
+#include "revprops.h"
+#include "util.h"
+#include "cached_data.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Part of the recovery procedure. Return the largest revision *REV in
+ filesystem FS. Use POOL for temporary allocation. */
+static svn_error_t *
+recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
+{
+ /* Discovering the largest revision in the filesystem would be an
+ expensive operation if we did a readdir() or searched linearly,
+ so we'll do a form of binary search. left is a revision that we
+ know exists, right a revision that we know does not exist. */
+ apr_pool_t *iterpool;
+ svn_revnum_t left, right = 1;
+
+ iterpool = svn_pool_create(pool);
+ /* Keep doubling right, until we find a revision that doesn't exist. */
+ while (1)
+ {
+ svn_error_t *err;
+ svn_fs_fs__revision_file_t *file;
+ svn_pool_clear(iterpool);
+
+ err = svn_fs_fs__open_pack_or_rev_file(&file, fs, right, iterpool,
+ iterpool);
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ break;
+ }
+ else
+ SVN_ERR(err);
+
+ right <<= 1;
+ }
+
+ left = right >> 1;
+
+ /* We know that left exists and right doesn't. Do a normal bsearch to find
+ the last revision. */
+ while (left + 1 < right)
+ {
+ svn_revnum_t probe = left + ((right - left) / 2);
+ svn_error_t *err;
+ svn_fs_fs__revision_file_t *file;
+ svn_pool_clear(iterpool);
+
+ err = svn_fs_fs__open_pack_or_rev_file(&file, fs, probe, iterpool,
+ iterpool);
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ right = probe;
+ }
+ else
+ {
+ SVN_ERR(err);
+ left = probe;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ /* left is now the largest revision that exists. */
+ *rev = left;
+ return SVN_NO_ERROR;
+}
+
+/* A baton for reading a fixed amount from an open file. For
+ recover_find_max_ids() below. */
+struct recover_read_from_file_baton
+{
+ svn_stream_t *stream;
+ apr_pool_t *pool;
+ apr_off_t remaining;
+};
+
+/* A stream read handler used by recover_find_max_ids() below.
+ Read and return at most BATON->REMAINING bytes from the stream,
+ returning nothing after that to indicate EOF. */
+static svn_error_t *
+read_handler_recover(void *baton, char *buffer, apr_size_t *len)
+{
+ struct recover_read_from_file_baton *b = baton;
+ apr_size_t bytes_to_read = *len;
+
+ if (b->remaining == 0)
+ {
+ /* Return a successful read of zero bytes to signal EOF. */
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+
+ if ((apr_int64_t)bytes_to_read > (apr_int64_t)b->remaining)
+ bytes_to_read = (apr_size_t)b->remaining;
+ b->remaining -= bytes_to_read;
+
+ return svn_stream_read_full(b->stream, buffer, &bytes_to_read);
+}
+
+/* Part of the recovery procedure. Read the directory noderev at offset
+ OFFSET of file REV_FILE (the revision file of revision REV of
+ filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
+ and copy-id of that node, if greater than the current value stored
+ in either. Recurse into any child directories that were modified in
+ this revision.
+
+ MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
+
+ Perform temporary allocation in POOL. */
+static svn_error_t *
+recover_find_max_ids(svn_fs_t *fs,
+ svn_revnum_t rev,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_off_t offset,
+ apr_uint64_t *max_node_id,
+ apr_uint64_t *max_copy_id,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__rep_header_t *header;
+ struct recover_read_from_file_baton baton;
+ svn_stream_t *stream;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ node_revision_t *noderev;
+ svn_error_t *err;
+
+ baton.stream = rev_file->stream;
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
+ SVN_ERR(svn_fs_fs__read_noderev(&noderev, baton.stream, pool, pool));
+
+ /* Check that this is a directory. It should be. */
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Recovery encountered a non-directory node"));
+
+ /* Get the data location. No data location indicates an empty directory. */
+ if (!noderev->data_rep)
+ return SVN_NO_ERROR;
+
+ /* If the directory's data representation wasn't changed in this revision,
+ we've already scanned the directory's contents for noderevs, so we don't
+ need to again. This will occur if a property is changed on a directory
+ without changing the directory's contents. */
+ if (noderev->data_rep->revision != rev)
+ return SVN_NO_ERROR;
+
+ /* We could use get_dir_contents(), but this is much cheaper. It does
+ rely on directory entries being stored as PLAIN reps, though. */
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL,
+ noderev->data_rep->item_index, pool));
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
+ SVN_ERR(svn_fs_fs__read_rep_header(&header, baton.stream, pool, pool));
+ if (header->type != svn_fs_fs__rep_plain)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Recovery encountered a deltified directory "
+ "representation"));
+
+ /* Now create a stream that's allowed to read only as much data as is
+ stored in the representation. Note that this is a directory, i.e.
+ represented using the hash format on disk and can never have 0 length. */
+ baton.pool = pool;
+ baton.remaining = noderev->data_rep->expanded_size
+ ? noderev->data_rep->expanded_size
+ : noderev->data_rep->size;
+ stream = svn_stream_create(&baton, pool);
+ svn_stream_set_read2(stream, NULL /* only full read support */,
+ read_handler_recover);
+
+ /* Now read the entries from that stream. */
+ entries = apr_hash_make(pool);
+ err = svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool);
+ if (err)
+ {
+ svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
+
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_quick_wrapf(err,
+ _("malformed representation for node-revision '%s'"),
+ id_str->data);
+ }
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Now check each of the entries in our directory to find new node and
+ copy ids, and recurse into new subdirectories. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ char *str_val;
+ char *str;
+ svn_node_kind_t kind;
+ const svn_fs_id_t *id;
+ const svn_fs_fs__id_part_t *rev_item;
+ apr_uint64_t node_id, copy_id;
+ apr_off_t child_dir_offset;
+ const svn_string_t *path = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ str_val = apr_pstrdup(iterpool, path->data);
+
+ str = svn_cstring_tokenize(" ", &str_val);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+
+ if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
+ kind = svn_node_file;
+ else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
+ kind = svn_node_dir;
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+ }
+
+ str = svn_cstring_tokenize(" ", &str_val);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+
+ SVN_ERR(svn_fs_fs__id_parse(&id, str, iterpool));
+
+ rev_item = svn_fs_fs__id_rev_item(id);
+ if (rev_item->revision != rev)
+ {
+ /* If the node wasn't modified in this revision, we've already
+ checked the node and copy id. */
+ continue;
+ }
+
+ node_id = svn_fs_fs__id_node_id(id)->number;
+ copy_id = svn_fs_fs__id_copy_id(id)->number;
+
+ if (node_id > *max_node_id)
+ *max_node_id = node_id;
+ if (copy_id > *max_copy_id)
+ *max_copy_id = copy_id;
+
+ if (kind == svn_node_file)
+ continue;
+
+ SVN_ERR(svn_fs_fs__item_offset(&child_dir_offset, fs,
+ rev_file, rev, NULL, rev_item->number,
+ iterpool));
+ SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
+ max_node_id, max_copy_id, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Part of the recovery procedure. Given an open non-packed revision file
+ REV_FILE for REV, locate the trailer that specifies the offset to the root
+ node-id and store this offset in *ROOT_OFFSET. Do temporary allocations in
+ POOL. */
+static svn_error_t *
+recover_get_root_offset(apr_off_t *root_offset,
+ svn_revnum_t rev,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_pool_t *pool)
+{
+ char buffer[64];
+ svn_stringbuf_t *trailer;
+ apr_off_t start;
+ apr_off_t end;
+ apr_size_t len;
+
+ SVN_ERR_ASSERT(!rev_file->is_packed);
+
+ /* We will assume that the last line containing the two offsets (to the root
+ node-id and to the changed path information) will never be longer than 64
+ characters. */
+ end = 0;
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &end, pool));
+
+ if (end < sizeof(buffer))
+ {
+ len = (apr_size_t)end;
+ start = 0;
+ }
+ else
+ {
+ len = sizeof(buffer);
+ start = end - sizeof(buffer);
+ }
+
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &start, pool));
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, len,
+ NULL, NULL, pool));
+
+ trailer = svn_stringbuf_ncreate(buffer, len, pool);
+ SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset, NULL, trailer, rev));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for recover_body below. */
+struct recover_baton {
+ svn_fs_t *fs;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+/* The work-horse for svn_fs_fs__recover, called with the FS
+ write lock. This implements the svn_fs_fs__with_write_lock()
+ 'body' callback type. BATON is a 'struct recover_baton *'. */
+static svn_error_t *
+recover_body(void *baton, apr_pool_t *pool)
+{
+ struct recover_baton *b = baton;
+ svn_fs_t *fs = b->fs;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_revnum_t max_rev;
+ apr_uint64_t next_node_id = 0;
+ apr_uint64_t next_copy_id = 0;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t youngest_revprops_kind;
+
+ /* The admin may have created a plain copy of this repo before attempting
+ to recover it (hotcopy may or may not work with corrupted repos).
+ Bump the instance ID. */
+ SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
+
+ /* We need to know the largest revision in the filesystem. */
+ SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
+
+ /* Get the expected youngest revision */
+ SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev, fs, pool));
+
+ /* Policy note:
+
+ Since the revprops file is written after the revs file, the true
+ maximum available revision is the youngest one for which both are
+ present. That's probably the same as the max_rev we just found,
+ but if it's not, we could, in theory, repeatedly decrement
+ max_rev until we find a revision that has both a revs and
+ revprops file, then write db/current with that.
+
+ But we choose not to. If a repository is so corrupt that it's
+ missing at least one revprops file, we shouldn't assume that the
+ youngest revision for which both the revs and revprops files are
+ present is healthy. In other words, we're willing to recover
+ from a missing or out-of-date db/current file, because db/current
+ is truly redundant -- it's basically a cache so we don't have to
+ find max_rev each time, albeit a cache with unusual semantics,
+ since it also officially defines when a revision goes live. But
+ if we're missing more than the cache, it's time to back out and
+ let the admin reconstruct things by hand: correctness at that
+ point may depend on external things like checking a commit email
+ list, looking in particular working copies, etc.
+
+ This policy matches well with a typical naive backup scenario.
+ Say you're rsyncing your FSFS repository nightly to the same
+ location. Once revs and revprops are written, you've got the
+ maximum rev; if the backup should bomb before db/current is
+ written, then db/current could stay arbitrarily out-of-date, but
+ we can still recover. It's a small window, but we might as well
+ do what we can. */
+
+ /* Even if db/current were missing, it would be created with 0 by
+ get_youngest(), so this conditional remains valid. */
+ if (youngest_rev > max_rev)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Expected current rev to be <= %ld "
+ "but found %ld"), max_rev, youngest_rev);
+
+ /* We only need to search for maximum IDs for old FS formats which
+ se global ID counters. */
+ if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ /* Next we need to find the maximum node id and copy id in use across the
+ filesystem. Unfortunately, the only way we can get this information
+ is to scan all the noderevs of all the revisions and keep track as
+ we go along. */
+ svn_revnum_t rev;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (rev = 0; rev <= max_rev; rev++)
+ {
+ svn_fs_fs__revision_file_t *rev_file;
+ apr_off_t root_offset;
+
+ svn_pool_clear(iterpool);
+
+ if (b->cancel_func)
+ SVN_ERR(b->cancel_func(b->cancel_baton));
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool,
+ iterpool));
+ SVN_ERR(recover_get_root_offset(&root_offset, rev, rev_file, pool));
+ SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
+ &next_node_id, &next_copy_id, pool));
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now that we finally have the maximum revision, node-id and copy-id, we
+ can bump the two ids to get the next of each. */
+ next_node_id++;
+ next_copy_id++;
+ }
+
+ /* Before setting current, verify that there is a revprops file
+ for the youngest revision. (Issue #2992) */
+ SVN_ERR(svn_io_check_path(svn_fs_fs__path_revprops(fs, max_rev, pool),
+ &youngest_revprops_kind, pool));
+ if (youngest_revprops_kind == svn_node_none)
+ {
+ svn_boolean_t missing = TRUE;
+ if (!svn_fs_fs__packed_revprop_available(&missing, fs, max_rev, pool))
+ {
+ if (missing)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but no "
+ "revprops file"),
+ max_rev);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but the "
+ "revprops file is inaccessible"),
+ max_rev);
+ }
+ }
+ }
+ else if (youngest_revprops_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a non-file where its "
+ "revprops file should be"),
+ max_rev);
+ }
+
+ /* Prune younger-than-(newfound-youngest) revisions from the rep
+ cache if sharing is enabled taking care not to create the cache
+ if it does not exist. */
+ if (ffd->rep_sharing_allowed)
+ {
+ svn_boolean_t rep_cache_exists;
+
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
+ if (rep_cache_exists)
+ SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
+ }
+
+ /* Now store the discovered youngest revision, and the next IDs if
+ relevant, in a new 'current' file. */
+ return svn_fs_fs__write_current(fs, max_rev, next_node_id, next_copy_id,
+ pool);
+}
+
+/* This implements the fs_library_vtable_t.recover() API. */
+svn_error_t *
+svn_fs_fs__recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct recover_baton b;
+
+ /* We have no way to take out an exclusive lock in FSFS, so we're
+ restricted as to the types of recovery we can do. Luckily,
+ we just want to recreate the 'current' file, and we can do that just
+ by blocking other writers. */
+ b.fs = fs;
+ b.cancel_func = cancel_func;
+ b.cancel_baton = cancel_baton;
+ return svn_fs_fs__with_all_locks(fs, recover_body, &b, pool);
+}
diff --git a/subversion/libsvn_fs_fs/recovery.h b/subversion/libsvn_fs_fs/recovery.h
new file mode 100644
index 0000000..5a6b68a
--- /dev/null
+++ b/subversion/libsvn_fs_fs/recovery.h
@@ -0,0 +1,36 @@
+/* recovery.h : interface to the FSFS recovery functionality
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__RECOVERY_H
+#define SVN_LIBSVN_FS__RECOVERY_H
+
+#include "fs.h"
+
+/* Recover the fsfs associated with filesystem FS.
+ Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
+ Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/rep-cache-db.h b/subversion/libsvn_fs_fs/rep-cache-db.h
index 255ea07..0f2cc89 100644
--- a/subversion/libsvn_fs_fs/rep-cache-db.h
+++ b/subversion/libsvn_fs_fs/rep-cache-db.h
@@ -1,4 +1,4 @@
-/* This file is automatically generated from rep-cache-db.sql and .dist_sandbox/subversion-1.8.13/subversion/libsvn_fs_fs/token-map.h.
+/* This file is automatically generated from rep-cache-db.sql and .dist_sandbox/subversion-1.9.7/subversion/libsvn_fs_fs/token-map.h.
* Do not edit this file -- edit the source and rerun gen-make.py */
#define STMT_CREATE_SCHEMA 0
@@ -58,6 +58,12 @@
"INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0) " \
""
+#define STMT_UNLOCK_REP 7
+#define STMT_7_INFO {"STMT_UNLOCK_REP", NULL}
+#define STMT_7 \
+ "ROLLBACK TRANSACTION; " \
+ ""
+
#define REP_CACHE_DB_SQL_DECLARE_STATEMENTS(varname) \
static const char * const varname[] = { \
STMT_0, \
@@ -67,6 +73,7 @@
STMT_4, \
STMT_5, \
STMT_6, \
+ STMT_7, \
NULL \
}
@@ -79,5 +86,6 @@
STMT_4_INFO, \
STMT_5_INFO, \
STMT_6_INFO, \
+ STMT_7_INFO, \
{NULL, NULL} \
}
diff --git a/subversion/libsvn_fs_fs/rep-cache-db.sql b/subversion/libsvn_fs_fs/rep-cache-db.sql
index b88c3e0..caaac33 100644
--- a/subversion/libsvn_fs_fs/rep-cache-db.sql
+++ b/subversion/libsvn_fs_fs/rep-cache-db.sql
@@ -63,3 +63,6 @@ WHERE revision > ?1
-- STMT_LOCK_REP
BEGIN TRANSACTION;
INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0)
+
+-- STMT_UNLOCK_REP
+ROLLBACK TRANSACTION;
diff --git a/subversion/libsvn_fs_fs/rep-cache.c b/subversion/libsvn_fs_fs/rep-cache.c
index 0082266..437d603 100644
--- a/subversion/libsvn_fs_fs/rep-cache.c
+++ b/subversion/libsvn_fs_fs/rep-cache.c
@@ -50,19 +50,12 @@ path_rep_cache_db(const char *fs_path,
return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
}
-/* Check that REP refers to a revision that exists in FS. */
-static svn_error_t *
-rep_has_been_born(representation_t *rep,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- SVN_ERR_ASSERT(rep);
-
- SVN_ERR(svn_fs_fs__revision_exists(rep->revision, fs, pool));
-
- return SVN_NO_ERROR;
-}
-
+#define SVN_ERR_CLOSE(x, db) do \
+{ \
+ svn_error_t *svn__err = (x); \
+ if (svn__err) \
+ return svn_error_compose_create(svn__err, svn_sqlite__close(db)); \
+} while (0)
/** Library-private API's. **/
@@ -81,7 +74,7 @@ open_rep_cache(void *baton,
int version;
/* Open (or create) the sqlite database. It will be automatically
- closed when fs->pool is destoyed. */
+ closed when fs->pool is destroyed. */
db_path = path_rep_cache_db(fs->path, pool);
#ifndef WIN32
{
@@ -94,7 +87,7 @@ open_rep_cache(void *baton,
if (!exists)
{
const char *current = svn_fs_fs__path_current(fs, pool);
- svn_error_t *err = svn_io_file_create(db_path, "", pool);
+ svn_error_t *err = svn_io_file_create_empty(db_path, pool);
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
/* A real error. */
@@ -110,15 +103,15 @@ open_rep_cache(void *baton,
#endif
SVN_ERR(svn_sqlite__open(&sdb, db_path,
svn_sqlite__mode_rwcreate, statements,
- 0, NULL,
+ 0, NULL, 0,
fs->pool, pool));
- SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool));
+ SVN_ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool), sdb);
if (version < REP_CACHE_SCHEMA_FORMAT)
{
/* Must be 0 -- an uninitialized (no schema) database. Create
the schema. Results in schema version of 1. */
- SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA));
+ SVN_ERR_CLOSE(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA), sdb);
}
/* This is used as a flag that the database is available so don't
@@ -135,7 +128,25 @@ svn_fs_fs__open_rep_cache(svn_fs_t *fs,
fs_fs_data_t *ffd = fs->fsap_data;
svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
open_rep_cache, fs, pool);
- return svn_error_quick_wrap(err, _("Couldn't open rep-cache database"));
+ return svn_error_quick_wrapf(err,
+ _("Couldn't open rep-cache database '%s'"),
+ svn_dirent_local_style(
+ path_rep_cache_db(fs->path, pool), pool));
+}
+
+svn_error_t *
+svn_fs_fs__close_rep_cache(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->rep_cache_db)
+ {
+ SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
+ ffd->rep_cache_db = NULL;
+ ffd->rep_cache_db_opened = 0;
+ }
+
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -188,7 +199,7 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
max = svn_sqlite__column_revnum(stmt, 0);
SVN_ERR(svn_sqlite__reset(stmt));
if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
- SVN_ERR(svn_fs_fs__revision_exists(max, fs, iterpool));
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(max, fs, iterpool));
}
SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
@@ -203,6 +214,7 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
representation_t *rep;
const char *sha1_digest;
svn_error_t *err;
+ svn_checksum_t *checksum;
/* Clear ITERPOOL occasionally. */
if (iterations++ % 16 == 0)
@@ -218,14 +230,17 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
/* Construct a representation_t. */
rep = apr_pcalloc(iterpool, sizeof(*rep));
+ svn_fs_fs__id_txn_reset(&rep->txn_id);
sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
- err = svn_checksum_parse_hex(&rep->sha1_checksum,
- svn_checksum_sha1, sha1_digest,
- iterpool);
+ err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ sha1_digest, iterpool);
if (err)
return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ rep->has_sha1 = TRUE;
+ memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
rep->revision = svn_sqlite__column_revnum(stmt, 1);
- rep->offset = svn_sqlite__column_int64(stmt, 2);
+ rep->item_index = svn_sqlite__column_int64(stmt, 2);
rep->size = svn_sqlite__column_int64(stmt, 3);
rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
@@ -275,9 +290,12 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
if (have_row)
{
*rep = apr_pcalloc(pool, sizeof(**rep));
- (*rep)->sha1_checksum = svn_checksum_dup(checksum, pool);
+ svn_fs_fs__id_txn_reset(&(*rep)->txn_id);
+ memcpy((*rep)->sha1_digest, checksum->digest,
+ sizeof((*rep)->sha1_digest));
+ (*rep)->has_sha1 = TRUE;
(*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
- (*rep)->offset = svn_sqlite__column_int64(stmt, 1);
+ (*rep)->item_index = svn_sqlite__column_int64(stmt, 1);
(*rep)->size = svn_sqlite__column_int64(stmt, 2);
(*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
}
@@ -287,7 +305,16 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
SVN_ERR(svn_sqlite__reset(stmt));
if (*rep)
- SVN_ERR(rep_has_been_born(*rep, fs, pool));
+ {
+ /* Check that REP refers to a revision that exists in FS. */
+ svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision,
+ fs, pool);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Checksum '%s' in rep-cache is beyond HEAD",
+ svn_checksum_to_cstring_display(checksum,
+ pool));
+ }
return SVN_NO_ERROR;
}
@@ -295,28 +322,30 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
svn_error_t *
svn_fs_fs__set_rep_reference(svn_fs_t *fs,
representation_t *rep,
- svn_boolean_t reject_dup,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_sqlite__stmt_t *stmt;
svn_error_t *err;
+ svn_checksum_t checksum;
+ checksum.kind = svn_checksum_sha1;
+ checksum.digest = rep->sha1_digest;
SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
if (! ffd->rep_cache_db)
SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
/* We only allow SHA1 checksums in this table. */
- if (rep->sha1_checksum == NULL)
+ if (! rep->has_sha1)
return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
_("Only SHA1 checksums can be used as keys in the "
"rep_cache table.\n"));
SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
- svn_checksum_to_cstring(rep->sha1_checksum, pool),
+ svn_checksum_to_cstring(&checksum, pool),
(apr_int64_t) rep->revision,
- (apr_int64_t) rep->offset,
+ (apr_int64_t) rep->item_index,
(apr_int64_t) rep->size,
(apr_int64_t) rep->expanded_size));
@@ -331,35 +360,11 @@ svn_fs_fs__set_rep_reference(svn_fs_t *fs,
svn_error_clear(err);
/* Constraint failed so the mapping for SHA1_CHECKSUM->REP
- should exist. If so, and the value is the same one we were
- about to write, that's cool -- just do nothing. If, however,
- the value is *different*, that's a red flag! */
- SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, rep->sha1_checksum,
- pool));
+ should exist. If so that's cool -- just do nothing. If not,
+ that's a red flag! */
+ SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool));
- if (old_rep)
- {
- if (reject_dup && ((old_rep->revision != rep->revision)
- || (old_rep->offset != rep->offset)
- || (old_rep->size != rep->size)
- || (old_rep->expanded_size != rep->expanded_size)))
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- apr_psprintf(pool,
- _("Representation key for checksum '%%s' exists "
- "in filesystem '%%s' with a different value "
- "(%%ld,%%%s,%%%s,%%%s) than what we were about "
- "to store (%%ld,%%%s,%%%s,%%%s)"),
- APR_OFF_T_FMT, SVN_FILESIZE_T_FMT,
- SVN_FILESIZE_T_FMT, APR_OFF_T_FMT,
- SVN_FILESIZE_T_FMT, SVN_FILESIZE_T_FMT),
- svn_checksum_to_cstring_display(rep->sha1_checksum, pool),
- fs->path, old_rep->revision, old_rep->offset, old_rep->size,
- old_rep->expanded_size, rep->revision, rep->offset, rep->size,
- rep->expanded_size);
- else
- return SVN_NO_ERROR;
- }
- else
+ if (!old_rep)
{
/* Something really odd at this point, we failed to insert the
checksum AND failed to read an existing checksum. Do we need
@@ -391,9 +396,13 @@ svn_fs_fs__del_rep_reference(svn_fs_t *fs,
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
- apr_pool_t *pool)
+/* Start a transaction to take an SQLite reserved lock that prevents
+ other writes.
+
+ See unlock_rep_cache(). */
+static svn_error_t *
+lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
@@ -404,3 +413,31 @@ svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
return SVN_NO_ERROR;
}
+
+/* End the transaction started by lock_rep_cache(). */
+static svn_error_t *
+unlock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
+
+ SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *,
+ apr_pool_t *),
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR(lock_rep_cache(fs, pool));
+ err = body(baton, pool);
+ return svn_error_compose_create(err, unlock_rep_cache(fs, pool));
+}
diff --git a/subversion/libsvn_fs_fs/rep-cache.h b/subversion/libsvn_fs_fs/rep-cache.h
index 3ccb056..75072a0 100644
--- a/subversion/libsvn_fs_fs/rep-cache.h
+++ b/subversion/libsvn_fs_fs/rep-cache.h
@@ -40,6 +40,10 @@ svn_error_t *
svn_fs_fs__open_rep_cache(svn_fs_t *fs,
apr_pool_t *pool);
+/* Close the rep cache database associated with FS. */
+svn_error_t *
+svn_fs_fs__close_rep_cache(svn_fs_t *fs);
+
/* Set *EXISTS to TRUE iff the rep-cache DB file exists. */
svn_error_t *
svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
@@ -61,7 +65,8 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
/* Return the representation REP in FS which has fulltext CHECKSUM.
REP is allocated in POOL. If the rep cache database has not been
- opened, just set *REP to NULL. */
+ opened, just set *REP to NULL. Returns SVN_ERR_FS_CORRUPT if
+ a reference beyond HEAD is detected. */
svn_error_t *
svn_fs_fs__get_rep_reference(representation_t **rep,
svn_fs_t *fs,
@@ -69,16 +74,13 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
apr_pool_t *pool);
/* Set the representation REP in FS, using REP->CHECKSUM.
- Use POOL for temporary allocations.
-
- If the rep cache database has not been opened, this may be a no op.
+ Use POOL for temporary allocations. Returns SVN_ERR_FS_CORRUPT if
+ an existing reference beyond HEAD is detected.
- If REJECT_DUP is TRUE, return an error if there is an existing
- match for REP->CHECKSUM. */
+ If the rep cache database has not been opened, this may be a no op. */
svn_error_t *
svn_fs_fs__set_rep_reference(svn_fs_t *fs,
representation_t *rep,
- svn_boolean_t reject_dup,
apr_pool_t *pool);
/* Delete from the cache all reps corresponding to revisions younger
@@ -89,10 +91,14 @@ svn_fs_fs__del_rep_reference(svn_fs_t *fs,
apr_pool_t *pool);
/* Start a transaction to take an SQLite reserved lock that prevents
- other writes. */
+ other writes, call BODY, end the transaction, and return what BODY returned.
+ */
svn_error_t *
-svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
- apr_pool_t *pool);
+svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
#ifdef __cplusplus
}
diff --git a/subversion/libsvn_fs_fs/rev_file.c b/subversion/libsvn_fs_fs/rev_file.c
new file mode 100644
index 0000000..7c18ac8
--- /dev/null
+++ b/subversion/libsvn_fs_fs/rev_file.c
@@ -0,0 +1,306 @@
+/* rev_file.c --- revision file and index access functions
+ *
+ * ====================================================================
+ * 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 "rev_file.h"
+#include "fs_fs.h"
+#include "index.h"
+#include "low_level.h"
+#include "util.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_io_private.h"
+#include "svn_private_config.h"
+
+/* Initialize the *FILE structure for REVISION in filesystem FS. Set its
+ * pool member to the provided POOL. */
+static void
+init_revision_file(svn_fs_fs__revision_file_t *file,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ file->is_packed = svn_fs_fs__is_packed_rev(fs, revision);
+ file->start_revision = svn_fs_fs__packed_base_rev(fs, revision);
+
+ file->file = NULL;
+ file->stream = NULL;
+ file->p2l_stream = NULL;
+ file->l2p_stream = NULL;
+ file->block_size = ffd->block_size;
+ file->l2p_offset = -1;
+ file->l2p_checksum = NULL;
+ file->p2l_offset = -1;
+ file->p2l_checksum = NULL;
+ file->footer_offset = -1;
+ file->pool = pool;
+}
+
+/* Baton type for set_read_only() */
+typedef struct set_read_only_baton_t
+{
+ /* File to set to read-only. */
+ const char *file_path;
+
+ /* Scratch pool sufficient life time.
+ * Ideally the pool that we registered the cleanup on. */
+ apr_pool_t *pool;
+} set_read_only_baton_t;
+
+/* APR pool cleanup callback taking a set_read_only_baton_t baton and then
+ * (trying to) set the specified file to r/o mode. */
+static apr_status_t
+set_read_only(void *baton)
+{
+ set_read_only_baton_t *ro_baton = baton;
+ apr_status_t status = APR_SUCCESS;
+ svn_error_t *err;
+
+ err = svn_io_set_file_read_only(ro_baton->file_path, TRUE, ro_baton->pool);
+ if (err)
+ {
+ status = err->apr_err;
+ svn_error_clear(err);
+ }
+
+ return status;
+}
+
+/* If the file at PATH is read-only, attempt to make it writable. The
+ * original state will be restored with RESULT_POOL gets cleaned up.
+ * SCRATCH_POOL is for temporary allocations. */
+static svn_error_t *
+auto_make_writable(const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_read_only;
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_stat(&finfo, path, SVN__APR_FINFO_READONLY, scratch_pool));
+ SVN_ERR(svn_io__is_finfo_read_only(&is_read_only, &finfo, scratch_pool));
+
+ if (is_read_only)
+ {
+ /* Tell the pool to restore the r/o state upon cleanup
+ (assuming the file will still exist, failing silently otherwise). */
+ set_read_only_baton_t *baton = apr_pcalloc(result_pool,
+ sizeof(*baton));
+ baton->pool = result_pool;
+ baton->file_path = apr_pstrdup(result_pool, path);
+ apr_pool_cleanup_register(result_pool, baton,
+ set_read_only, apr_pool_cleanup_null);
+
+ /* Finally, allow write access (undoing it has already been scheduled
+ and is idempotent). */
+ SVN_ERR(svn_io_set_file_read_write(path, FALSE, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Core implementation of svn_fs_fs__open_pack_or_rev_file working on an
+ * existing, initialized FILE structure. If WRITABLE is TRUE, give write
+ * access to the file - temporarily resetting the r/o state if necessary.
+ */
+static svn_error_t *
+open_pack_or_rev_file(svn_fs_fs__revision_file_t *file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ svn_boolean_t writable,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_error_t *err;
+ svn_boolean_t retry = FALSE;
+
+ do
+ {
+ const char *path = svn_fs_fs__path_rev_absolute(fs, rev, scratch_pool);
+ apr_file_t *apr_file;
+ apr_int32_t flags = writable
+ ? APR_READ | APR_WRITE | APR_BUFFERED
+ : APR_READ | APR_BUFFERED;
+
+ /* We may have to *temporarily* enable write access. */
+ err = writable ? auto_make_writable(path, result_pool, scratch_pool)
+ : SVN_NO_ERROR;
+
+ /* open the revision file in buffered r/o or r/w mode */
+ if (!err)
+ err = svn_io_file_open(&apr_file, path, flags, APR_OS_DEFAULT,
+ result_pool);
+
+ if (!err)
+ {
+ file->file = apr_file;
+ file->stream = svn_stream_from_aprfile2(apr_file, TRUE,
+ result_pool);
+ file->is_packed = svn_fs_fs__is_packed_rev(fs, rev);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ /* Could not open the file. This may happen if the
+ * file once existed but got packed later. */
+ svn_error_clear(err);
+
+ /* if that was our 2nd attempt, leave it at that. */
+ if (retry)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+
+ /* We failed for the first time. Refresh cache & retry. */
+ SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, scratch_pool));
+ file->start_revision = svn_fs_fs__packed_base_rev(fs, rev);
+
+ retry = TRUE;
+ }
+ else
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+ }
+ }
+ else
+ {
+ retry = FALSE;
+ }
+ }
+ while (retry);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *file = apr_palloc(result_pool, sizeof(**file));
+ init_revision_file(*file, fs, rev, result_pool);
+
+ return svn_error_trace(open_pack_or_rev_file(*file, fs, rev, FALSE,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file_writable(svn_fs_fs__revision_file_t** file,
+ svn_fs_t* fs,
+ svn_revnum_t rev,
+ apr_pool_t* result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *file = apr_palloc(result_pool, sizeof(**file));
+ init_revision_file(*file, fs, rev, result_pool);
+
+ return svn_error_trace(open_pack_or_rev_file(*file, fs, rev, TRUE,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_fs_fs__auto_read_footer(svn_fs_fs__revision_file_t *file)
+{
+ if (file->l2p_offset == -1)
+ {
+ apr_off_t filesize = 0;
+ unsigned char footer_length;
+ svn_stringbuf_t *footer;
+
+ /* Determine file size. */
+ SVN_ERR(svn_io_file_seek(file->file, APR_END, &filesize, file->pool));
+
+ /* Read last byte (containing the length of the footer). */
+ SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL,
+ filesize - 1, file->pool));
+ SVN_ERR(svn_io_file_read_full2(file->file, &footer_length,
+ sizeof(footer_length), NULL, NULL,
+ file->pool));
+
+ /* Read footer. */
+ footer = svn_stringbuf_create_ensure(footer_length, file->pool);
+ SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL,
+ filesize - 1 - footer_length,
+ file->pool));
+ SVN_ERR(svn_io_file_read_full2(file->file, footer->data, footer_length,
+ &footer->len, NULL, file->pool));
+ footer->data[footer->len] = '\0';
+
+ /* Extract index locations. */
+ SVN_ERR(svn_fs_fs__parse_footer(&file->l2p_offset, &file->l2p_checksum,
+ &file->p2l_offset, &file->p2l_checksum,
+ footer, file->start_revision,
+ file->pool));
+ file->footer_offset = filesize - footer_length - 1;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__open_proto_rev_file(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t* result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *apr_file;
+ SVN_ERR(svn_io_file_open(&apr_file,
+ svn_fs_fs__path_txn_proto_rev(fs, txn_id,
+ scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ result_pool));
+
+ *file = apr_pcalloc(result_pool, sizeof(**file));
+ (*file)->file = apr_file;
+ (*file)->is_packed = FALSE;
+ (*file)->start_revision = SVN_INVALID_REVNUM;
+ (*file)->stream = svn_stream_from_aprfile2(apr_file, TRUE, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__close_revision_file(svn_fs_fs__revision_file_t *file)
+{
+ if (file->stream)
+ SVN_ERR(svn_stream_close(file->stream));
+ if (file->file)
+ SVN_ERR(svn_io_file_close(file->file, file->pool));
+
+ file->file = NULL;
+ file->stream = NULL;
+ file->l2p_stream = NULL;
+ file->p2l_stream = NULL;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/rev_file.h b/subversion/libsvn_fs_fs/rev_file.h
new file mode 100644
index 0000000..9511994
--- /dev/null
+++ b/subversion/libsvn_fs_fs/rev_file.h
@@ -0,0 +1,145 @@
+/* rev_file.h --- revision file and index access data structure
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__REV_FILE_H
+#define SVN_LIBSVN_FS__REV_FILE_H
+
+#include "svn_fs.h"
+#include "id.h"
+
+/* In format 7, index files must be read in sync with the respective
+ * revision / pack file. I.e. we must use packed index files for packed
+ * rev files and unpacked ones for non-packed rev files. So, the whole
+ * point is to open them with matching "is packed" setting in case some
+ * background pack process was run.
+ */
+
+/* Opaque index stream type.
+ */
+typedef struct svn_fs_fs__packed_number_stream_t
+ svn_fs_fs__packed_number_stream_t;
+
+/* Data file, including indexes data, and associated properties for
+ * START_REVISION. As the FILE is kept open, background pack operations
+ * will not cause access to this file to fail.
+ */
+typedef struct svn_fs_fs__revision_file_t
+{
+ /* first (potentially only) revision in the rev / pack file.
+ * SVN_INVALID_REVNUM for txn proto-rev files. */
+ svn_revnum_t start_revision;
+
+ /* the revision was packed when the first file / stream got opened */
+ svn_boolean_t is_packed;
+
+ /* rev / pack file */
+ apr_file_t *file;
+
+ /* stream based on FILE and not NULL exactly when FILE is not NULL */
+ svn_stream_t *stream;
+
+ /* the opened P2L index stream or NULL. Always NULL for txns. */
+ svn_fs_fs__packed_number_stream_t *p2l_stream;
+
+ /* the opened L2P index stream or NULL. Always NULL for txns. */
+ svn_fs_fs__packed_number_stream_t *l2p_stream;
+
+ /* Copied from FS->FFD->BLOCK_SIZE upon creation. It allows us to
+ * use aligned seek() without having the FS handy. */
+ apr_off_t block_size;
+
+ /* Offset within FILE at which the rev data ends and the L2P index
+ * data starts. Less than P2L_OFFSET. -1 if svn_fs_fs__auto_read_footer
+ * has not been called, yet. */
+ apr_off_t l2p_offset;
+
+ /* MD5 checksum on the whole on-disk representation of the L2P index.
+ * NULL if svn_fs_fs__auto_read_footer has not been called, yet. */
+ svn_checksum_t *l2p_checksum;
+
+ /* Offset within FILE at which the L2P index ends and the P2L index
+ * data starts. Greater than L2P_OFFSET. -1 if svn_fs_fs__auto_read_footer
+ * has not been called, yet. */
+ apr_off_t p2l_offset;
+
+ /* MD5 checksum on the whole on-disk representation of the P2L index.
+ * NULL if svn_fs_fs__auto_read_footer has not been called, yet. */
+ svn_checksum_t *p2l_checksum;
+
+ /* Offset within FILE at which the P2L index ends and the footer starts.
+ * Greater than P2L_OFFSET. -1 if svn_fs_fs__auto_read_footer has not
+ * been called, yet. */
+ apr_off_t footer_offset;
+
+ /* pool containing this object */
+ apr_pool_t *pool;
+} svn_fs_fs__revision_file_t;
+
+/* Open the correct revision file for REV. If the filesystem FS has
+ * been packed, *FILE will be set to the packed file; otherwise, set *FILE
+ * to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the
+ * file doesn't exist. Allocate *FILE in RESULT_POOL and use SCRATCH_POOL
+ * for temporaries. */
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Open the correct revision file for REV with read and write access.
+ * If necessary, temporarily reset the file's read-only state. If the
+ * filesystem FS has been packed, *FILE will be set to the packed file;
+ * otherwise, set *FILE to the revision file for REV.
+ *
+ * Return SVN_ERR_FS_NO_SUCH_REVISION if the file doesn't exist.
+ * Allocate *FILE in RESULT_POOL and use SCRATCH_POOLfor temporaries. */
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file_writable(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* If the footer data in FILE has not been read, yet, do so now.
+ * Index locations will only be read upon request as we assume they get
+ * cached and the FILE is usually used for REP data access only.
+ * Hence, the separate step.
+ */
+svn_error_t *
+svn_fs_fs__auto_read_footer(svn_fs_fs__revision_file_t *file);
+
+/* Open the proto-rev file of transaction TXN_ID in FS and return it in *FILE.
+ * Allocate *FILE in RESULT_POOL use and SCRATCH_POOL for temporaries.. */
+svn_error_t *
+svn_fs_fs__open_proto_rev_file(svn_fs_fs__revision_file_t **file,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t* result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Close all files and streams in FILE.
+ */
+svn_error_t *
+svn_fs_fs__close_revision_file(svn_fs_fs__revision_file_t *file);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/revprops.c b/subversion/libsvn_fs_fs/revprops.c
new file mode 100644
index 0000000..dbb185b
--- /dev/null
+++ b/subversion/libsvn_fs_fs/revprops.c
@@ -0,0 +1,1381 @@
+/* revprops.c --- everything needed to handle revprops in FSFS
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_pools.h"
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+
+#include "fs_fs.h"
+#include "revprops.h"
+#include "util.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Give writing processes 10 seconds to replace an existing revprop
+ file with a new one. After that time, we assume that the writing
+ process got aborted and that we have re-read revprops. */
+#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
+
+svn_error_t *
+svn_fs_fs__upgrade_pack_revprops(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *revprops_shard_path;
+ const char *revprops_pack_file_dir;
+ apr_int64_t shard;
+ apr_int64_t first_unpacked_shard
+ = ffd->min_unpacked_rev / ffd->max_files_per_dir;
+
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
+ scratch_pool);
+ int compression_level = ffd->compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE;
+
+ /* first, pack all revprops shards to match the packed revision shards */
+ for (shard = 0; shard < first_unpacked_shard; ++shard)
+ {
+ svn_pool_clear(iterpool);
+
+ revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ shard),
+ iterpool);
+ revprops_shard_path = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
+ iterpool);
+
+ SVN_ERR(svn_fs_fs__pack_revprops_shard(revprops_pack_file_dir,
+ revprops_shard_path,
+ shard, ffd->max_files_per_dir,
+ (int)(0.9 * ffd->revprop_pack_size),
+ compression_level,
+ cancel_func, cancel_baton,
+ iterpool));
+ if (notify_func)
+ SVN_ERR(notify_func(notify_baton, shard,
+ svn_fs_upgrade_pack_revprops, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__upgrade_cleanup_pack_revprops(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *revprops_shard_path;
+ apr_int64_t shard;
+ apr_int64_t first_unpacked_shard
+ = ffd->min_unpacked_rev / ffd->max_files_per_dir;
+
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
+ scratch_pool);
+
+ /* delete the non-packed revprops shards afterwards */
+ for (shard = 0; shard < first_unpacked_shard; ++shard)
+ {
+ svn_pool_clear(iterpool);
+
+ revprops_shard_path = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
+ iterpool);
+ SVN_ERR(svn_fs_fs__delete_revprops_shard(revprops_shard_path,
+ shard,
+ ffd->max_files_per_dir,
+ cancel_func, cancel_baton,
+ iterpool));
+ if (notify_func)
+ SVN_ERR(notify_func(notify_baton, shard,
+ svn_fs_upgrade_cleanup_revprops, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Container for all data required to access the packed revprop file
+ * for a given REVISION. This structure will be filled incrementally
+ * by read_pack_revprops() its sub-routines.
+ */
+typedef struct packed_revprops_t
+{
+ /* revision number to read (not necessarily the first in the pack) */
+ svn_revnum_t revision;
+
+ /* current revprop generation. Used when populating the revprop cache */
+ apr_int64_t generation;
+
+ /* the actual revision properties */
+ apr_hash_t *properties;
+
+ /* their size when serialized to a single string
+ * (as found in PACKED_REVPROPS) */
+ apr_size_t serialized_size;
+
+
+ /* name of the pack file (without folder path) */
+ const char *filename;
+
+ /* packed shard folder path */
+ const char *folder;
+
+ /* sum of values in SIZES */
+ apr_size_t total_size;
+
+ /* first revision in the pack (>= MANIFEST_START) */
+ svn_revnum_t start_revision;
+
+ /* size of the revprops in PACKED_REVPROPS */
+ apr_array_header_t *sizes;
+
+ /* offset of the revprops in PACKED_REVPROPS */
+ apr_array_header_t *offsets;
+
+
+ /* concatenation of the serialized representation of all revprops
+ * in the pack, i.e. the pack content without header and compression */
+ svn_stringbuf_t *packed_revprops;
+
+ /* First revision covered by MANIFEST.
+ * Will equal the shard start revision or 1, for the 1st shard. */
+ svn_revnum_t manifest_start;
+
+ /* content of the manifest.
+ * Maps long(rev - MANIFEST_START) to const char* pack file name */
+ apr_array_header_t *manifest;
+} packed_revprops_t;
+
+/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
+ * Also, put them into the revprop cache, if activated, for future use.
+ * Three more parameters are being used to update the revprop cache: FS is
+ * our file system, the revprops belong to REVISION and the global revprop
+ * GENERATION is used as well.
+ *
+ * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
+ * for temporary allocations.
+ */
+static svn_error_t *
+parse_revprop(apr_hash_t **properties,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_int64_t generation,
+ svn_string_t *content,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
+ *properties = apr_hash_make(pool);
+
+ SVN_ERR_W(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool),
+ apr_psprintf(scratch_pool, "Failed to parse revprops for r%ld.",
+ revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the non-packed revprops for revision REV in FS, put them into the
+ * revprop cache if activated and return them in *PROPERTIES. GENERATION
+ * is the current revprop generation.
+ *
+ * If the data could not be read due to an otherwise recoverable error,
+ * leave *PROPERTIES unchanged. No error will be returned in that case.
+ *
+ * Allocations will be done in POOL.
+ */
+static svn_error_t *
+read_non_packed_revprop(apr_hash_t **properties,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_int64_t generation,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *content = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_boolean_t missing = FALSE;
+ int i;
+
+ for (i = 0;
+ i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !missing && !content;
+ ++i)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&content,
+ &missing,
+ svn_fs_fs__path_revprops(fs, rev, iterpool),
+ i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT ,
+ iterpool));
+ }
+
+ if (content)
+ SVN_ERR(parse_revprop(properties, fs, rev, generation,
+ svn_stringbuf__morph_into_string(content),
+ pool, iterpool));
+
+ svn_pool_clear(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the minimum length of any packed revprop file name in REVPROPS. */
+static apr_size_t
+get_min_filename_len(packed_revprops_t *revprops)
+{
+ char number_buffer[SVN_INT64_BUFFER_SIZE];
+
+ /* The revprop filenames have the format <REV>.<COUNT> - with <REV> being
+ * at least the first rev in the shard and <COUNT> having at least one
+ * digit. Thus, the minimum is 2 + #decimal places in the start rev.
+ */
+ return svn__i64toa(number_buffer, revprops->manifest_start) + 2;
+}
+
+/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
+ * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
+ */
+static svn_error_t *
+get_revprop_packname(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stringbuf_t *content = NULL;
+ const char *manifest_file_path;
+ int idx, rev_count;
+ char *buffer, *buffer_end;
+ const char **filenames, **filenames_end;
+ apr_size_t min_filename_len;
+
+ /* Determine the dimensions. Rev 0 is excluded from the first shard. */
+ rev_count = ffd->max_files_per_dir;
+ revprops->manifest_start
+ = revprops->revision - (revprops->revision % rev_count);
+ if (revprops->manifest_start == 0)
+ {
+ ++revprops->manifest_start;
+ --rev_count;
+ }
+
+ revprops->manifest = apr_array_make(pool, rev_count, sizeof(const char*));
+
+ /* No line in the file can be less than this number of chars long. */
+ min_filename_len = get_min_filename_len(revprops);
+
+ /* Read the content of the manifest file */
+ revprops->folder
+ = svn_fs_fs__path_revprops_pack_shard(fs, revprops->revision, pool);
+ manifest_file_path
+ = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
+
+ SVN_ERR(svn_fs_fs__read_content(&content, manifest_file_path, pool));
+
+ /* There CONTENT must have a certain minimal size and there no
+ * unterminated lines at the end of the file. Both guarantees also
+ * simplify the parser loop below.
+ */
+ if ( content->len < rev_count * (min_filename_len + 1)
+ || content->data[content->len - 1] != '\n')
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop manifest for r%ld not "
+ "properly terminated"), revprops->revision);
+
+ /* Chop (parse) the manifest CONTENT into filenames, one per line.
+ * We only have to replace all newlines with NUL and add all line
+ * starts to REVPROPS->MANIFEST.
+ *
+ * There must be exactly REV_COUNT lines and that is the number of
+ * lines we parse from BUFFER to FILENAMES. Set the end pointer for
+ * the source BUFFER such that BUFFER+MIN_FILENAME_LEN is still valid
+ * BUFFER_END is always valid due to CONTENT->LEN > MIN_FILENAME_LEN.
+ *
+ * Please note that this loop is performance critical for e.g. 'svn log'.
+ * It is run 1000x per revprop access, i.e. per revision and about
+ * 50 million times per sec (and CPU core).
+ */
+ for (filenames = (const char **)revprops->manifest->elts,
+ filenames_end = filenames + rev_count,
+ buffer = content->data,
+ buffer_end = buffer + content->len - min_filename_len;
+ (filenames < filenames_end) && (buffer < buffer_end);
+ ++filenames)
+ {
+ /* BUFFER always points to the start of the next line / filename. */
+ *filenames = buffer;
+
+ /* Find the next EOL. This is guaranteed to stay within the CONTENT
+ * buffer because we left enough room after BUFFER_END and we know
+ * we will always see a newline as the last non-NUL char. */
+ buffer += min_filename_len;
+ while (*buffer != '\n')
+ ++buffer;
+
+ /* Found EOL. Turn it into the filename terminator and move BUFFER
+ * to the start of the next line or CONTENT buffer end. */
+ *buffer = '\0';
+ ++buffer;
+ }
+
+ /* We must have reached the end of both buffers. */
+ if (buffer < content->data + content->len)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop manifest for r%ld "
+ "has too many entries"), revprops->revision);
+
+ if (filenames < filenames_end)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop manifest for r%ld "
+ "has too few entries"), revprops->revision);
+
+ /* The target array has now exactly one entry per revision. */
+ revprops->manifest->nelts = rev_count;
+
+ /* Now get the file name */
+ idx = (int)(revprops->revision - revprops->manifest_start);
+ revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
+ */
+static svn_boolean_t
+same_shard(svn_fs_t *fs,
+ svn_revnum_t r1,
+ svn_revnum_t r2)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
+}
+
+/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
+ * fill the START_REVISION member, and make PACKED_REVPROPS point to the
+ * first serialized revprop. If READ_ALL is set, initialize the SIZES
+ * and OFFSETS members as well.
+ *
+ * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
+ * well as the SERIALIZED_SIZE member. If revprop caching has been
+ * enabled, parse all revprops in the pack and cache them.
+ */
+static svn_error_t *
+parse_packed_revprops(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ svn_boolean_t read_all,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ apr_int64_t first_rev, count, i;
+ apr_off_t offset;
+ const char *header_end;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* decompress (even if the data is only "stored", there is still a
+ * length header to remove) */
+ svn_stringbuf_t *compressed = revprops->packed_revprops;
+ svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
+
+ /* read first revision number and number of revisions in the pack */
+ stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
+ SVN_ERR(svn_fs_fs__read_number_from_stream(&first_rev, NULL, stream,
+ iterpool));
+ SVN_ERR(svn_fs_fs__read_number_from_stream(&count, NULL, stream,
+ iterpool));
+
+ /* Check revision range for validity. */
+ if ( !same_shard(fs, revprops->revision, first_rev)
+ || !same_shard(fs, revprops->revision, first_rev + count - 1)
+ || count < 1)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revprop pack for revision r%ld"
+ " contains revprops for r%ld .. r%ld"),
+ revprops->revision,
+ (svn_revnum_t)first_rev,
+ (svn_revnum_t)(first_rev + count -1));
+
+ /* Since start & end are in the same shard, it is enough to just test
+ * the FIRST_REV for being actually packed. That will also cover the
+ * special case of rev 0 never being packed. */
+ if (!svn_fs_fs__is_packed_revprop(fs, first_rev))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revprop pack for revision r%ld"
+ " starts at non-packed revisions r%ld"),
+ revprops->revision, (svn_revnum_t)first_rev);
+
+ /* make PACKED_REVPROPS point to the first char after the header.
+ * This is where the serialized revprops are. */
+ header_end = strstr(uncompressed->data, "\n\n");
+ if (header_end == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Header end not found"));
+
+ offset = header_end - uncompressed->data + 2;
+
+ revprops->packed_revprops = svn_stringbuf_create_empty(pool);
+ revprops->packed_revprops->data = uncompressed->data + offset;
+ revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
+ revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
+
+ /* STREAM still points to the first entry in the sizes list. */
+ revprops->start_revision = (svn_revnum_t)first_rev;
+ if (read_all)
+ {
+ /* Init / construct REVPROPS members. */
+ revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
+ revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
+ }
+
+ /* Now parse, revision by revision, the size and content of each
+ * revisions' revprops. */
+ for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
+ {
+ apr_int64_t size;
+ svn_string_t serialized;
+ svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
+ svn_pool_clear(iterpool);
+
+ /* read & check the serialized size */
+ SVN_ERR(svn_fs_fs__read_number_from_stream(&size, NULL, stream,
+ iterpool));
+ if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop size exceeds pack file size"));
+
+ /* Parse this revprops list, if necessary */
+ serialized.data = revprops->packed_revprops->data + offset;
+ serialized.len = (apr_size_t)size;
+
+ if (revision == revprops->revision)
+ {
+ SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
+ revprops->generation, &serialized,
+ pool, iterpool));
+ revprops->serialized_size = serialized.len;
+
+ /* If we only wanted the revprops for REVISION then we are done. */
+ if (!read_all)
+ break;
+ }
+
+ if (read_all)
+ {
+ /* fill REVPROPS data structures */
+ APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
+ APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
+ }
+ revprops->total_size += serialized.len;
+
+ offset += serialized.len;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* In filesystem FS, read the packed revprops for revision REV into
+ * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled.
+ * If you want to modify revprop contents / update REVPROPS, READ_ALL
+ * must be set. Otherwise, only the properties of REV are being provided.
+ * Allocate data in POOL.
+ */
+static svn_error_t *
+read_pack_revprop(packed_revprops_t **revprops,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_int64_t generation,
+ svn_boolean_t read_all,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_boolean_t missing = FALSE;
+ svn_error_t *err;
+ packed_revprops_t *result;
+ int i;
+
+ /* someone insisted that REV is packed. Double-check if necessary */
+ if (!svn_fs_fs__is_packed_revprop(fs, rev))
+ SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, iterpool));
+
+ if (!svn_fs_fs__is_packed_revprop(fs, rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such packed revision %ld"), rev);
+
+ /* initialize the result data structure */
+ result = apr_pcalloc(pool, sizeof(*result));
+ result->revision = rev;
+ result->generation = generation;
+
+ /* try to read the packed revprops. This may require retries if we have
+ * concurrent writers. */
+ for (i = 0;
+ i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !result->packed_revprops;
+ ++i)
+ {
+ const char *file_path;
+ svn_pool_clear(iterpool);
+
+ /* there might have been concurrent writes.
+ * Re-read the manifest and the pack file.
+ */
+ SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
+ file_path = svn_dirent_join(result->folder,
+ result->filename,
+ iterpool);
+ SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&result->packed_revprops,
+ &missing,
+ file_path,
+ i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT,
+ pool));
+ }
+
+ /* the file content should be available now */
+ if (!result->packed_revprops)
+ return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
+ _("Failed to read revprop pack file for r%ld"), rev);
+
+ /* parse it. RESULT will be complete afterwards. */
+ err = parse_packed_revprops(fs, result, read_all, pool, iterpool);
+ svn_pool_destroy(iterpool);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ _("Revprop pack file for r%ld is corrupt"), rev);
+
+ *revprops = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
+ *
+ * Allocations will be done in POOL.
+ */
+svn_error_t *
+svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_int64_t generation = 0;
+
+ /* not found, yet */
+ *proplist_p = NULL;
+
+ /* should they be available at all? */
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ /* if REV had not been packed when we began, try reading it from the
+ * non-packed shard. If that fails, we will fall through to packed
+ * shard reads. */
+ if (!svn_fs_fs__is_packed_revprop(fs, rev))
+ {
+ svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
+ generation, pool);
+ if (err)
+ {
+ if (!APR_STATUS_IS_ENOENT(err->apr_err)
+ || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
+ }
+ }
+
+ /* if revprop packing is available and we have not read the revprops, yet,
+ * try reading them from a packed shard. If that fails, REV is most
+ * likely invalid (or its revprops highly contested). */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
+ {
+ packed_revprops_t *revprops;
+ SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, FALSE, pool));
+ *proplist_p = revprops->properties;
+ }
+
+ /* The revprops should have been there. Did we get them? */
+ if (!*proplist_p)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Could not read revprops for revision %ld"),
+ rev);
+
+ return SVN_NO_ERROR;
+}
+
+/* Serialize the revision property list PROPLIST of revision REV in
+ * filesystem FS to a non-packed file. Return the name of that temporary
+ * file in *TMP_PATH and the file path that it must be moved to in
+ * *FINAL_PATH.
+ *
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+write_non_packed_revprop(const char **final_path,
+ const char **tmp_path,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ svn_stream_t *stream;
+ *final_path = svn_fs_fs__path_revprops(fs, rev, pool);
+
+ /* ### do we have a directory sitting around already? we really shouldn't
+ ### have to get the dirname here. */
+ SVN_ERR(svn_io_open_unique_file3(&file, tmp_path,
+ svn_dirent_dirname(*final_path, pool),
+ svn_io_file_del_none, pool, pool));
+ stream = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Flush temporary file to disk and close it. */
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* After writing the new revprop file(s), call this function to move the
+ * file at TMP_PATH to FINAL_PATH and give it the permissions from
+ * PERMS_REFERENCE.
+ *
+ * Finally, delete all the temporary files given in FILES_TO_DELETE.
+ * The latter may be NULL.
+ *
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+switch_to_new_revprop(svn_fs_t *fs,
+ const char *final_path,
+ const char *tmp_path,
+ const char *perms_reference,
+ apr_array_header_t *files_to_delete,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path, perms_reference,
+ pool));
+
+ /* Clean up temporary files, if necessary. */
+ if (files_to_delete)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < files_to_delete->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Write a pack file header to STREAM that starts at revision START_REVISION
+ * and contains the indexes [START,END) of SIZES.
+ */
+static svn_error_t *
+serialize_revprops_header(svn_stream_t *stream,
+ svn_revnum_t start_revision,
+ apr_array_header_t *sizes,
+ int start,
+ int end,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ SVN_ERR_ASSERT(start < end);
+
+ /* start revision and entry count */
+ SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
+ SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
+
+ /* the sizes array */
+ for (i = start; i < end; ++i)
+ {
+ /* Non-standard pool usage.
+ *
+ * We only allocate a few bytes each iteration -- even with a
+ * million iterations we would still be in good shape memory-wise.
+ */
+ apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
+ SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
+ size));
+ }
+
+ /* the double newline char indicates the end of the header */
+ SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Writes the a pack file to FILE. It copies the serialized data
+ * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
+ *
+ * The data for the latter is taken from NEW_SERIALIZED. Note, that
+ * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
+ * taken in that case but only a subset of the old data will be copied.
+ *
+ * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
+ * POOL is used for temporary allocations.
+ */
+static svn_error_t *
+repack_revprops(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ int start,
+ int end,
+ int changed_index,
+ svn_stringbuf_t *new_serialized,
+ apr_off_t new_total_size,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *stream;
+ int i;
+
+ /* create data empty buffers and the stream object */
+ svn_stringbuf_t *uncompressed
+ = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
+ svn_stringbuf_t *compressed
+ = svn_stringbuf_create_empty(pool);
+ stream = svn_stream_from_stringbuf(uncompressed, pool);
+
+ /* write the header*/
+ SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
+ revprops->sizes, start, end, pool));
+
+ /* append the serialized revprops */
+ for (i = start; i < end; ++i)
+ if (i == changed_index)
+ {
+ SVN_ERR(svn_stream_write(stream,
+ new_serialized->data,
+ &new_serialized->len));
+ }
+ else
+ {
+ apr_size_t size
+ = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
+ apr_size_t offset
+ = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
+
+ SVN_ERR(svn_stream_write(stream,
+ revprops->packed_revprops->data + offset,
+ &size));
+ }
+
+ /* flush the stream buffer (if any) to our underlying data buffer */
+ SVN_ERR(svn_stream_close(stream));
+
+ /* compress / store the data */
+ SVN_ERR(svn__compress(uncompressed,
+ compressed,
+ ffd->compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE));
+
+ /* finally, write the content to the target file, flush and close it */
+ SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len,
+ NULL, pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate a new pack file name for revisions
+ * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
+ * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE,
+ * auto-create that array if necessary. Return an open file *FILE that is
+ * allocated in POOL.
+ */
+static svn_error_t *
+repack_file_open(apr_file_t **file,
+ svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ int start,
+ int end,
+ apr_array_header_t **files_to_delete,
+ apr_pool_t *pool)
+{
+ apr_int64_t tag;
+ const char *tag_string;
+ svn_string_t *new_filename;
+ int i;
+ int manifest_offset
+ = (int)(revprops->start_revision - revprops->manifest_start);
+
+ /* get the old (= current) file name and enlist it for later deletion */
+ const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
+ start + manifest_offset,
+ const char*);
+
+ if (*files_to_delete == NULL)
+ *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
+
+ APR_ARRAY_PUSH(*files_to_delete, const char*)
+ = svn_dirent_join(revprops->folder, old_filename, pool);
+
+ /* increase the tag part, i.e. the counter after the dot */
+ tag_string = strchr(old_filename, '.');
+ if (tag_string == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed file '%s' misses a tag"),
+ old_filename);
+
+ SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
+ new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
+ revprops->start_revision + start,
+ ++tag);
+
+ /* update the manifest to point to the new file */
+ for (i = start; i < end; ++i)
+ APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
+ = new_filename->data;
+
+ /* open the file */
+ SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder,
+ new_filename->data,
+ pool),
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* For revision REV in filesystem FS, set the revision properties to
+ * PROPLIST. Return a new file in *TMP_PATH that the caller shall move
+ * to *FINAL_PATH to make the change visible. Files to be deleted will
+ * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+write_packed_revprop(const char **final_path,
+ const char **tmp_path,
+ apr_array_header_t **files_to_delete,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ packed_revprops_t *revprops;
+ apr_int64_t generation = 0;
+ svn_stream_t *stream;
+ apr_file_t *file;
+ svn_stringbuf_t *serialized;
+ apr_off_t new_total_size;
+ int changed_index;
+
+ /* read contents of the current pack file */
+ SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, TRUE, pool));
+
+ /* serialize the new revprops */
+ serialized = svn_stringbuf_create_empty(pool);
+ stream = svn_stream_from_stringbuf(serialized, pool);
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* calculate the size of the new data */
+ changed_index = (int)(rev - revprops->start_revision);
+ new_total_size = revprops->total_size - revprops->serialized_size
+ + serialized->len
+ + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
+
+ APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
+
+ /* can we put the new data into the same pack as the before? */
+ if ( new_total_size < ffd->revprop_pack_size
+ || revprops->sizes->nelts == 1)
+ {
+ /* simply replace the old pack file with new content as we do it
+ * in the non-packed case */
+
+ *final_path = svn_dirent_join(revprops->folder, revprops->filename,
+ pool);
+ SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
+ changed_index, serialized, new_total_size,
+ file, pool));
+ }
+ else
+ {
+ /* split the pack file into two of roughly equal size */
+ int right_count, left_count, i;
+
+ int left = 0;
+ int right = revprops->sizes->nelts - 1;
+ apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
+ apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
+
+ /* let left and right side grow such that their size difference
+ * is minimal after each step. */
+ while (left <= right)
+ if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
+ < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
+ {
+ left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
+ + SVN_INT64_BUFFER_SIZE;
+ ++left;
+ }
+ else
+ {
+ right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
+ + SVN_INT64_BUFFER_SIZE;
+ --right;
+ }
+
+ /* since the items need much less than SVN_INT64_BUFFER_SIZE
+ * bytes to represent their length, the split may not be optimal */
+ left_count = left;
+ right_count = revprops->sizes->nelts - left;
+
+ /* if new_size is large, one side may exceed the pack size limit.
+ * In that case, split before and after the modified revprop.*/
+ if ( left_size > ffd->revprop_pack_size
+ || right_size > ffd->revprop_pack_size)
+ {
+ left_count = changed_index;
+ right_count = revprops->sizes->nelts - left_count - 1;
+ }
+
+ /* write the new, split files */
+ if (left_count)
+ {
+ SVN_ERR(repack_file_open(&file, fs, revprops, 0,
+ left_count, files_to_delete, pool));
+ SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
+ changed_index, serialized, new_total_size,
+ file, pool));
+ }
+
+ if (left_count + right_count < revprops->sizes->nelts)
+ {
+ SVN_ERR(repack_file_open(&file, fs, revprops, changed_index,
+ changed_index + 1, files_to_delete,
+ pool));
+ SVN_ERR(repack_revprops(fs, revprops, changed_index,
+ changed_index + 1,
+ changed_index, serialized, new_total_size,
+ file, pool));
+ }
+
+ if (right_count)
+ {
+ SVN_ERR(repack_file_open(&file, fs, revprops,
+ revprops->sizes->nelts - right_count,
+ revprops->sizes->nelts,
+ files_to_delete, pool));
+ SVN_ERR(repack_revprops(fs, revprops,
+ revprops->sizes->nelts - right_count,
+ revprops->sizes->nelts, changed_index,
+ serialized, new_total_size, file,
+ pool));
+ }
+
+ /* write the new manifest */
+ *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
+ SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
+ svn_io_file_del_none, pool, pool));
+
+ for (i = 0; i < revprops->manifest->nelts; ++i)
+ {
+ const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
+ const char*);
+ SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename),
+ NULL, pool));
+ SVN_ERR(svn_io_file_putc('\n', file, pool));
+ }
+
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set the revision property list of revision REV in filesystem FS to
+ PROPLIST. Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_packed;
+ const char *final_path;
+ const char *tmp_path;
+ const char *perms_reference;
+ apr_array_header_t *files_to_delete = NULL;
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ /* this info will not change while we hold the global FS write lock */
+ is_packed = svn_fs_fs__is_packed_revprop(fs, rev);
+
+ /* Serialize the new revprop data */
+ if (is_packed)
+ SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
+ fs, rev, proplist, pool));
+ else
+ SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
+ fs, rev, proplist, pool));
+
+ /* We use the rev file of this revision as the perms reference,
+ * because when setting revprops for the first time, the revprop
+ * file won't exist and therefore can't serve as its own reference.
+ * (Whereas the rev file should already exist at this point.)
+ */
+ perms_reference = svn_fs_fs__path_rev_absolute(fs, rev, pool);
+
+ /* Now, switch to the new revprop data. */
+ SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
+ files_to_delete, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
+ * Use POOL for temporary allocations.
+ * Set *MISSING, if the reason is a missing manifest or pack file.
+ */
+svn_boolean_t
+svn_fs_fs__packed_revprop_available(svn_boolean_t *missing,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stringbuf_t *content = NULL;
+
+ /* try to read the manifest file */
+ const char *folder
+ = svn_fs_fs__path_revprops_pack_shard(fs, revision, pool);
+ const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
+
+ svn_error_t *err = svn_fs_fs__try_stringbuf_from_file(&content,
+ missing,
+ manifest_path,
+ FALSE,
+ pool);
+
+ /* if the manifest cannot be read, consider the pack files inaccessible
+ * even if the file itself exists. */
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ if (*missing)
+ return FALSE;
+
+ /* parse manifest content until we find the entry for REVISION.
+ * Revision 0 is never packed. */
+ revision = revision < ffd->max_files_per_dir
+ ? revision - 1
+ : revision % ffd->max_files_per_dir;
+ while (content->data)
+ {
+ char *next = strchr(content->data, '\n');
+ if (next)
+ {
+ *next = 0;
+ ++next;
+ }
+
+ if (revision-- == 0)
+ {
+ /* the respective pack file must exist (and be a file) */
+ svn_node_kind_t kind;
+ err = svn_io_check_path(svn_dirent_join(folder, content->data,
+ pool),
+ &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ *missing = kind == svn_node_none;
+ return kind == svn_node_file;
+ }
+
+ content->data = next;
+ }
+
+ return FALSE;
+}
+
+
+/****** Packing FSFS shards *********/
+
+svn_error_t *
+svn_fs_fs__copy_revprops(const char *pack_file_dir,
+ const char *pack_filename,
+ const char *shard_path,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ apr_array_header_t *sizes,
+ apr_size_t total_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *pack_stream;
+ apr_file_t *pack_file;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* create empty data buffer and a write stream on top of it */
+ svn_stringbuf_t *uncompressed
+ = svn_stringbuf_create_ensure(total_size, scratch_pool);
+ svn_stringbuf_t *compressed
+ = svn_stringbuf_create_empty(scratch_pool);
+ pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
+
+ /* write the pack file header */
+ SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
+ sizes->nelts, iterpool));
+
+ /* Some useful paths. */
+ SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
+ pack_filename,
+ scratch_pool),
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
+ scratch_pool));
+
+ /* Iterate over the revisions in this shard, squashing them together. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ const char *path;
+ svn_stream_t *stream;
+
+ svn_pool_clear(iterpool);
+
+ /* Construct the file name. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+
+ /* Copy all the bits from the non-packed revprop file to the end of
+ * the pack file. */
+ SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
+ SVN_ERR(svn_stream_copy3(stream, pack_stream,
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ /* flush stream buffers to content buffer */
+ SVN_ERR(svn_stream_close(pack_stream));
+
+ /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
+ SVN_ERR(svn__compress(uncompressed, compressed, compression_level));
+
+ /* write the pack file content to disk */
+ SVN_ERR(svn_io_file_write_full(pack_file, compressed->data, compressed->len,
+ NULL, scratch_pool));
+ SVN_ERR(svn_io_file_flush_to_disk(pack_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(pack_file, scratch_pool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_off_t max_pack_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *manifest_file_path, *pack_filename = NULL;
+ apr_file_t *manifest_file;
+ svn_stream_t *manifest_stream;
+ svn_revnum_t start_rev, end_rev, rev;
+ apr_off_t total_size;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *sizes;
+
+ /* Some useful paths. */
+ manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
+ scratch_pool);
+
+ /* Remove any existing pack file for this shard, since it is incomplete. */
+ SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Create the new directory and manifest file stream. */
+ SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
+
+ SVN_ERR(svn_io_file_open(&manifest_file, manifest_file_path,
+ APR_WRITE | APR_BUFFERED | APR_CREATE | APR_EXCL,
+ APR_OS_DEFAULT, scratch_pool));
+ manifest_stream = svn_stream_from_aprfile2(manifest_file, TRUE,
+ scratch_pool);
+
+ /* revisions to handle. Special case: revision 0 */
+ start_rev = (svn_revnum_t) (shard * max_files_per_dir);
+ end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
+ if (start_rev == 0)
+ ++start_rev;
+ /* Special special case: if max_files_per_dir is 1, then at this point
+ start_rev == 1 and end_rev == 0 (!). Fortunately, everything just
+ works. */
+
+ /* initialize the revprop size info */
+ sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
+ total_size = 2 * SVN_INT64_BUFFER_SIZE;
+
+ /* Iterate over the revisions in this shard, determine their size and
+ * squashing them together into pack files. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ apr_finfo_t finfo;
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the size of the file. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
+
+ /* if we already have started a pack file and this revprop cannot be
+ * appended to it, write the previous pack file. */
+ if (sizes->nelts != 0 &&
+ total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
+ {
+ SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
+ shard_path, start_rev, rev-1,
+ sizes, (apr_size_t)total_size,
+ compression_level, cancel_func,
+ cancel_baton, iterpool));
+
+ /* next pack file starts empty again */
+ apr_array_clear(sizes);
+ total_size = 2 * SVN_INT64_BUFFER_SIZE;
+ start_rev = rev;
+ }
+
+ /* Update the manifest. Allocate a file name for the current pack
+ * file if it is a new one */
+ if (sizes->nelts == 0)
+ pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
+
+ SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
+ pack_filename));
+
+ /* add to list of files to put into the current pack file */
+ APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
+ total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
+ }
+
+ /* write the last pack file */
+ if (sizes->nelts != 0)
+ SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
+ shard_path, start_rev, rev-1,
+ sizes, (apr_size_t)total_size,
+ compression_level, cancel_func,
+ cancel_baton, iterpool));
+
+ /* flush the manifest file to disk and update permissions */
+ SVN_ERR(svn_stream_close(manifest_stream));
+ SVN_ERR(svn_io_file_flush_to_disk(manifest_file, iterpool));
+ SVN_ERR(svn_io_file_close(manifest_file, iterpool));
+ SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__delete_revprops_shard(const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (shard == 0)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* delete all files except the one for revision 0 */
+ for (i = 1; i < max_files_per_dir; ++i)
+ {
+ const char *path;
+ svn_pool_clear(iterpool);
+
+ path = svn_dirent_join(shard_path,
+ apr_psprintf(iterpool, "%d", i),
+ iterpool);
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ else
+ SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_fs_fs/revprops.h b/subversion/libsvn_fs_fs/revprops.h
new file mode 100644
index 0000000..66c137c
--- /dev/null
+++ b/subversion/libsvn_fs_fs/revprops.h
@@ -0,0 +1,159 @@
+/* revprops.h --- everything needed to handle revprops in FSFS
+ *
+ * ====================================================================
+ * 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"
+
+/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
+ *
+ * NOTE: Keep the old non-packed shards around until after the format bump.
+ * Otherwise, re-running upgrade will drop the packed revprop shard but
+ * have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after
+ * the bump.
+ *
+ * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
+ * used in the usual way. Temporary allocations are done in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_fs_fs__upgrade_pack_revprops(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* In the filesystem FS, remove all non-packed revprop shards up to
+ * min_unpacked_rev. Temporary allocations are done in SCRATCH_POOL.
+ *
+ * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
+ * used in the usual way. Cancellation is supported in the sense that we
+ * will cleanly abort the operation. However, there will be remnant shards
+ * that must be removed manually.
+ *
+ * See upgrade_pack_revprops for more info.
+ */
+svn_error_t *
+svn_fs_fs__upgrade_cleanup_pack_revprops(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
+ *
+ * Allocations will be done in POOL.
+ */
+svn_error_t *
+svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set the revision property list of revision REV in filesystem FS to
+ PROPLIST. Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool);
+
+
+/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
+ * Use POOL for temporary allocations.
+ * Set *MISSING, if the reason is a missing manifest or pack file.
+ */
+svn_boolean_t
+svn_fs_fs__packed_revprop_available(svn_boolean_t *missing,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+
+/****** Packing FSFS shards *********/
+
+/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
+ * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
+ *
+ * The file sizes have already been determined and written to SIZES.
+ * Please note that this function will be executed while the filesystem
+ * has been locked and that revprops files will therefore not be modified
+ * while the pack is in progress.
+ *
+ * COMPRESSION_LEVEL defines how well the resulting pack file shall be
+ * compressed or whether is shall be compressed at all. TOTAL_SIZE is
+ * a hint on which initial buffer size we should use to hold the pack file
+ * content.
+ *
+ * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
+ * are done in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_fs_fs__copy_revprops(const char *pack_file_dir,
+ const char *pack_filename,
+ const char *shard_path,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ apr_array_header_t *sizes,
+ apr_size_t total_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
+ *
+ * NOTE: Keep the old non-packed shards around until after the format bump.
+ * Otherwise, re-running upgrade will drop the packed revprop shard but
+ * have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after
+ * the bump.
+ *
+ * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
+ * used in the usual way. Temporary allocations are done in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_off_t max_pack_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* In the filesystem FS, remove all non-packed revprop shards up to
+ * min_unpacked_rev. Temporary allocations are done in SCRATCH_POOL.
+ *
+ * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
+ * used in the usual way. Cancellation is supported in the sense that we
+ * will cleanly abort the operation. However, there will be remnant shards
+ * that must be removed manually.
+ *
+ * See upgrade_pack_revprops for more info.
+ */
+svn_error_t *
+svn_fs_fs__delete_revprops_shard(const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
diff --git a/subversion/libsvn_fs_fs/stats.c b/subversion/libsvn_fs_fs/stats.c
new file mode 100644
index 0000000..97a2ed7
--- /dev/null
+++ b/subversion/libsvn_fs_fs/stats.c
@@ -0,0 +1,1255 @@
+/* stats.c -- implements the svn_fs_fs__get_stats private API.
+ *
+ * ====================================================================
+ * 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_dirent_uri.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+
+#include "private/svn_cache.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_fs_fs_private.h"
+
+#include "index.h"
+#include "pack.h"
+#include "rev_file.h"
+#include "util.h"
+#include "fs_fs.h"
+#include "cached_data.h"
+#include "low_level.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* We group representations into 2x2 different kinds plus one default:
+ * [dir / file] x [text / prop]. The assignment is done by the first node
+ * that references the respective representation.
+ */
+typedef enum rep_kind_t
+{
+ /* The representation is not used _directly_, i.e. not referenced by any
+ * noderev. However, some other representation may use it as delta base.
+ * Null value. Should not occur in real-word repositories. */
+ unused_rep,
+
+ /* a properties on directory rep */
+ dir_property_rep,
+
+ /* a properties on file rep */
+ file_property_rep,
+
+ /* a directory rep */
+ dir_rep,
+
+ /* a file rep */
+ file_rep
+} rep_kind_t;
+
+/* A representation fragment.
+ */
+typedef struct rep_stats_t
+{
+ /* absolute offset in the file */
+ apr_off_t offset;
+
+ /* item length in bytes */
+ apr_uint64_t size;
+
+ /* item length after de-deltification */
+ apr_uint64_t expanded_size;
+
+ /* revision that contains this representation
+ * (may be referenced by other revisions, though) */
+ svn_revnum_t revision;
+
+ /* number of nodes that reference this representation */
+ apr_uint32_t ref_count;
+
+ /* length of the PLAIN / DELTA line in the source file in bytes */
+ apr_uint16_t header_size;
+
+ /* classification of the representation. values of rep_kind_t */
+ char kind;
+
+} rep_stats_t;
+
+/* Represents a single revision.
+ * There will be only one instance per revision. */
+typedef struct revision_info_t
+{
+ /* number of this revision */
+ svn_revnum_t revision;
+
+ /* pack file offset (manifest value), 0 for non-packed files */
+ apr_off_t offset;
+
+ /* length of the changes list on bytes */
+ apr_uint64_t changes_len;
+
+ /* offset of the changes list relative to OFFSET */
+ apr_uint64_t change_count;
+
+ /* first offset behind the revision data in the pack file (file length
+ * for non-packed revs) */
+ apr_off_t end;
+
+ /* number of directory noderevs in this revision */
+ apr_uint64_t dir_noderev_count;
+
+ /* number of file noderevs in this revision */
+ apr_uint64_t file_noderev_count;
+
+ /* total size of directory noderevs (i.e. the structs - not the rep) */
+ apr_uint64_t dir_noderev_size;
+
+ /* total size of file noderevs (i.e. the structs - not the rep) */
+ apr_uint64_t file_noderev_size;
+
+ /* all rep_stats_t of this revision (in no particular order),
+ * i.e. those that point back to this struct */
+ apr_array_header_t *representations;
+
+ /* Temporary rev / pack file access object, used in phys. addressing
+ * mode only. NULL when done reading this revision. */
+ svn_fs_fs__revision_file_t *rev_file;
+} revision_info_t;
+
+/* Root data structure containing all information about a given repository.
+ * We use it as a wrapper around svn_fs_t and pass it around where we would
+ * otherwise just use a svn_fs_t.
+ */
+typedef struct query_t
+{
+ /* FS API object*/
+ svn_fs_t *fs;
+
+ /* The HEAD revision. */
+ svn_revnum_t head;
+
+ /* Number of revs per shard; 0 for non-sharded repos. */
+ int shard_size;
+
+ /* First non-packed revision. */
+ svn_revnum_t min_unpacked_rev;
+
+ /* all revisions */
+ apr_array_header_t *revisions;
+
+ /* empty representation.
+ * Used as a dummy base for DELTA reps without base. */
+ rep_stats_t *null_base;
+
+ /* collected statistics */
+ svn_fs_fs__stats_t *stats;
+
+ /* Progress notification callback to call after each shard. May be NULL. */
+ svn_fs_progress_notify_func_t progress_func;
+
+ /* Baton for PROGRESS_FUNC. */
+ void *progress_baton;
+
+ /* Cancellation support callback to call once in a while. May be NULL. */
+ svn_cancel_func_t cancel_func;
+
+ /* Baton for CANCEL_FUNC. */
+ void *cancel_baton;
+} query_t;
+
+/* Return the length of REV_FILE in *FILE_SIZE.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_file_size(apr_off_t *file_size,
+ svn_fs_fs__revision_file_t *rev_file,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, rev_file->file,
+ scratch_pool));
+
+ *file_size = finfo.size;
+ return SVN_NO_ERROR;
+}
+
+/* Initialize the LARGEST_CHANGES member in STATS with a capacity of COUNT
+ * entries. Allocate the result in RESULT_POOL.
+ */
+static void
+initialize_largest_changes(svn_fs_fs__stats_t *stats,
+ apr_size_t count,
+ apr_pool_t *result_pool)
+{
+ apr_size_t i;
+
+ stats->largest_changes = apr_pcalloc(result_pool,
+ sizeof(*stats->largest_changes));
+ stats->largest_changes->count = count;
+ stats->largest_changes->min_size = 1;
+ stats->largest_changes->changes
+ = apr_palloc(result_pool, count * sizeof(*stats->largest_changes->changes));
+
+ /* allocate *all* entries before the path stringbufs. This increases
+ * cache locality and enhances performance significantly. */
+ for (i = 0; i < count; ++i)
+ stats->largest_changes->changes[i]
+ = apr_palloc(result_pool, sizeof(**stats->largest_changes->changes));
+
+ /* now initialize them and allocate the stringbufs */
+ for (i = 0; i < count; ++i)
+ {
+ stats->largest_changes->changes[i]->size = 0;
+ stats->largest_changes->changes[i]->revision = SVN_INVALID_REVNUM;
+ stats->largest_changes->changes[i]->path
+ = svn_stringbuf_create_ensure(1024, result_pool);
+ }
+}
+
+/* Add entry for SIZE to HISTOGRAM.
+ */
+static void
+add_to_histogram(svn_fs_fs__histogram_t *histogram,
+ apr_int64_t size)
+{
+ apr_int64_t shift = 0;
+
+ while (((apr_int64_t)(1) << shift) <= size)
+ shift++;
+
+ histogram->total.count++;
+ histogram->total.sum += size;
+ histogram->lines[(apr_size_t)shift].count++;
+ histogram->lines[(apr_size_t)shift].sum += size;
+}
+
+/* Update data aggregators in STATS with this representation of type KIND,
+ * on-disk REP_SIZE and expanded node size EXPANDED_SIZE for PATH in REVSION.
+ * PLAIN_ADDED indicates whether the node has a deltification predecessor.
+ */
+static void
+add_change(svn_fs_fs__stats_t *stats,
+ apr_uint64_t rep_size,
+ apr_uint64_t expanded_size,
+ svn_revnum_t revision,
+ const char *path,
+ rep_kind_t kind,
+ svn_boolean_t plain_added)
+{
+ /* identify largest reps */
+ if (rep_size >= stats->largest_changes->min_size)
+ {
+ apr_size_t i;
+ svn_fs_fs__largest_changes_t *largest_changes = stats->largest_changes;
+ svn_fs_fs__large_change_info_t *info
+ = largest_changes->changes[largest_changes->count - 1];
+ info->size = rep_size;
+ info->revision = revision;
+ svn_stringbuf_set(info->path, path);
+
+ /* linear insertion but not too bad since count is low and insertions
+ * near the end are more likely than close to front */
+ for (i = largest_changes->count - 1; i > 0; --i)
+ if (largest_changes->changes[i-1]->size >= rep_size)
+ break;
+ else
+ largest_changes->changes[i] = largest_changes->changes[i-1];
+
+ largest_changes->changes[i] = info;
+ largest_changes->min_size
+ = largest_changes->changes[largest_changes->count-1]->size;
+ }
+
+ /* global histograms */
+ add_to_histogram(&stats->rep_size_histogram, rep_size);
+ add_to_histogram(&stats->node_size_histogram, expanded_size);
+
+ if (plain_added)
+ {
+ add_to_histogram(&stats->added_rep_size_histogram, rep_size);
+ add_to_histogram(&stats->added_node_size_histogram, expanded_size);
+ }
+
+ /* specific histograms by type */
+ switch (kind)
+ {
+ case unused_rep:
+ add_to_histogram(&stats->unused_rep_histogram, rep_size);
+ break;
+ case dir_property_rep:
+ add_to_histogram(&stats->dir_prop_rep_histogram, rep_size);
+ add_to_histogram(&stats->dir_prop_histogram, expanded_size);
+ break;
+ case file_property_rep:
+ add_to_histogram(&stats->file_prop_rep_histogram, rep_size);
+ add_to_histogram(&stats->file_prop_histogram, expanded_size);
+ break;
+ case dir_rep:
+ add_to_histogram(&stats->dir_rep_histogram, rep_size);
+ add_to_histogram(&stats->dir_histogram, expanded_size);
+ break;
+ case file_rep:
+ add_to_histogram(&stats->file_rep_histogram, rep_size);
+ add_to_histogram(&stats->file_histogram, expanded_size);
+ break;
+ }
+
+ /* by extension */
+ if (kind == file_rep)
+ {
+ /* determine extension */
+ svn_fs_fs__extension_info_t *info;
+ const char * file_name = strrchr(path, '/');
+ const char * extension = file_name ? strrchr(file_name, '.') : NULL;
+
+ if (extension == NULL || extension == file_name + 1)
+ extension = "(none)";
+
+ /* get / auto-insert entry for this extension */
+ info = apr_hash_get(stats->by_extension, extension, APR_HASH_KEY_STRING);
+ if (info == NULL)
+ {
+ apr_pool_t *pool = apr_hash_pool_get(stats->by_extension);
+ info = apr_pcalloc(pool, sizeof(*info));
+ info->extension = apr_pstrdup(pool, extension);
+
+ apr_hash_set(stats->by_extension, info->extension,
+ APR_HASH_KEY_STRING, info);
+ }
+
+ /* update per-extension histogram */
+ add_to_histogram(&info->node_histogram, expanded_size);
+ add_to_histogram(&info->rep_histogram, rep_size);
+ }
+}
+
+/* Comparator used for binary search comparing the absolute file offset
+ * of a representation to some other offset. DATA is a *rep_stats_t,
+ * KEY is a pointer to an apr_off_t.
+ */
+static int
+compare_representation_offsets(const void *data, const void *key)
+{
+ apr_off_t lhs = (*(const rep_stats_t *const *)data)->offset;
+ apr_off_t rhs = *(const apr_off_t *)key;
+
+ if (lhs < rhs)
+ return -1;
+ return (lhs > rhs ? 1 : 0);
+}
+
+/* Find the revision_info_t object to the given REVISION in QUERY and
+ * return it in *REVISION_INFO. For performance reasons, we skip the
+ * lookup if the info is already provided.
+ *
+ * In that revision, look for the rep_stats_t object for offset OFFSET.
+ * If it already exists, set *IDX to its index in *REVISION_INFO's
+ * representations list and return the representation object. Otherwise,
+ * set the index to where it must be inserted and return NULL.
+ */
+static rep_stats_t *
+find_representation(int *idx,
+ query_t *query,
+ revision_info_t **revision_info,
+ svn_revnum_t revision,
+ apr_off_t offset)
+{
+ revision_info_t *info;
+ *idx = -1;
+
+ /* first let's find the revision */
+ info = revision_info ? *revision_info : NULL;
+ if (info == NULL || info->revision != revision)
+ {
+ info = APR_ARRAY_IDX(query->revisions, revision, revision_info_t*);
+ if (revision_info)
+ *revision_info = info;
+ }
+
+ /* not found -> no result */
+ if (info == NULL)
+ return NULL;
+
+ /* look for the representation */
+ *idx = svn_sort__bsearch_lower_bound(info->representations,
+ &offset,
+ compare_representation_offsets);
+ if (*idx < info->representations->nelts)
+ {
+ /* return the representation, if this is the one we were looking for */
+ rep_stats_t *result
+ = APR_ARRAY_IDX(info->representations, *idx, rep_stats_t *);
+ if (result->offset == offset)
+ return result;
+ }
+
+ /* not parsed, yet */
+ return NULL;
+}
+
+/* Find / auto-construct the representation stats for REP in QUERY and
+ * return it in *REPRESENTATION.
+ *
+ * If necessary, allocate the result in RESULT_POOL; use SCRATCH_POOL for
+ * temporary allocations.
+ */
+static svn_error_t *
+parse_representation(rep_stats_t **representation,
+ query_t *query,
+ representation_t *rep,
+ revision_info_t *revision_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ rep_stats_t *result;
+ int idx;
+
+ /* read location (revision, offset) and size */
+
+ /* look it up */
+ result = find_representation(&idx, query, &revision_info, rep->revision,
+ (apr_off_t)rep->item_index);
+ if (!result)
+ {
+ /* not parsed, yet (probably a rep in the same revision).
+ * Create a new rep object and determine its base rep as well.
+ */
+ result = apr_pcalloc(result_pool, sizeof(*result));
+ result->revision = rep->revision;
+ result->expanded_size = (rep->expanded_size ? rep->expanded_size
+ : rep->size);
+ result->offset = (apr_off_t)rep->item_index;
+ result->size = rep->size;
+
+ /* In phys. addressing mode, follow link to the actual representation.
+ * In log. addressing mode, we will find it already as part of our
+ * linear walk through the whole file. */
+ if (!svn_fs_fs__use_log_addressing(query->fs))
+ {
+ svn_fs_fs__rep_header_t *header;
+ apr_off_t offset = revision_info->offset + result->offset;
+
+ SVN_ERR_ASSERT(revision_info->rev_file);
+ SVN_ERR(svn_io_file_seek(revision_info->rev_file->file, APR_SET,
+ &offset, scratch_pool));
+ SVN_ERR(svn_fs_fs__read_rep_header(&header,
+ revision_info->rev_file->stream,
+ scratch_pool, scratch_pool));
+
+ result->header_size = header->header_size;
+ }
+
+ svn_sort__array_insert(revision_info->representations, &result, idx);
+ }
+
+ *representation = result;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* forward declaration */
+static svn_error_t *
+read_noderev(query_t *query,
+ svn_stringbuf_t *noderev_str,
+ revision_info_t *revision_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read the noderev item at OFFSET in REVISION_INFO from the filesystem
+ * provided by QUERY. Return it in *NODEREV, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * The textual representation of the noderev will be used to determine
+ * the on-disk size of the noderev. Only called in phys. addressing mode.
+ */
+static svn_error_t *
+read_phsy_noderev(svn_stringbuf_t **noderev,
+ query_t *query,
+ apr_off_t offset,
+ revision_info_t *revision_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *noderev_str = svn_stringbuf_create_empty(result_pool);
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Navigate the file stream to the start of noderev. */
+ SVN_ERR_ASSERT(revision_info->rev_file);
+
+ offset += revision_info->offset;
+ SVN_ERR(svn_io_file_seek(revision_info->rev_file->file, APR_SET,
+ &offset, scratch_pool));
+
+ /* Read it (terminated by an empty line) */
+ do
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_stream_readline(revision_info->rev_file->stream, &line,
+ "\n", &eof, iterpool));
+ svn_stringbuf_appendstr(noderev_str, line);
+ svn_stringbuf_appendbyte(noderev_str, '\n');
+ }
+ while (line->len > 0 && !eof);
+
+ /* Return the result. */
+ *noderev = noderev_str;
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Starting at the directory in NODEREV's text, read all DAG nodes,
+ * directories and representations linked in that tree structure.
+ * Store them in QUERY and REVISION_INFO. Also, read them only once.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+parse_dir(query_t *query,
+ node_revision_t *noderev,
+ revision_info_t *revision_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ int i;
+ apr_array_header_t *entries;
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, query->fs, noderev,
+ scratch_pool, scratch_pool));
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
+
+ if (svn_fs_fs__id_rev(dirent->id) == revision_info->revision)
+ {
+ svn_stringbuf_t *noderev_str;
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(read_phsy_noderev(&noderev_str, query,
+ svn_fs_fs__id_item(dirent->id),
+ revision_info, iterpool, iterpool));
+ SVN_ERR(read_noderev(query, noderev_str, revision_info,
+ result_pool, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse the noderev given as NODEREV_STR and store the info in QUERY and
+ * REVISION_INFO. In phys. addressing mode, continue reading all DAG nodes,
+ * directories and representations linked in that tree structure.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_noderev(query_t *query,
+ svn_stringbuf_t *noderev_str,
+ revision_info_t *revision_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ rep_stats_t *text = NULL;
+ rep_stats_t *props = NULL;
+ node_revision_t *noderev;
+
+ svn_stream_t *stream = svn_stream_from_stringbuf(noderev_str, scratch_pool);
+ SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, scratch_pool,
+ scratch_pool));
+
+ if (noderev->data_rep)
+ {
+ SVN_ERR(parse_representation(&text, query,
+ noderev->data_rep, revision_info,
+ result_pool, scratch_pool));
+
+ /* if we are the first to use this rep, mark it as "text rep" */
+ if (++text->ref_count == 1)
+ text->kind = noderev->kind == svn_node_dir ? dir_rep : file_rep;
+ }
+
+ if (noderev->prop_rep)
+ {
+ SVN_ERR(parse_representation(&props, query,
+ noderev->prop_rep, revision_info,
+ result_pool, scratch_pool));
+
+ /* if we are the first to use this rep, mark it as "prop rep" */
+ if (++props->ref_count == 1)
+ props->kind = noderev->kind == svn_node_dir ? dir_property_rep
+ : file_property_rep;
+ }
+
+ /* record largest changes */
+ if (text && text->ref_count == 1)
+ add_change(query->stats, text->size, text->expanded_size, text->revision,
+ noderev->created_path, text->kind, !noderev->predecessor_id);
+ if (props && props->ref_count == 1)
+ add_change(query->stats, props->size, props->expanded_size,
+ props->revision, noderev->created_path, props->kind,
+ !noderev->predecessor_id);
+
+ /* if this is a directory and has not been processed, yet, read and
+ * process it recursively */
+ if ( noderev->kind == svn_node_dir && text && text->ref_count == 1
+ && !svn_fs_fs__use_log_addressing(query->fs))
+ SVN_ERR(parse_dir(query, noderev, revision_info, result_pool,
+ scratch_pool));
+
+ /* update stats */
+ if (noderev->kind == svn_node_dir)
+ {
+ revision_info->dir_noderev_size += noderev_str->len;
+ revision_info->dir_noderev_count++;
+ }
+ else
+ {
+ revision_info->file_noderev_size += noderev_str->len;
+ revision_info->file_noderev_count++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* For the revision given as REVISION_INFO within QUERY, determine the number
+ * of entries in its changed paths list and store that info in REVISION_INFO.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_phys_change_count(query_t *query,
+ revision_info_t *revision_info,
+ apr_pool_t *scratch_pool)
+{
+ /* We are going to use our own sub-pool here because the changes object
+ * may well be >100MB and SCRATCH_POOL may not get cleared until all other
+ * info has been read by read_phys_revision(). Therefore, tidy up early.
+ */
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *changes;
+
+ SVN_ERR(svn_fs_fs__get_changes(&changes, query->fs,
+ revision_info->revision, subpool));
+ revision_info->change_count = changes->nelts;
+
+ /* Release potentially tons of memory. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read header information for the revision stored in FILE_CONTENT (one
+ * whole revision). Return the offsets within FILE_CONTENT for the
+ * *ROOT_NODEREV, the list of *CHANGES and its len in *CHANGES_LEN.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+read_phys_revision(query_t *query,
+ revision_info_t *info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char buf[64];
+ apr_off_t root_node_offset;
+ apr_off_t changes_offset;
+ svn_stringbuf_t *trailer;
+ svn_stringbuf_t *noderev_str;
+
+ /* Read the last 64 bytes of the revision (if long enough). */
+ apr_off_t start = MAX(info->offset, info->end - sizeof(buf));
+ apr_size_t len = (apr_size_t)(info->end - start);
+ SVN_ERR(svn_io_file_seek(info->rev_file->file, APR_SET, &start,
+ scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(info->rev_file->file, buf, len, NULL, NULL,
+ scratch_pool));
+ trailer = svn_stringbuf_ncreate(buf, len, scratch_pool);
+
+ /* Parse that trailer. */
+ SVN_ERR(svn_fs_fs__parse_revision_trailer(&root_node_offset,
+ &changes_offset, trailer,
+ info->revision));
+ SVN_ERR(get_phys_change_count(query, info, scratch_pool));
+
+ /* Calculate the length of the changes list. */
+ trailer = svn_fs_fs__unparse_revision_trailer(root_node_offset,
+ changes_offset,
+ scratch_pool);
+ info->changes_len = info->end - info->offset - changes_offset
+ - trailer->len;
+
+ /* Recursively read nodes added in this rev. */
+ SVN_ERR(read_phsy_noderev(&noderev_str, query, root_node_offset, info,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_noderev(query, noderev_str, info, result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the content of the pack file staring at revision BASE physical
+ * addressing mode and store it in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_phys_pack_file(query_t *query,
+ svn_revnum_t base,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ apr_off_t file_size = 0;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, query->fs, base,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_file_size(&file_size, rev_file, scratch_pool));
+
+ /* process each revision in the pack file */
+ for (i = 0; i < query->shard_size; ++i)
+ {
+ revision_info_t *info;
+
+ /* cancellation support */
+ if (query->cancel_func)
+ SVN_ERR(query->cancel_func(query->cancel_baton));
+
+ /* create the revision info for the current rev */
+ info = apr_pcalloc(result_pool, sizeof(*info));
+ info->representations = apr_array_make(result_pool, 4,
+ sizeof(rep_stats_t*));
+ info->rev_file = rev_file;
+
+ info->revision = base + i;
+ SVN_ERR(svn_fs_fs__get_packed_offset(&info->offset, query->fs, base + i,
+ iterpool));
+ if (i + 1 == query->shard_size)
+ info->end = file_size;
+ else
+ SVN_ERR(svn_fs_fs__get_packed_offset(&info->end, query->fs,
+ base + i + 1, iterpool));
+
+ SVN_ERR(read_phys_revision(query, info, result_pool, iterpool));
+
+ info->representations = apr_array_copy(result_pool,
+ info->representations);
+
+ /* Done with this revision. */
+ info->rev_file = NULL;
+
+ /* put it into our container */
+ APR_ARRAY_PUSH(query->revisions, revision_info_t*) = info;
+
+ /* destroy temps */
+ svn_pool_clear(iterpool);
+ }
+
+ /* Done with this pack file. */
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ /* one more pack file processed */
+ if (query->progress_func)
+ query->progress_func(base, query->progress_baton, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the content of the file for REVISION in physical addressing mode
+ * and store its contents in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_phys_revision_file(query_t *query,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ revision_info_t *info = apr_pcalloc(result_pool, sizeof(*info));
+ apr_off_t file_size = 0;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ /* cancellation support */
+ if (query->cancel_func)
+ SVN_ERR(query->cancel_func(query->cancel_baton));
+
+ /* read the whole pack file into memory */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, query->fs, revision,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_file_size(&file_size, rev_file, scratch_pool));
+
+ /* create the revision info for the current rev */
+ info->representations = apr_array_make(result_pool, 4, sizeof(rep_stats_t*));
+
+ info->rev_file = rev_file;
+ info->revision = revision;
+ info->offset = 0;
+ info->end = file_size;
+
+ SVN_ERR(read_phys_revision(query, info, result_pool, scratch_pool));
+
+ /* Done with this revision. */
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+ info->rev_file = NULL;
+
+ /* put it into our container */
+ APR_ARRAY_PUSH(query->revisions, revision_info_t*) = info;
+
+ /* show progress every 1000 revs or so */
+ if (query->progress_func)
+ {
+ if (query->shard_size && (revision % query->shard_size == 0))
+ query->progress_func(revision, query->progress_baton, scratch_pool);
+ if (!query->shard_size && (revision % 1000 == 0))
+ query->progress_func(revision, query->progress_baton, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Given the unparsed changes list in CHANGES with LEN chars, return the
+ * number of changed paths encoded in it. Only used in log. addressing
+ * mode.
+ */
+static apr_uint64_t
+get_log_change_count(const char *changes,
+ apr_size_t len)
+{
+ apr_size_t lines = 0;
+ const char *end = changes + len;
+
+ /* line count */
+ for (; changes < end; ++changes)
+ if (*changes == '\n')
+ ++lines;
+
+ /* two lines per change */
+ return lines / 2;
+}
+
+/* Read the item described by ENTRY from the REV_FILE and return the
+ * respective byte sequence in *CONTENTS, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations
+ */
+static svn_error_t *
+read_item(svn_stringbuf_t **contents,
+ svn_fs_fs__revision_file_t *rev_file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *item = svn_stringbuf_create_ensure(entry->size,
+ result_pool);
+ item->len = entry->size;
+ item->data[item->len] = 0;
+
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, rev_file->block_size,
+ NULL, entry->offset, scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(rev_file->file, item->data, item->len,
+ NULL, NULL, scratch_pool));
+
+ *contents = item;
+
+ return SVN_NO_ERROR;
+}
+
+/* Process the logically addressed revision contents of revisions BASE to
+ * BASE + COUNT - 1 in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_log_rev_or_packfile(query_t *query,
+ svn_revnum_t base,
+ int count,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = query->fs->fsap_data;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_off_t max_offset;
+ apr_off_t offset = 0;
+ int i;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ /* we will process every revision in the rev / pack file */
+ for (i = 0; i < count; ++i)
+ {
+ /* create the revision info for the current rev */
+ revision_info_t *info = apr_pcalloc(result_pool, sizeof(*info));
+ info->representations = apr_array_make(result_pool, 4,
+ sizeof(rep_stats_t*));
+ info->revision = base + i;
+
+ APR_ARRAY_PUSH(query->revisions, revision_info_t*) = info;
+ }
+
+ /* open the pack / rev file that is covered by the p2l index */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, query->fs, base,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_fs_fs__p2l_get_max_offset(&max_offset, query->fs, rev_file,
+ base, scratch_pool));
+
+ /* record the whole pack size in the first rev so the total sum will
+ still be correct */
+ APR_ARRAY_IDX(query->revisions, base, revision_info_t*)->end = max_offset;
+
+ /* for all offsets in the file, get the P2L index entries and process
+ the interesting items (change lists, noderevs) */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+
+ svn_pool_clear(iterpool);
+
+ /* cancellation support */
+ if (query->cancel_func)
+ SVN_ERR(query->cancel_func(query->cancel_baton));
+
+ /* get all entries for the current block */
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, query->fs, rev_file, base,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+
+ /* process all entries (and later continue with the next block) */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+
+ /* skip bits we previously processed */
+ if (i == 0 && entry->offset < offset)
+ continue;
+
+ /* skip zero-sized entries */
+ if (entry->size == 0)
+ continue;
+
+ /* read and process interesting items */
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_NODEREV)
+ {
+ svn_stringbuf_t *item;
+ revision_info_t *info = APR_ARRAY_IDX(query->revisions,
+ entry->item.revision,
+ revision_info_t*);
+ SVN_ERR(read_item(&item, rev_file, entry, iterpool, iterpool));
+ SVN_ERR(read_noderev(query, item, info, result_pool, iterpool));
+ }
+ else if (entry->type == SVN_FS_FS__ITEM_TYPE_CHANGES)
+ {
+ svn_stringbuf_t *item;
+ revision_info_t *info = APR_ARRAY_IDX(query->revisions,
+ entry->item.revision,
+ revision_info_t*);
+ SVN_ERR(read_item(&item, rev_file, entry, iterpool, iterpool));
+ info->change_count
+ = get_log_change_count(item->data + 0, item->len);
+ info->changes_len += entry->size;
+ }
+
+ /* advance offset */
+ offset += entry->size;
+ }
+ }
+
+ /* clean up and close file handles */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the content of the pack file staring at revision BASE logical
+ * addressing mode and store it in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_log_pack_file(query_t *query,
+ svn_revnum_t base,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(read_log_rev_or_packfile(query, base, query->shard_size,
+ result_pool, scratch_pool));
+
+ /* one more pack file processed */
+ if (query->progress_func)
+ query->progress_func(base, query->progress_baton, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the content of the file for REVISION in logical addressing mode
+ * and store its contents in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_log_revision_file(query_t *query,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(read_log_rev_or_packfile(query, revision, 1,
+ result_pool, scratch_pool));
+
+ /* show progress every 1000 revs or so */
+ if (query->progress_func)
+ {
+ if (query->shard_size && (revision % query->shard_size == 0))
+ query->progress_func(revision, query->progress_baton, scratch_pool);
+ if (!query->shard_size && (revision % 1000 == 0))
+ query->progress_func(revision, query->progress_baton, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the repository and collect the stats info in QUERY.
+ *
+ * Use RESULT_POOL for persistent allocations and SCRATCH_POOL for
+ * temporaries.
+ */
+static svn_error_t *
+read_revisions(query_t *query,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_revnum_t revision;
+
+ /* read all packed revs */
+ for ( revision = 0
+ ; revision < query->min_unpacked_rev
+ ; revision += query->shard_size)
+ {
+ svn_pool_clear(iterpool);
+
+ if (svn_fs_fs__use_log_addressing(query->fs))
+ SVN_ERR(read_log_pack_file(query, revision, result_pool, iterpool));
+ else
+ SVN_ERR(read_phys_pack_file(query, revision, result_pool, iterpool));
+ }
+
+ /* read non-packed revs */
+ for ( ; revision <= query->head; ++revision)
+ {
+ svn_pool_clear(iterpool);
+
+ if (svn_fs_fs__use_log_addressing(query->fs))
+ SVN_ERR(read_log_revision_file(query, revision, result_pool,
+ iterpool));
+ else
+ SVN_ERR(read_phys_revision_file(query, revision, result_pool,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Accumulate stats of REP in STATS.
+ */
+static void
+add_rep_pack_stats(svn_fs_fs__rep_pack_stats_t *stats,
+ rep_stats_t *rep)
+{
+ stats->count++;
+
+ stats->packed_size += rep->size;
+ stats->expanded_size += rep->expanded_size;
+ stats->overhead_size += rep->header_size + 7 /* ENDREP\n */;
+}
+
+/* Accumulate stats of REP in STATS.
+ */
+static void
+add_rep_stats(svn_fs_fs__representation_stats_t *stats,
+ rep_stats_t *rep)
+{
+ add_rep_pack_stats(&stats->total, rep);
+ if (rep->ref_count == 1)
+ add_rep_pack_stats(&stats->uniques, rep);
+ else
+ add_rep_pack_stats(&stats->shared, rep);
+
+ stats->references += rep->ref_count;
+ stats->expanded_size += rep->ref_count * rep->expanded_size;
+}
+
+/* Aggregate the info the in revision_info_t * array REVISIONS into the
+ * respectve fields of STATS.
+ */
+static void
+aggregate_stats(const apr_array_header_t *revisions,
+ svn_fs_fs__stats_t *stats)
+{
+ int i, k;
+
+ /* aggregate info from all revisions */
+ stats->revision_count = revisions->nelts;
+ for (i = 0; i < revisions->nelts; ++i)
+ {
+ revision_info_t *revision = APR_ARRAY_IDX(revisions, i,
+ revision_info_t *);
+
+ /* data gathered on a revision level */
+ stats->change_count += revision->change_count;
+ stats->change_len += revision->changes_len;
+ stats->total_size += revision->end - revision->offset;
+
+ stats->dir_node_stats.count += revision->dir_noderev_count;
+ stats->dir_node_stats.size += revision->dir_noderev_size;
+ stats->file_node_stats.count += revision->file_noderev_count;
+ stats->file_node_stats.size += revision->file_noderev_size;
+ stats->total_node_stats.count += revision->dir_noderev_count
+ + revision->file_noderev_count;
+ stats->total_node_stats.size += revision->dir_noderev_size
+ + revision->file_noderev_size;
+
+ /* process representations */
+ for (k = 0; k < revision->representations->nelts; ++k)
+ {
+ rep_stats_t *rep = APR_ARRAY_IDX(revision->representations, k,
+ rep_stats_t *);
+
+ /* accumulate in the right bucket */
+ switch(rep->kind)
+ {
+ case file_rep:
+ add_rep_stats(&stats->file_rep_stats, rep);
+ break;
+ case dir_rep:
+ add_rep_stats(&stats->dir_rep_stats, rep);
+ break;
+ case file_property_rep:
+ add_rep_stats(&stats->file_prop_rep_stats, rep);
+ break;
+ case dir_property_rep:
+ add_rep_stats(&stats->dir_prop_rep_stats, rep);
+ break;
+ default:
+ break;
+ }
+
+ add_rep_stats(&stats->total_rep_stats, rep);
+ }
+ }
+}
+
+/* Return a new svn_fs_fs__stats_t instance, allocated in RESULT_POOL.
+ */
+static svn_fs_fs__stats_t *
+create_stats(apr_pool_t *result_pool)
+{
+ svn_fs_fs__stats_t *stats = apr_pcalloc(result_pool, sizeof(*stats));
+
+ initialize_largest_changes(stats, 64, result_pool);
+ stats->by_extension = apr_hash_make(result_pool);
+
+ return stats;
+}
+
+/* Create a *QUERY, allocated in RESULT_POOL, reading filesystem FS and
+ * collecting results in STATS. Store the optional PROCESS_FUNC and
+ * PROGRESS_BATON as well as CANCEL_FUNC and CANCEL_BATON in *QUERY, too.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+create_query(query_t **query,
+ svn_fs_t *fs,
+ svn_fs_fs__stats_t *stats,
+ svn_fs_progress_notify_func_t progress_func,
+ void *progress_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *query = apr_pcalloc(result_pool, sizeof(**query));
+
+ /* Read repository dimensions. */
+ (*query)->shard_size = svn_fs_fs__shard_size(fs);
+ SVN_ERR(svn_fs_fs__youngest_rev(&(*query)->head, fs, scratch_pool));
+ SVN_ERR(svn_fs_fs__min_unpacked_rev(&(*query)->min_unpacked_rev, fs,
+ scratch_pool));
+
+ /* create data containers and caches
+ * Note: this assumes that int is at least 32-bits and that we only support
+ * 32-bit wide revision numbers (actually 31-bits due to the signedness
+ * of both the nelts field of the array and our revision numbers). This
+ * means this code will fail on platforms where int is less than 32-bits
+ * and the repository has more revisions than int can hold. */
+ (*query)->revisions = apr_array_make(result_pool, (int) (*query)->head + 1,
+ sizeof(revision_info_t *));
+ (*query)->null_base = apr_pcalloc(result_pool,
+ sizeof(*(*query)->null_base));
+
+ /* Store other parameters */
+ (*query)->fs = fs;
+ (*query)->stats = stats;
+ (*query)->progress_func = progress_func;
+ (*query)->progress_baton = progress_baton;
+ (*query)->cancel_func = cancel_func;
+ (*query)->cancel_baton = cancel_baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_stats(svn_fs_fs__stats_t **stats,
+ svn_fs_t *fs,
+ svn_fs_progress_notify_func_t progress_func,
+ void *progress_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ query_t *query;
+
+ *stats = create_stats(result_pool);
+ SVN_ERR(create_query(&query, fs, *stats, progress_func, progress_baton,
+ cancel_func, cancel_baton, scratch_pool,
+ scratch_pool));
+ SVN_ERR(read_revisions(query, scratch_pool, scratch_pool));
+ aggregate_stats(query->revisions, *stats);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure
index 41caf1d..7b5129f 100644
--- a/subversion/libsvn_fs_fs/structure
+++ b/subversion/libsvn_fs_fs/structure
@@ -43,7 +43,7 @@ repository) is:
<shard>.pack/ Pack directory, if the repo has been packed (see below)
<rev>.<count> Pack file, if the repository has been packed (see below)
manifest Pack manifest file, if a pack file exists (see below)
- revprops.db SQLite database of the packed revision properties
+ revprops.db SQLite database of the packed revprops (format 5 only)
transactions/ Subdirectory containing transactions
<txnid>.txn/ Directory containing transaction <txnid>
txn-protorevs/ Subdirectory containing transaction proto-revision files
@@ -58,12 +58,13 @@ repository) is:
current File specifying current revision and next node/copy id
fs-type File identifying this filesystem as an FSFS filesystem
write-lock Empty file, locked to serialise writers
+ pack-lock Empty file, locked to serialise 'svnadmin pack' (f. 7+)
txn-current-lock Empty file, locked to serialise 'txn-current'
- uuid File containing the UUID of the repository
+ uuid File containing the repository IDs
format File containing the format number of this filesystem
fsfs.conf Configuration file
min-unpacked-rev File containing the oldest revision not in a pack file
- min-unpacked-revprop File containing the oldest revision of unpacked revprop
+ min-unpacked-revprop Same for revision properties (format 5 only)
rep-cache.db SQLite database mapping rep checksums to locations
Files in the revprops directory are in the hash dump format used by
@@ -84,9 +85,19 @@ The "write-lock" file is an empty file which is locked before the
final stage of a commit and unlocked after the new "current" file has
been moved into place to indicate that a new revision is present. It
is also locked during a revprop propchange while the revprop file is
-read in, mutated, and written out again. Note that readers are never
-blocked by any operation - writers must ensure that the filesystem is
-always in a consistent state.
+read in, mutated, and written out again. Furthermore, it will be used
+to serialize the repository structure changes during 'svnadmin pack'
+(see also next section). Note that readers are never blocked by any
+operation - writers must ensure that the filesystem is always in a
+consistent state.
+
+The "pack-lock" file is an empty file which is locked before an 'svnadmin
+pack' operation commences. Thus, only one process may attempt to modify
+the repository structure at a time while other processes may still read
+and write (commit) to the repository during most of the pack procedure.
+It is only available with format 7 and newer repositories. Older formats
+use the global write-lock instead which disables commits completely
+for the duration of the pack process.
The "txn-current" file is a file with a single line of text that
contains only a base-36 number. The current value will be used in the
@@ -138,6 +149,7 @@ The formats are:
Format 4, understood by Subversion 1.6+
Format 5, understood by Subversion 1.7-dev, never released
Format 6, understood by Subversion 1.8
+ Format 7, understood by Subversion 1.9
The differences between the formats are:
@@ -148,6 +160,7 @@ Delta representation in revision files
Format options
Formats 1-2: none permitted
Format 3+: "layout" option
+ Format 7+: "addressing" option
Transaction name reuse
Formats 1-2: transaction names may be reused
@@ -176,6 +189,7 @@ Mergeinfo metadata:
Revision changed paths list:
Format 1-3: Does not contain the node's kind.
Format 4+: Contains the node's kind.
+ Format 7+: Contains the mergeinfo-mod flag.
Shard packing:
Format 4: Applied to revision data only.
@@ -183,15 +197,25 @@ Shard packing:
Format 6+: Applied equally to revision data and revprop data
(i.e. same min packed revision)
+Addressing:
+ Format 1-6: Physical addressing; uses fixed positions within a rev file
+ Format 7+: Logical addressing; uses item index that will be translated
+ on-the-fly to the actual rev / pack file location
+
+Repository IDs:
+ Format 1+: The first line of db/uuid contains the repository UUID
+ Format 7+: The second line contains the instance ID (in UUID formatting)
+
# Incomplete list. See SVN_FS_FS__MIN_*_FORMAT
Filesystem format options
-------------------------
-Currently, the only recognised format option is "layout", which
-specifies the paths that will be used to store the revision files and
-revision property files.
+Currently, the only recognised format options are "layout" and "addressing".
+The first specifies the paths that will be used to store the revision
+files and revision property files. The second specifies that logical to
+physical address translation is required.
The "layout" option is followed by the name of the filesystem layout
and any required parameters. The default layout, if no "layout"
@@ -219,19 +243,92 @@ The known layouts, and the parameters they require, are as follows:
revs/0/ directory will contain revisions 0-999, revs/1/ will contain
1000-1999, and so on.
+The "addressing" option is followed by the name of the addressing mode
+and any required parameters. The default addressing, if no "addressing"
+keyword is specified, is the 'physical' addressing.
+
+The supported modes, and the parameters they require, are as follows:
+
+"physical"
+ All existing and future revision files will use the traditional
+ physical addressing scheme. All references are given as rev/offset
+ pairs with "offset" being the byte offset relative to the beginning of
+ the revision in the respective rev or pack file.
+
+"logical"
+ All existing and future revision files will use logical
+ addressing. It is illegal to use logical addressing on non-sharded
+ repositories.
+
+
+Addressing modes
+----------------
+
+Two addressing modes are supported in format 7: physical and logical
+addressing. Both use the same address format but apply a different
+interpretation to it. Older formats only support physical addressing.
+
+All items are addressed using <rev> <item_index> pairs. In physical
+addressing mode, item_index is the (ASCII decimal) number of bytes from
+the start of the revision file to the start of the respective item. For
+non-packed files that is also the absolute file offset. Revision pack
+files simply concatenate multiple rev files, i.e. the absolute file offset
+is determined as
+
+ absolute offset = rev offset taken from manifest + item_index
+
+This simple addressing scheme makes it hard to change the location of
+any item since that may break references from later revisions.
+
+Logical addressing uses an index file to translate the rev / item_index
+pairs into absolute file offsets. There is one such index for every rev /
+pack file using logical addressing and both are created in sync. That
+makes it possible to reorder items during pack file creation, particularly
+to mix items from different revisions.
+
+Some item_index values are pre-defined and apply to every revision:
+
+ 0 ... not used / invalid
+ 1 ... changed path list
+ 2 ... root node revision
+
+A reverse index (phys-to-log) is being created as well that allows for
+translating arbitrary file locations into item descriptions (type, rev,
+item_index, on-disk length). Known item types
+
+ 0 ... unused / empty section
+ 1 ... file representation
+ 2 ... directory representation
+ 3 ... file property representation
+ 4 ... directory property representation
+ 5 ... node revision
+ 6 ... changed paths list
+
+The various representation types all share the same morphology. The
+distinction is only made to allow for more effective reordering heuristics.
+Zero-length items are allowed.
+
+
Packing revisions
-----------------
A filesystem can optionally be "packed" to conserve space on disk. The
packing process concatenates all the revision files in each full shard to
-create pack files. A manifest file is also created for each shard which
+create a pack file. The original shard is removed, and reads are
+redirected to the pack file.
+
+With physical addressing, a manifest file is created for each shard which
records the indexes of the corresponding revision files in the pack file.
-In addition, the original shard is removed, and reads are redirected to the
-pack file.
+The manifest file consists of a list of offsets, one for each revision in
+the pack file. The offsets are stored as ASCII decimal, and separated by
+a newline character.
+
+Revision pack files using logical addressing don't use manifest files but
+appends index data to the revision contents. The revisions inside a pack
+file will also get interleaved to reduce I/O for typical access patterns.
+There is no structural difference between packed and non-packed revision
+files in that mode.
-The manifest file consists of a list of offsets, one for each revision in the
-pack file. The offsets are stored as ASCII decimal, and separated by a newline
-character.
Packing revision properties (format 5: SQLite)
---------------------------
@@ -341,13 +438,12 @@ Within a new transaction:
Within a revision:
Within a revision file, node-revs have a txn-id field of the form
- "r<rev>/<offset>", to support easy lookup. The <offset> is the (ASCII
- decimal) number of bytes from the start of the revision file to the
- start of the node-rev.
+ "r<rev>/<item_index>", to support easy lookup. See addressing modes
+ for details.
During the final phase of a commit, node-revision IDs are rewritten
to have repository-wide unique node-ID and copy-ID fields, and to have
- "r<rev>/<offset>" txn-id fields.
+ "r<rev>/<item_index>" txn-id fields.
In Format 3 and above, this uniqueness is done by changing a temporary
id of "_<base36>" to "<base36>-<rev>". Note that this means that the
@@ -429,13 +525,14 @@ A revision file contains a concatenation of various kinds of data:
* Text and property representations
* Node-revisions
* The changed-path data
- * Two offsets at the very end
+ * Index data (logical addressing only)
+ * Revision / pack file footer (logical addressing only)
A representation begins with a line containing either "PLAIN\n" or
-"DELTA\n" or "DELTA <rev> <offset> <length>\n", where <rev>, <offset>,
-and <length> give the location of the delta base of the representation
-and the amount of data it contains (not counting the header or
-trailer). If no base location is given for a delta, the base is the
+"DELTA\n" or "DELTA <rev> <item_index> <length>\n", where <rev>,
+<item_index>, and <length> give the location of the delta base of the
+representation and the amount of data it contains (not counting the header
+or trailer). If no base location is given for a delta, the base is the
empty stream. After the initial line comes raw svndiff data, followed
by a cosmetic trailer "ENDREP\n".
@@ -459,12 +556,11 @@ defined:
type "file" or "dir"
pred The ID of the predecessor node-rev
count Count of node-revs since the base of the node
- text "<rev> <offset> <length> <size> <digest>" for text rep
- props "<rev> <offset> <length> <size> <digest>" for props rep
- <rev> and <offset> give location of rep
+ text "<rev> <item_index> <length> <size> <digest>" for text rep
+ props "<rev> <item_index> <length> <size> <digest>" for props rep
+ <rev> and <item_index> give location of rep
<length> gives length of rep, sans header and trailer
- <size> gives size of expanded rep; may be 0 if equal
- to the length
+ <size> gives size of expanded rep (*)
<digest> gives hex MD5 digest of expanded rep
### in formats >=4, also present:
<sha1-digest> gives hex SHA1 digest of expanded rep
@@ -476,6 +572,16 @@ defined:
which have svn:mergeinfo.
minfo-here Exists if this node itself has svn:mergeinfo.
+(*) Earlier versions of this document would state that <size> may be 0
+ if the actual value matches <length>. This is only true for property
+ and directory representations and should be avoided in general. File
+ representations may not be handled correctly by SVN before 1.7.20,
+ 1.8.12 and 1.9.0, if they have 0 <size> fields for non-empty contents.
+ Releases 1.8.0 through 1.8.11 may have falsely created instances of
+ that (see issue #4554). Finally, 0 <size> fields are only ever legal
+ for DELTA representations if the reconstructed full-text is actually
+ empty.
+
The predecessor of a node-rev crosses both soft and true copies;
together with the count field, it allows efficient determination of
the base for skip-deltas. The first node-rev of a node contains no
@@ -489,28 +595,40 @@ of the copy; it may be omitted if the node-rev is its own copy root
of revision 0). Copy roots are identified by revision and
created-path, not by node-rev ID, because a copy root may be a
node-rev which exists later on within the same revision file, meaning
-its offset is not yet known.
+its location is not yet known.
The changed-path data is represented as a series of changed-path
items, each consisting of two lines. The first line has the format
-"<id> <action> <text-mod> <prop-mod> <path>\n", where <id> is the
-node-rev ID of the new node-rev, <action> is "add", "delete",
-"replace", or "modify", <text-mod> and <prop-mod> are "true" or
-"false" indicating whether the text and/or properties changed, and
-<path> is the changed pathname. For deletes, <id> is the node-rev ID
-of the deleted node-rev, and <text-mod> and <prop-mod> are always
-"false". The second line has the format "<rev> <path>\n" containing
-the node-rev's copyfrom information if it has any; if it does not, the
-second line is blank.
+"<id> <action> <text-mod> <prop-mod> <mergeinfo-mod> <path>\n",
+where <id> is the node-rev ID of the new node-rev, <action> is "add",
+"delete", "replace", or "modify", <text-mod>, <prop-mod>, and
+<mergeinfo-mod> are "true" or "false" indicating whether the text,
+properties and/or mergeinfo changed, and <path> is the changed pathname.
+For deletes, <id> is the node-rev ID of the deleted node-rev, and
+<text-mod> and <prop-mod> are always "false". The second line has the
+format "<rev> <path>\n" containing the node-rev's copyfrom information
+if it has any; if it does not, the second line is blank.
Starting with FS format 4, <action> may contain the kind ("file" or
"dir") of the node, after a hyphen; for example, an added directory
may be represented as "add-dir".
-At the very end of a rev file is a pair of lines containing
-"\n<root-offset> <cp-offset>\n", where <root-offset> is the offset of
-the root directory node revision and <cp-offset> is the offset of the
-changed-path data.
+Prior to FS format 7, <mergeinfo-mod> flag is not available. It may
+also be missing in revisions upgraded from pre-f7 formats.
+
+In physical addressing mode, at the very end of a rev file is a pair of
+lines containing "\n<root-offset> <cp-offset>\n", where <root-offset> is
+the offset of the root directory node revision and <cp-offset> is the
+offset of the changed-path data.
+
+In logical addressing mode, the revision footer has the form
+
+ <l2p offset> <l2p checksum> <p2l offset> <p2l checksum><terminal byte>
+
+The terminal byte contains the length (as plain 8 bit value) of the footer
+excluding that length byte. The first offset is the start of the log-to-
+phys index, followed by the digest of the MD5 checksum over its content.
+The other pair gives the same of for the phys-to-log index.
All numbers in the rev file format are unsigned and are represented as
ASCII decimal.
@@ -521,6 +639,7 @@ Transaction layout
A transaction directory has the following layout:
props Transaction props
+ props-final Final transaction props (optional)
next-ids Next temporary node-ID and copy-ID
changes Changed-path information so far
node.<nid>.<cid> New node-rev data for node
@@ -533,19 +652,29 @@ In FS formats 1 and 2, it also contains:
rev Prototype rev file with new text reps
rev-lock Lockfile for writing to the above
-In newer formats, these files are in the txn-protorevs/ directory.
+(In newer formats, these files are in the txn-protorevs/ directory.)
+
+In format 7+ logical addressing mode, it contains two additional index
+files (see structure-indexes for a detailed description) and one more
+counter file:
+
+ itemidx Next item_index value as decimal integer
+ index.l2p Log-to-phys proto-index
+ index.p2l Phys-to-log proto-index
The prototype rev file is used to store the text representations as
they are received from the client. To ensure that only one client is
writing to the file at a given time, the "rev-lock" file is locked for
the duration of each write.
-The two kinds of props files are all in hash dump format. The "props"
+The three kinds of props files are all in hash dump format. The "props"
file will always be present. The "node.<nid>.<cid>.props" file will
-only be present if the node-rev properties have been changed.
+only be present if the node-rev properties have been changed. The
+"props-final" only exists while converting the transaction into a revision.
+
The <sha1> files have been introduced in FS format 6. Their content
-is that of text rep references: "<rev> <offset> <length> <size> <digest>"
+is that of text rep references: "<rev> <item_offset> <length> <size> <digest>"
They will be written for text reps in the current transaction and be
used to eliminate duplicate reps within that transaction.
@@ -619,3 +748,15 @@ reference the same path as above, but look for a list of children in
that file (instead of lock information). Children are listed as MD5
digests, too, so you would simply iterate over those digests and
consult the files they reference for lock information.
+
+
+Index Data
+----------
+
+Format 7 introduces logical addressing that requires item indexes
+to be translated / mapped to physical rev / pack file offsets.
+These indexes are appended to the respective rev / pack file.
+
+Details of the binary format used by these index files can be
+found in structure-indexes.
+
diff --git a/subversion/libsvn_fs_fs/structure-indexes b/subversion/libsvn_fs_fs/structure-indexes
new file mode 100644
index 0000000..25490c7
--- /dev/null
+++ b/subversion/libsvn_fs_fs/structure-indexes
@@ -0,0 +1,352 @@
+This file describes the design, data model, and storage formats of FSFS
+index data.
+
+
+Design
+======
+
+Each pack and each rev file using logical addressing contains exactly
+two index sections. One, the log-to-phys index, maps the (rev, item_index)
+pairs to absolute file offsets. The other, phys-to-log, is a reverse
+index that gives basic information on any file location. This is enough
+to read and cache any data without traversing DAGs.
+
+Rev and pack files are immutable, so the same is true for index data.
+During a transaction or while packing a file, a proto index file gets
+written (actually, one log-to-phys and one phys-to-log). Its format is
+a simple concatenation of runtime structs and as such, an implementation
+detail subject to change. A proto index basically aggregates all the
+information that must later be transformed into the final index.
+
+
+General design concerns
+-----------------------
+
+In Subversion, there is no limit to the size of a revision; even practical
+limits are in the order of millions of changes at least. Index data for
+these would be multiple megabytes in size with pack file indexes possibly
+approaching 1 GB. To ensure we still get roughly O(1) access time, we
+need a hierarchical data structure.
+
+Therefore, the indexes will start with a header containing an array of
+references to sub-sections or pages. The length of these pages varies
+but is limited to a size configurable in fsfs.conf.
+
+Finally, it is assumed that whole pages can be cached efficiently and
+with a high cache hit rate. So, although a page may have a thousand or
+more entries, the access time is still amortized O(1) in many scenarios.
+
+
+Items and item types
+--------------------
+
+The index implementation treats item_index and item type as simple ints,
+except for SVN_FS_FS__ITEM_INDEX_UNUSED and SVN_FS_FS__ITEM_TYPE_UNUSED.
+Since they have been defined as 0, the code may test for "used" etc.
+by simply comparing with 0.
+
+See section "addressing modes" in structure to a list of item types
+and pre-defined item_index values.
+
+
+Encoding
+--------
+
+The final index data format is tuned for space and decoding efficiency.
+Indexes are stored as a sequence of variable integers. The encoding is
+as follows:
+
+* Unsigned integers are stored in little endian order with a variable
+ length 7b/8b encoding. If most significant bit a byte has been set,
+ the next byte has also belongs to the same value.
+
+ 0x00 .. 0x7f -> 0x00 .. 0x7f ( 7 bits stored in 8 bits)
+ 0x80 .. 0xff -> 0x80 0x01 .. 0xff 0x01 (14 bits stored in 16 bits)
+ 0x100 .. 0x3fff -> 0x80 0x02 .. 0xff 0x7f (14 bits stored in 16 bits)
+ 0x100000000 -> 0x80 0x80 0x80 0x80 0x10 (35 bits stored in 40 bits)
+
+ Technically, we can represent integers of arbitrary lengths. Currently,
+ we only generate and parse up to 64 bits.
+
+* Signed integers are mapped onto the unsigned value space as follows:
+
+ x >= 0 -> 2 * x
+ x < 0 -> -2 * x - 1
+
+ Again, we can represent arbitrary length numbers that way but the code
+ is currently restricted to 64 bits.
+
+Most data is unsigned by nature but will be stored differentially using
+signed integers.
+
+
+Encoding in proto-index files
+-----------------------------
+
+These have a much simpler encoding. Throughout the files, all records have
+the same length (but different between L2P and P2L). All records contain
+unsigned 64 bit integers only, stored in little endian byte order.
+
+
+Log-to-phys index
+=================
+
+This index has to map (rev, item_index) -> offset. It assumes that the
+item_index values per revision are dense and start at 0. There may be
+unused item_index values, though; the data structure simply gets less
+space-efficient when the more sparse the value space gets.
+
+
+Index data model
+----------------
+
+hierarchy:
+
+ header -> per-revision info -> page -> offset
+
+ There is one entry per revision in the header. Per revision there are
+ one or more pages (exclusive to that revision) containing up to a known,
+ fixed limit of entries (= page size). The total access path is:
+
+ pages = header->pages[revision];
+ offsets = page = pages[item_index / page_size];
+ offset = offsets[item_index % page_size];
+
+ Different log-to-phys indexes in the same repository may have different
+ page sizes but within any given index, the page size is the same and
+ immutable.
+
+header:
+
+ <first revision> ... first revision covered by this index
+ <revision count> ... number of revision covered by this index
+ <page size> ... maximum number of entries per page
+ <page table index> ... array, for each revision containing the index in
+ <page table> of the first page that belongs to
+ this revision. This has <revision count>+1
+ entries to terminate the last revision.
+ <page table> ... array of page headers. It has
+ <page table index>[<revision count>] entries.
+
+page table:
+
+ <offset> ... absolute position of the page contents within the
+ index
+ <entry count> ... number of offset entries in the page.
+ Must match <header>.<page size> unless this is
+ the last page for the respective revision.
+ <size> ... length in bytes of the on-disk page description.
+ Note that this is redundant with the <offset>.
+
+page:
+
+ <entry count> ... number of offset entries in the page.
+ Must match <header>.<page size> unless this is
+ the last page for the respective revision.
+ Redundant with <page table>.<entry count>
+ <offsets> ... array of absolute file positions within the rev /
+ pack file. This has <entry count> entries.
+
+
+Index on-disk format
+--------------------
+
+ index := "L2P-INDEX\n" header revisions pages offsets
+
+ header := u(<header>.<first revision>) \
+ u(<header>.<page size>) \
+ u(<header>.<revision count>) \
+ u(s(<header>.<page table>))
+
+ revisions := u( <header>.<page table index>[k+1]
+ - <header>.<page table index>[k]),
+ for k in 0 .. <header>.<revision count>-1
+
+ pages := u(<header>.<page table>[k].<size>) \
+ u(<header>.<page table>[k].<entry count>),
+ for k in 0 .. s(<header>.<page table>)-1
+
+ offsets := page(k),
+ for k in 0 .. s(<header>.<page table>)-1
+
+ page(k) := i(<header>.<page table>[k].<offsets>[0]) \
+ i( <header>.<page table>[k].<offsets>[l] \
+ - <header>.<page table>[k].<offsets>[l - 1]),
+ for l in 1 .. s(<header>.<page table>[k].<entry count>)-1
+
+ u(x) ... unsigned int x in 7b/8b encoding
+ i(x) ... signed int x in 7b/8b encoding
+ s(x) ... number of entries in array x
+
+
+Proto index file format
+-----------------------
+
+The index will be created from a "revision-less" proto index file
+containing (<offset><item_index>) pairs only.
+
+All mappings belonging to the same revision must be written in one go but
+there is no restriction on the order of those entries. To signify that
+a new revision begins, a (0, 0) mapping must be written. A (0, 0) entry
+at the beginning of the file is optional and will be ignored.
+
+ <bof> /* begin of proto index file for revision r and following */
+ (0, 0) /* mark start of revision r, optional for first rev */
+ (off, item)* /* zero to many mappings in random order */
+ (0, 0) /* mark start of revision r + 1 */
+ (off, item)* /* zero to many mappings in random order */
+ (0, 0) /* mark start of revision r + 2 */
+ (off, item)* /* zero to many mappings in random order */
+ ...
+ <eof> /* end of file. */
+
+All entries are pairs of 64 bit unsigned integers in little endian order.
+
+
+Phys-to-log index
+=================
+
+This index has to map offset -> (rev, item_index, type, len, checksum).
+
+
+Index data model
+----------------
+
+hierarchy:
+
+ header -> page -> item info
+
+ Logically, the index splits up index rev / pack file into pages of a
+ fixed size. That page size may differ from the FS's block size. The
+ index will describe each rev / pack file page with one index page.
+
+ page = header->pages[offset % page_size];
+ item info = binary_search(page.data, offset)
+
+ Note that the current implementation will not return any data if the
+ offset is does not match any actual item start. To simplify the lookup,
+ the last index page will have an "unused item" entry for the section
+ behind EOF. Holes aren't allowed as well, i.e. every byte of the rev /
+ pack is expected to be covered by the index.
+
+ Also, there may be items stretching across page borders or even over
+ multiple pages. The data model solves this issue by storing the item
+ descriptions as a "primary array" and then representing the pages as
+ ranges within that array. Thus multiple pages may reference the same
+ item description.
+
+header:
+
+ <first revision> ... first revision covered by this index
+ <file size> ... size of the rev / pack file in bytes
+ <page size> ... number of bytes in the rev / pack file covered by
+ each index page
+ <page count> ... number of pages
+ <offsets> ... array of page offsets, i.e. locations the page
+ data within the index.
+ This array has <page count> + 1 entries.
+
+page:
+
+ <entries> ... array of item descriptions, ordered by offset.
+ First and last entry may cross page boundaries.
+
+entry:
+
+ <offset> ... absolute position in the pack / rev file
+ <size> ... on-disk size of the item in the pack / rev file
+ <type> ... item type
+ <FNV checksum> ... modified 32 bit FNV-1a checksum of that section
+ of the pack / rev file (see below). 0 for empty
+ or zero-length items
+ <revision> ... revision that this item belongs to
+ <item_index> ... item_index within that revision
+
+
+Index on-disk format
+--------------------
+
+ index := "P2L-INDEX\n" header pages items
+
+ header := u(<header>.<first revision>) \
+ u(<header>.<file size>) \
+ u(<header>.<page size>) \
+ u(<header>.<page count>)
+
+ pages := u(<header>.<offsets>[k+1] - <header>.<offsets>[k]),
+ for k in 0 .. <header>.<page count> -1
+
+ items := u(<items in page k>[0].<offset>) \
+ u(<items in page k>[l].<size>) \
+ i(c(<items in page k>[l]) - c(<items of page k>[l-1])) \
+ i( <items in page k>[l].<revision>
+ - <items in page k>[l-1].<revision>), \
+ u(FNV checksum)
+ for l in 0 .. s(<items in page k>)-1,
+ for k in 0 .. <header>.<page count>-1
+
+ u(x) ... unsigned int x in 7b/8b encoding
+ i(x) ... signed int x in 7b/8b encoding
+ s(x) ... number of entries in collection x
+ c(x) ... compound function := x.<item_index> * 8 + x.<type>
+
+ Access to negative indexes gives a 0 value.
+
+ <Items in page k> are in strict ascending offset order. Items that
+ started after the begin of a given page and overlap with the next page
+ will not be stored in the start page. The runtime representation will
+ duplicate items overlapping page boundaries; the on-disk representation
+ will not. Thus, pages inside large items will have zero entries on disk.
+
+
+Proto index file format
+-----------------------
+
+The index will be created from a proto index file containing simple
+instances of svn_fs_fs__p2l_entry_t with the following element order:
+
+ item offset as uint64
+ item size as uint64
+ item type as uint64
+ modified FNV1a checksum as uint64
+ revision as uint64, with SVN_INVALID_REVNUM mapped to 0
+ and revisions >= 0 stored as rev+1
+ item index as uint64
+
+All values are stored in little endian order.
+
+Page table and header information, except start revision and page size,
+can easily be derived from that information.
+
+All entries must be written in strict offset order. Overlapping entries
+are not allowed; zero-length items are.
+
+In transactions, the final revision number may not be known when writing
+the proto index file (e.g. while still writing the proto rev file). Items
+with revision set to SVN_INVALID_REVNUM will therefore be automatically
+updated when creating the final index. This is possible in conjunction
+with rev files but not for pack files.
+
+
+FNV checksum
+------------
+
+FNV-1a can be found here: http://www.isthe.com/chongo/tech/comp/fnv/
+For performance reasons we use a modified version:
+
+* split the input byte stream [b0 .. bN] into 4 sub-streams of equal
+ length and up to 3 remnants:
+
+ [b0 b4 b8 ..], [b1 b5 b9 ..], [b2 b6 b10 ..], [b3 b7 b11 ..], [remnant]
+
+* calculate 32 bit FNV-1a checksums for the 4 substreams:
+
+ h0 = fnv_1a([b0 b4 b8 ..]), ..., h3 = fnv_1a([b3 b7 b11 ..])
+
+* combine the big endian representation of these checksums plus the
+ remnant of the original stream into a 12 to 15 byte long intermediate
+
+ [i0 .. iK], 12 <= K+1 <= 15
+
+* FNV checksum = fnv_1a([i0 .. iK]) in big endian representation
+
diff --git a/subversion/libsvn_fs_fs/temp_serializer.c b/subversion/libsvn_fs_fs/temp_serializer.c
index 0178143..4e7ae2d 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.c
+++ b/subversion/libsvn_fs_fs/temp_serializer.c
@@ -24,15 +24,18 @@
#include "svn_pools.h"
#include "svn_hash.h"
-
-#include "id.h"
+#include "svn_sorts.h"
#include "svn_fs.h"
#include "private/svn_fs_util.h"
+#include "private/svn_sorts_private.h"
#include "private/svn_temp_serializer.h"
#include "private/svn_subr_private.h"
+#include "id.h"
#include "temp_serializer.h"
+#include "low_level.h"
+#include "cached_data.h"
/* Utility to encode a signed NUMBER into a variable-length sequence of
* 8-bit chars in KEY_BUFFER and return the last writen position.
@@ -106,13 +109,12 @@ serialize_svn_string(svn_temp_serializer__context_t *context,
/* the "string" content may actually be arbitrary binary data.
* Thus, we cannot use svn_temp_serializer__add_string. */
- svn_temp_serializer__push(context,
- (const void * const *)&string->data,
- string->len + 1);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&string->data,
+ string->len + 1);
/* back to the caller's nesting level */
svn_temp_serializer__pop(context);
- svn_temp_serializer__pop(context);
}
/* Utility function to deserialize the STRING inside the BUFFER.
@@ -127,44 +129,6 @@ deserialize_svn_string(void *buffer, svn_string_t **string)
svn_temp_deserializer__resolve(*string, (void **)&(*string)->data);
}
-/* Utility function to serialize checkum CS within the given serialization
- * CONTEXT.
- */
-static void
-serialize_checksum(svn_temp_serializer__context_t *context,
- svn_checksum_t * const *cs)
-{
- const svn_checksum_t *checksum = *cs;
- if (checksum == NULL)
- return;
-
- svn_temp_serializer__push(context,
- (const void * const *)cs,
- sizeof(*checksum));
-
- /* The digest is arbitrary binary data.
- * Thus, we cannot use svn_temp_serializer__add_string. */
- svn_temp_serializer__push(context,
- (const void * const *)&checksum->digest,
- svn_checksum_size(checksum));
-
- /* return to the caller's nesting level */
- svn_temp_serializer__pop(context);
- svn_temp_serializer__pop(context);
-}
-
-/* Utility function to deserialize the checksum CS inside the BUFFER.
- */
-static void
-deserialize_checksum(void *buffer, svn_checksum_t **cs)
-{
- svn_temp_deserializer__resolve(buffer, (void **)cs);
- if (*cs == NULL)
- return;
-
- svn_temp_deserializer__resolve(*cs, (void **)&(*cs)->digest);
-}
-
/* Utility function to serialize the REPRESENTATION within the given
* serialization CONTEXT.
*/
@@ -177,48 +141,17 @@ serialize_representation(svn_temp_serializer__context_t *context,
return;
/* serialize the representation struct itself */
- svn_temp_serializer__push(context,
- (const void * const *)representation,
- sizeof(*rep));
-
- /* serialize sub-structures */
- serialize_checksum(context, &rep->md5_checksum);
- serialize_checksum(context, &rep->sha1_checksum);
-
- svn_temp_serializer__add_string(context, &rep->txn_id);
- svn_temp_serializer__add_string(context, &rep->uniquifier);
-
- /* return to the caller's nesting level */
- svn_temp_serializer__pop(context);
-}
-
-/* Utility function to deserialize the REPRESENTATIONS inside the BUFFER.
- */
-static void
-deserialize_representation(void *buffer,
- representation_t **representation)
-{
- representation_t *rep;
-
- /* fixup the reference to the representation itself */
- svn_temp_deserializer__resolve(buffer, (void **)representation);
- rep = *representation;
- if (rep == NULL)
- return;
-
- /* fixup of sub-structures */
- deserialize_checksum(rep, &rep->md5_checksum);
- deserialize_checksum(rep, &rep->sha1_checksum);
-
- svn_temp_deserializer__resolve(rep, (void **)&rep->txn_id);
- svn_temp_deserializer__resolve(rep, (void **)&rep->uniquifier);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)representation,
+ sizeof(*rep));
}
-/* auxilliary structure representing the content of a directory hash */
-typedef struct hash_data_t
+/* auxiliary structure representing the content of a directory array */
+typedef struct dir_data_t
{
- /* number of entries in the directory */
- apr_size_t count;
+ /* number of entries in the directory
+ * (it's int because the directory is an APR array) */
+ int count;
/* number of unused dir entry buckets in the index */
apr_size_t over_provision;
@@ -238,14 +171,7 @@ typedef struct hash_data_t
/* size of the serialized entries and don't be too wasteful
* (needed since the entries are no longer in sequence) */
apr_uint32_t *lengths;
-} hash_data_t;
-
-static int
-compare_dirent_id_names(const void *lhs, const void *rhs)
-{
- return strcmp((*(const svn_fs_dirent_t *const *)lhs)->name,
- (*(const svn_fs_dirent_t *const *)rhs)->name);
-}
+} dir_data_t;
/* Utility function to serialize the *ENTRY_P into a the given
* serialization CONTEXT. Return the serialized size of the
@@ -276,91 +202,85 @@ serialize_dir_entry(svn_temp_serializer__context_t *context,
* context to be returned. Allocation will be made form POOL.
*/
static svn_temp_serializer__context_t *
-serialize_dir(apr_hash_t *entries, apr_pool_t *pool)
+serialize_dir(apr_array_header_t *entries, apr_pool_t *pool)
{
- hash_data_t hash_data;
- apr_hash_index_t *hi;
- apr_size_t i = 0;
+ dir_data_t dir_data;
+ int i = 0;
svn_temp_serializer__context_t *context;
/* calculate sizes */
- apr_size_t count = apr_hash_count(entries);
+ int count = entries->nelts;
apr_size_t over_provision = 2 + count / 4;
apr_size_t entries_len = (count + over_provision) * sizeof(svn_fs_dirent_t*);
apr_size_t lengths_len = (count + over_provision) * sizeof(apr_uint32_t);
- /* copy the hash entries to an auxilliary struct of known layout */
- hash_data.count = count;
- hash_data.over_provision = over_provision;
- hash_data.operations = 0;
- hash_data.entries = apr_palloc(pool, entries_len);
- hash_data.lengths = apr_palloc(pool, lengths_len);
+ /* copy the hash entries to an auxiliary struct of known layout */
+ dir_data.count = count;
+ dir_data.over_provision = over_provision;
+ dir_data.operations = 0;
+ dir_data.entries = apr_palloc(pool, entries_len);
+ dir_data.lengths = apr_palloc(pool, lengths_len);
- for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi), ++i)
- hash_data.entries[i] = svn__apr_hash_index_val(hi);
-
- /* sort entry index by ID name */
- qsort(hash_data.entries,
- count,
- sizeof(*hash_data.entries),
- compare_dirent_id_names);
+ for (i = 0; i < count; ++i)
+ dir_data.entries[i] = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
/* Serialize that aux. structure into a new one. Also, provide a good
* estimate for the size of the buffer that we will need. */
- context = svn_temp_serializer__init(&hash_data,
- sizeof(hash_data),
+ context = svn_temp_serializer__init(&dir_data,
+ sizeof(dir_data),
50 + count * 200 + entries_len,
pool);
/* serialize entries references */
svn_temp_serializer__push(context,
- (const void * const *)&hash_data.entries,
+ (const void * const *)&dir_data.entries,
entries_len);
/* serialize the individual entries and their sub-structures */
for (i = 0; i < count; ++i)
serialize_dir_entry(context,
- &hash_data.entries[i],
- &hash_data.lengths[i]);
+ &dir_data.entries[i],
+ &dir_data.lengths[i]);
svn_temp_serializer__pop(context);
/* serialize entries references */
svn_temp_serializer__push(context,
- (const void * const *)&hash_data.lengths,
+ (const void * const *)&dir_data.lengths,
lengths_len);
return context;
}
-/* Utility function to reconstruct a dir entries hash from serialized data
- * in BUFFER and HASH_DATA. Allocation will be made form POOL.
+/* Utility function to reconstruct a dir entries array from serialized data
+ * in BUFFER and DIR_DATA. Allocation will be made form POOL.
*/
-static apr_hash_t *
-deserialize_dir(void *buffer, hash_data_t *hash_data, apr_pool_t *pool)
+static apr_array_header_t *
+deserialize_dir(void *buffer, dir_data_t *dir_data, apr_pool_t *pool)
{
- apr_hash_t *result = svn_hash__make(pool);
+ apr_array_header_t *result
+ = apr_array_make(pool, dir_data->count, sizeof(svn_fs_dirent_t *));
apr_size_t i;
apr_size_t count;
svn_fs_dirent_t *entry;
svn_fs_dirent_t **entries;
/* resolve the reference to the entries array */
- svn_temp_deserializer__resolve(buffer, (void **)&hash_data->entries);
- entries = hash_data->entries;
+ svn_temp_deserializer__resolve(buffer, (void **)&dir_data->entries);
+ entries = dir_data->entries;
/* fixup the references within each entry and add it to the hash */
- for (i = 0, count = hash_data->count; i < count; ++i)
+ for (i = 0, count = dir_data->count; i < count; ++i)
{
svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
- entry = hash_data->entries[i];
+ entry = dir_data->entries[i];
/* pointer fixup */
svn_temp_deserializer__resolve(entry, (void **)&entry->name);
svn_fs_fs__id_deserialize(entry, (svn_fs_id_t **)&entry->id);
/* add the entry to the hash */
- svn_hash_sets(result, entry->name, entry);
+ APR_ARRAY_PUSH(result, svn_fs_dirent_t *) = entry;
}
/* return the now complete hash */
@@ -413,14 +333,63 @@ svn_fs_fs__noderev_deserialize(void *buffer,
/* fixup of sub-structures */
svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->id);
svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->predecessor_id);
- deserialize_representation(noderev, &noderev->prop_rep);
- deserialize_representation(noderev, &noderev->data_rep);
+ svn_temp_deserializer__resolve(noderev, (void **)&noderev->prop_rep);
+ svn_temp_deserializer__resolve(noderev, (void **)&noderev->data_rep);
svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyfrom_path);
svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyroot_path);
svn_temp_deserializer__resolve(noderev, (void **)&noderev->created_path);
}
+svn_error_t *
+svn_fs_fs__serialize_raw_window(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__raw_cached_window_t *window = item;
+ svn_stringbuf_t *serialized;
+
+ /* initialize the serialization process and allocate a buffer large
+ * enough to do prevent re-allocations. */
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(window,
+ sizeof(*window),
+ sizeof(*window) + window->window.len + 16,
+ pool);
+
+ /* serialize the sub-structure(s) */
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&window->window.data,
+ window->window.len + 1);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *buffer = serialized->data;
+ *buffer_size = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_raw_window(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__raw_cached_window_t *window =
+ (svn_fs_fs__raw_cached_window_t *)buffer;
+
+ /* pointer reference fixup */
+ svn_temp_deserializer__resolve(window, (void **)&window->window.data);
+
+ /* done */
+ *item = buffer;
+
+ return SVN_NO_ERROR;
+}
+
/* Utility function to serialize COUNT svn_txdelta_op_t objects
* at OPS in the given serialization CONTEXT.
@@ -434,10 +403,9 @@ serialize_txdelta_ops(svn_temp_serializer__context_t *context,
return;
/* the ops form a contiguous chunk of memory with no further references */
- svn_temp_serializer__push(context,
- (const void * const *)ops,
- count * sizeof(svn_txdelta_op_t));
- svn_temp_serializer__pop(context);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)ops,
+ count * sizeof(svn_txdelta_op_t));
}
/* Utility function to serialize W in the given serialization CONTEXT.
@@ -551,7 +519,7 @@ svn_fs_fs__deserialize_manifest(void **out,
return SVN_NO_ERROR;
}
-/* Auxilliary structure representing the content of a properties hash.
+/* Auxiliary structure representing the content of a properties hash.
This structure is much easier to (de-)serialize than an apr_hash.
*/
typedef struct properties_data_t
@@ -621,7 +589,7 @@ svn_fs_fs__serialize_properties(void **data,
svn_stringbuf_t *serialized;
apr_size_t i;
- /* create our auxilliary data structure */
+ /* create our auxiliary data structure */
properties.count = apr_hash_count(hash);
properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
properties.values = apr_palloc(pool, sizeof(const char*) * properties.count);
@@ -629,8 +597,8 @@ svn_fs_fs__serialize_properties(void **data,
/* populate it with the hash entries */
for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
{
- properties.keys[i] = svn__apr_hash_index_key(hi);
- properties.values[i] = svn__apr_hash_index_val(hi);
+ properties.keys[i] = apr_hash_this_key(hi);
+ properties.values[i] = apr_hash_this_val(hi);
}
/* serialize it */
@@ -662,7 +630,7 @@ svn_fs_fs__deserialize_properties(void **out,
properties_data_t *properties = (properties_data_t *)data;
size_t i;
- /* de-serialize our auxilliary data structure */
+ /* de-serialize our auxiliary data structure */
svn_temp_deserializer__resolve(properties, (void**)&properties->keys);
svn_temp_deserializer__resolve(properties, (void**)&properties->values);
@@ -670,10 +638,10 @@ svn_fs_fs__deserialize_properties(void **out,
for (i = 0; i < properties->count; ++i)
{
apr_size_t len = properties->keys[i+1] - properties->keys[i] - 1;
- svn_temp_deserializer__resolve((void*)properties->keys,
+ svn_temp_deserializer__resolve(properties->keys,
(void**)&properties->keys[i]);
- deserialize_svn_string((void*)properties->values,
+ deserialize_svn_string(properties->values,
(svn_string_t **)&properties->values[i]);
apr_hash_set(hash,
@@ -785,7 +753,7 @@ return_serialized_dir_context(svn_temp_serializer__context_t *context,
*data = serialized->data;
*data_len = serialized->blocksize;
- ((hash_data_t *)serialized->data)->len = serialized->len;
+ ((dir_data_t *)serialized->data)->len = serialized->len;
return SVN_NO_ERROR;
}
@@ -796,7 +764,7 @@ svn_fs_fs__serialize_dir_entries(void **data,
void *in,
apr_pool_t *pool)
{
- apr_hash_t *dir = in;
+ apr_array_header_t *dir = in;
/* serialize the dir content into a new serialization context
* and return the serialized data */
@@ -812,10 +780,10 @@ svn_fs_fs__deserialize_dir_entries(void **out,
apr_pool_t *pool)
{
/* Copy the _full_ buffer as it also contains the sub-structures. */
- hash_data_t *hash_data = (hash_data_t *)data;
+ dir_data_t *dir_data = (dir_data_t *)data;
/* reconstruct the hash from the serialized data */
- *out = deserialize_dir(hash_data, hash_data, pool);
+ *out = deserialize_dir(dir_data, dir_data, pool);
return SVN_NO_ERROR;
}
@@ -888,22 +856,22 @@ svn_fs_fs__extract_dir_entry(void **out,
void *baton,
apr_pool_t *pool)
{
- const hash_data_t *hash_data = data;
+ const dir_data_t *dir_data = data;
const char* name = baton;
svn_boolean_t found;
/* resolve the reference to the entries array */
const svn_fs_dirent_t * const *entries =
- svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->entries);
+ svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->entries);
/* resolve the reference to the lengths array */
const apr_uint32_t *lengths =
- svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->lengths);
+ svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->lengths);
/* binary search for the desired entry by name */
apr_size_t pos = find_entry((svn_fs_dirent_t **)entries,
name,
- hash_data->count,
+ dir_data->count,
&found);
/* de-serialize that entry or return NULL, if no match has been found */
@@ -942,14 +910,33 @@ slowly_replace_dir_entry(void **data,
apr_pool_t *pool)
{
replace_baton_t *replace_baton = (replace_baton_t *)baton;
- hash_data_t *hash_data = (hash_data_t *)*data;
- apr_hash_t *dir;
+ dir_data_t *dir_data = (dir_data_t *)*data;
+ apr_array_header_t *dir;
+ int idx = -1;
+ svn_fs_dirent_t *entry;
SVN_ERR(svn_fs_fs__deserialize_dir_entries((void **)&dir,
*data,
- hash_data->len,
+ dir_data->len,
pool));
- svn_hash_sets(dir, replace_baton->name, replace_baton->new_entry);
+
+ entry = svn_fs_fs__find_dir_entry(dir, replace_baton->name, &idx);
+
+ /* Replacement or removal? */
+ if (replace_baton->new_entry)
+ {
+ /* Replace ENTRY with / insert the NEW_ENTRY */
+ if (entry)
+ APR_ARRAY_IDX(dir, idx, svn_fs_dirent_t *) = replace_baton->new_entry;
+ else
+ svn_sort__array_insert(dir, &replace_baton->new_entry, idx);
+ }
+ else
+ {
+ /* Remove the old ENTRY. */
+ if (entry)
+ svn_sort__array_delete(dir, idx, 1);
+ }
return svn_fs_fs__serialize_dir_entries(data, data_len, dir, pool);
}
@@ -961,7 +948,7 @@ svn_fs_fs__replace_dir_entry(void **data,
apr_pool_t *pool)
{
replace_baton_t *replace_baton = (replace_baton_t *)baton;
- hash_data_t *hash_data = (hash_data_t *)*data;
+ dir_data_t *dir_data = (dir_data_t *)*data;
svn_boolean_t found;
svn_fs_dirent_t **entries;
apr_uint32_t *lengths;
@@ -971,23 +958,23 @@ svn_fs_fs__replace_dir_entry(void **data,
svn_temp_serializer__context_t *context;
/* after quite a number of operations, let's re-pack everything.
- * This is to limit the number of vasted space as we cannot overwrite
+ * This is to limit the number of wasted space as we cannot overwrite
* existing data but must always append. */
- if (hash_data->operations > 2 + hash_data->count / 4)
+ if (dir_data->operations > 2 + dir_data->count / 4)
return slowly_replace_dir_entry(data, data_len, baton, pool);
/* resolve the reference to the entries array */
entries = (svn_fs_dirent_t **)
- svn_temp_deserializer__ptr((const char *)hash_data,
- (const void *const *)&hash_data->entries);
+ svn_temp_deserializer__ptr(dir_data,
+ (const void *const *)&dir_data->entries);
/* resolve the reference to the lengths array */
lengths = (apr_uint32_t *)
- svn_temp_deserializer__ptr((const char *)hash_data,
- (const void *const *)&hash_data->lengths);
+ svn_temp_deserializer__ptr(dir_data,
+ (const void *const *)&dir_data->lengths);
/* binary search for the desired entry by name */
- pos = find_entry(entries, replace_baton->name, hash_data->count, &found);
+ pos = find_entry(entries, replace_baton->name, dir_data->count, &found);
/* handle entry removal (if found at all) */
if (replace_baton->new_entry == NULL)
@@ -997,14 +984,14 @@ svn_fs_fs__replace_dir_entry(void **data,
/* remove reference to the entry from the index */
memmove(&entries[pos],
&entries[pos + 1],
- sizeof(entries[pos]) * (hash_data->count - pos));
+ sizeof(entries[pos]) * (dir_data->count - pos));
memmove(&lengths[pos],
&lengths[pos + 1],
- sizeof(lengths[pos]) * (hash_data->count - pos));
+ sizeof(lengths[pos]) * (dir_data->count - pos));
- hash_data->count--;
- hash_data->over_provision++;
- hash_data->operations++;
+ dir_data->count--;
+ dir_data->over_provision++;
+ dir_data->operations++;
}
return SVN_NO_ERROR;
@@ -1016,27 +1003,27 @@ svn_fs_fs__replace_dir_entry(void **data,
/* fallback to slow operation if there is no place left to insert an
* new entry to index. That will automatically give add some spare
* entries ("overprovision"). */
- if (hash_data->over_provision == 0)
+ if (dir_data->over_provision == 0)
return slowly_replace_dir_entry(data, data_len, baton, pool);
/* make entries[index] available for pointing to the new entry */
memmove(&entries[pos + 1],
&entries[pos],
- sizeof(entries[pos]) * (hash_data->count - pos));
+ sizeof(entries[pos]) * (dir_data->count - pos));
memmove(&lengths[pos + 1],
&lengths[pos],
- sizeof(lengths[pos]) * (hash_data->count - pos));
+ sizeof(lengths[pos]) * (dir_data->count - pos));
- hash_data->count++;
- hash_data->over_provision--;
- hash_data->operations++;
+ dir_data->count++;
+ dir_data->over_provision--;
+ dir_data->operations++;
}
/* de-serialize the new entry */
entries[pos] = replace_baton->new_entry;
- context = svn_temp_serializer__init_append(hash_data,
+ context = svn_temp_serializer__init_append(dir_data,
entries,
- hash_data->len,
+ dir_data->len,
*data_len,
pool);
serialize_dir_entry(context, &entries[pos], &length);
@@ -1050,15 +1037,45 @@ svn_fs_fs__replace_dir_entry(void **data,
* pointer may no longer point to the entry in that buffer. Therefore,
* re-map it again and store the length value after that. */
- hash_data = (hash_data_t *)*data;
+ dir_data = (dir_data_t *)*data;
lengths = (apr_uint32_t *)
- svn_temp_deserializer__ptr((const char *)hash_data,
- (const void *const *)&hash_data->lengths);
+ svn_temp_deserializer__ptr(dir_data,
+ (const void *const *)&dir_data->lengths);
lengths[pos] = length;
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_fs_fs__serialize_rep_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__rep_header_t *copy = apr_palloc(pool, sizeof(*copy));
+ *copy = *(svn_fs_fs__rep_header_t *)in;
+
+ *data_len = sizeof(svn_fs_fs__rep_header_t);
+ *data = copy;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_rep_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__rep_header_t *copy = apr_palloc(pool, sizeof(*copy));
+ SVN_ERR_ASSERT(data_len == sizeof(*copy));
+
+ *copy = *(svn_fs_fs__rep_header_t *)data;
+ *out = data;
+
+ return SVN_NO_ERROR;
+}
+
/* Utility function to serialize change CHANGE_P in the given serialization
* CONTEXT.
*/
@@ -1076,10 +1093,10 @@ serialize_change(svn_temp_serializer__context_t *context,
sizeof(*change));
/* serialize sub-structures */
- svn_fs_fs__id_serialize(context, &change->noderev_id);
+ svn_fs_fs__id_serialize(context, &change->info.node_rev_id);
- svn_temp_serializer__add_string(context, &change->path);
- svn_temp_serializer__add_string(context, &change->copyfrom_path);
+ svn_temp_serializer__add_string(context, &change->path.data);
+ svn_temp_serializer__add_string(context, &change->info.copyfrom_path);
/* return to the caller's nesting level */
svn_temp_serializer__pop(context);
@@ -1101,10 +1118,10 @@ deserialize_change(void *buffer, change_t **change_p)
return;
/* fix-up of sub-structures */
- svn_fs_fs__id_deserialize(change, (svn_fs_id_t **)&change->noderev_id);
+ svn_fs_fs__id_deserialize(change, (svn_fs_id_t **)&change->info.node_rev_id);
- svn_temp_deserializer__resolve(change, (void **)&change->path);
- svn_temp_deserializer__resolve(change, (void **)&change->copyfrom_path);
+ svn_temp_deserializer__resolve(change, (void **)&change->path.data);
+ svn_temp_deserializer__resolve(change, (void **)&change->info.copyfrom_path);
}
/* Auxiliary structure representing the content of a change_t array.
@@ -1131,18 +1148,15 @@ svn_fs_fs__serialize_changes(void **data,
svn_stringbuf_t *serialized;
int i;
- /* initialize our auxiliary data structure */
+ /* initialize our auxiliary data structure and link it to the
+ * array elements */
changes.count = array->nelts;
- changes.changes = apr_palloc(pool, sizeof(change_t*) * changes.count);
-
- /* populate it with the array elements */
- for (i = 0; i < changes.count; ++i)
- changes.changes[i] = APR_ARRAY_IDX(array, i, change_t*);
+ changes.changes = (change_t **)array->elts;
/* serialize it and all its elements */
context = svn_temp_serializer__init(&changes,
sizeof(changes),
- changes.count * 100,
+ changes.count * 250,
pool);
svn_temp_serializer__push(context,
@@ -1171,19 +1185,21 @@ svn_fs_fs__deserialize_changes(void **out,
{
int i;
changes_data_t *changes = (changes_data_t *)data;
- apr_array_header_t *array = apr_array_make(pool, changes->count,
- sizeof(change_t *));
+ apr_array_header_t *array = apr_array_make(pool, 0, sizeof(change_t *));
/* de-serialize our auxiliary data structure */
svn_temp_deserializer__resolve(changes, (void**)&changes->changes);
/* de-serialize each entry and add it to the array */
for (i = 0; i < changes->count; ++i)
- {
- deserialize_change((void*)changes->changes,
- (change_t **)&changes->changes[i]);
- APR_ARRAY_PUSH(array, change_t *) = changes->changes[i];
- }
+ deserialize_change(changes->changes,
+ (change_t **)&changes->changes[i]);
+
+ /* Use the changes buffer as the array's data buffer
+ * (DATA remains valid for at least as long as POOL). */
+ array->elts = (char *)changes->changes;
+ array->nelts = changes->count;
+ array->nalloc = changes->count;
/* done */
*out = array;
@@ -1252,7 +1268,7 @@ svn_fs_fs__serialize_mergeinfo(void **data,
i = 0;
for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
{
- svn_rangelist_t *ranges = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *ranges = apr_hash_this_val(hi);
for (k = 0; k < ranges->nelts; ++k, ++i)
merges.ranges[i] = *APR_ARRAY_IDX(ranges, k, svn_merge_range_t*);
}
@@ -1274,22 +1290,19 @@ svn_fs_fs__serialize_mergeinfo(void **data,
svn_temp_serializer__pop(context);
/* key lengths array */
- svn_temp_serializer__push(context,
- (const void * const *)&merges.key_lengths,
- merges.count * sizeof(*merges.key_lengths));
- svn_temp_serializer__pop(context);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&merges.key_lengths,
+ merges.count * sizeof(*merges.key_lengths));
/* range counts array */
- svn_temp_serializer__push(context,
- (const void * const *)&merges.range_counts,
- merges.count * sizeof(*merges.range_counts));
- svn_temp_serializer__pop(context);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&merges.range_counts,
+ merges.count * sizeof(*merges.range_counts));
/* ranges */
- svn_temp_serializer__push(context,
- (const void * const *)&merges.ranges,
- range_count * sizeof(*merges.ranges));
- svn_temp_serializer__pop(context);
+ svn_temp_serializer__add_leaf(context,
+ (const void * const *)&merges.ranges,
+ range_count * sizeof(*merges.ranges));
/* return the serialized result */
serialized = svn_temp_serializer__get(context);
@@ -1328,7 +1341,7 @@ svn_fs_fs__deserialize_mergeinfo(void **out,
for (k = 0; k < merges->range_counts[i]; ++k, ++n)
APR_ARRAY_PUSH(ranges, svn_merge_range_t*) = &merges->ranges[n];
- svn_temp_deserializer__resolve((void*)merges->keys,
+ svn_temp_deserializer__resolve(merges->keys,
(void**)&merges->keys[i]);
apr_hash_set(mergeinfo, merges->keys[i], merges->key_lengths[i], ranges);
}
diff --git a/subversion/libsvn_fs_fs/temp_serializer.h b/subversion/libsvn_fs_fs/temp_serializer.h
index 1009d63..4d14b01 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.h
+++ b/subversion/libsvn_fs_fs/temp_serializer.h
@@ -49,9 +49,42 @@ void
svn_fs_fs__noderev_deserialize(void *buffer,
node_revision_t **noderev_p);
+
+/**
+ * Adds position information to the the raw window data in WINDOW.
+ */
+typedef struct
+{
+ /* the (unprocessed) txdelta window byte sequence cached / to be cached */
+ svn_string_t window;
+
+ /* the offset within the representation right after reading the window */
+ apr_off_t end_offset;
+} svn_fs_fs__raw_cached_window_t;
+
+/**
+ * Implements #svn_cache__serialize_func_t for
+ * #svn_fs_fs__raw_cached_window_t.
+ */
+svn_error_t *
+svn_fs_fs__serialize_raw_window(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for
+ * #svn_fs_fs__raw_cached_window_t.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_raw_window(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool);
+
/**
* #svn_txdelta_window_t is not sufficient for caching the data it
- * represents because data read process needs auxilliary information.
+ * represents because data read process needs auxiliary information.
*/
typedef struct
{
@@ -159,7 +192,7 @@ svn_fs_fs__deserialize_node_revision(void **item,
apr_pool_t *pool);
/**
- * Implements #svn_cache__serialize_func_t for a directory contents hash
+ * Implements #svn_cache__serialize_func_t for a directory contents array
*/
svn_error_t *
svn_fs_fs__serialize_dir_entries(void **data,
@@ -168,7 +201,7 @@ svn_fs_fs__serialize_dir_entries(void **data,
apr_pool_t *pool);
/**
- * Implements #svn_cache__deserialize_func_t for a directory contents hash
+ * Implements #svn_cache__deserialize_func_t for a directory contents array
*/
svn_error_t *
svn_fs_fs__deserialize_dir_entries(void **out,
@@ -226,6 +259,24 @@ svn_fs_fs__replace_dir_entry(void **data,
apr_pool_t *pool);
/**
+ * Implements #svn_cache__serialize_func_t for a #svn_fs_fs__rep_header_t.
+ */
+svn_error_t *
+svn_fs_fs__serialize_rep_header(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a #svn_fs_fs__rep_header_t.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_rep_header(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
* Implements #svn_cache__serialize_func_t for an #apr_array_header_t of
* #change_t *.
*/
diff --git a/subversion/libsvn_fs_fs/transaction.c b/subversion/libsvn_fs_fs/transaction.c
new file mode 100644
index 0000000..bc93a5c
--- /dev/null
+++ b/subversion/libsvn_fs_fs/transaction.c
@@ -0,0 +1,3964 @@
+/* transaction.c --- transaction-related functions of FSFS
+ *
+ * ====================================================================
+ * 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 "transaction.h"
+
+#include <assert.h>
+#include <apr_sha1.h>
+
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+
+#include "fs_fs.h"
+#include "index.h"
+#include "tree.h"
+#include "util.h"
+#include "id.h"
+#include "low_level.h"
+#include "temp_serializer.h"
+#include "cached_data.h"
+#include "lock.h"
+#include "rep-cache.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Return the name of the sha1->rep mapping file in transaction TXN_ID
+ * within FS for the given SHA1 checksum. Use POOL for allocations.
+ */
+static APR_INLINE const char *
+path_txn_sha1(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ const unsigned char *sha1,
+ apr_pool_t *pool)
+{
+ svn_checksum_t checksum;
+ checksum.digest = sha1;
+ checksum.kind = svn_checksum_sha1;
+
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ svn_checksum_to_cstring(&checksum, pool),
+ pool);
+}
+
+static APR_INLINE const char *
+path_txn_changes(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_CHANGES, pool);
+}
+
+static APR_INLINE const char *
+path_txn_props(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_TXN_PROPS, pool);
+}
+
+static APR_INLINE const char *
+path_txn_props_final(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_TXN_PROPS_FINAL, pool);
+}
+
+static APR_INLINE const char *
+path_txn_next_ids(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_NEXT_IDS, pool);
+}
+
+
+/* The vtable associated with an open transaction object. */
+static txn_vtable_t txn_vtable = {
+ svn_fs_fs__commit_txn,
+ svn_fs_fs__abort_txn,
+ svn_fs_fs__txn_prop,
+ svn_fs_fs__txn_proplist,
+ svn_fs_fs__change_txn_prop,
+ svn_fs_fs__txn_root,
+ svn_fs_fs__change_txn_props
+};
+
+/* FSFS-specific data being attached to svn_fs_txn_t.
+ */
+typedef struct fs_txn_data_t
+{
+ /* Strongly typed representation of the TXN's ID member. */
+ svn_fs_fs__id_part_t txn_id;
+} fs_txn_data_t;
+
+const svn_fs_fs__id_part_t *
+svn_fs_fs__txn_get_id(svn_fs_txn_t *txn)
+{
+ fs_txn_data_t *ftd = txn->fsap_data;
+ return &ftd->txn_id;
+}
+
+/* Functions for working with shared transaction data. */
+
+/* Return the transaction object for transaction TXN_ID from the
+ transaction list of filesystem FS (which must already be locked via the
+ txn_list_lock mutex). If the transaction does not exist in the list,
+ then create a new transaction object and return it (if CREATE_NEW is
+ true) or return NULL (otherwise). */
+static fs_fs_shared_txn_data_t *
+get_shared_txn(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ svn_boolean_t create_new)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+ fs_fs_shared_txn_data_t *txn;
+
+ for (txn = ffsd->txns; txn; txn = txn->next)
+ if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
+ break;
+
+ if (txn || !create_new)
+ return txn;
+
+ /* Use the transaction object from the (single-object) freelist,
+ if one is available, or otherwise create a new object. */
+ if (ffsd->free_txn)
+ {
+ txn = ffsd->free_txn;
+ ffsd->free_txn = NULL;
+ }
+ else
+ {
+ apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
+ txn = apr_palloc(subpool, sizeof(*txn));
+ txn->pool = subpool;
+ }
+
+ txn->txn_id = *txn_id;
+ txn->being_written = FALSE;
+
+ /* Link this transaction into the head of the list. We will typically
+ be dealing with only one active transaction at a time, so it makes
+ sense for searches through the transaction list to look at the
+ newest transactions first. */
+ txn->next = ffsd->txns;
+ ffsd->txns = txn;
+
+ return txn;
+}
+
+/* Free the transaction object for transaction TXN_ID, and remove it
+ from the transaction list of filesystem FS (which must already be
+ locked via the txn_list_lock mutex). Do nothing if the transaction
+ does not exist. */
+static void
+free_shared_txn(svn_fs_t *fs, const svn_fs_fs__id_part_t *txn_id)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+ fs_fs_shared_txn_data_t *txn, *prev = NULL;
+
+ for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
+ if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
+ break;
+
+ if (!txn)
+ return;
+
+ if (prev)
+ prev->next = txn->next;
+ else
+ ffsd->txns = txn->next;
+
+ /* As we typically will be dealing with one transaction after another,
+ we will maintain a single-object free list so that we can hopefully
+ keep reusing the same transaction object. */
+ if (!ffsd->free_txn)
+ ffsd->free_txn = txn;
+ else
+ svn_pool_destroy(txn->pool);
+}
+
+
+/* Obtain a lock on the transaction list of filesystem FS, call BODY
+ with FS, BATON, and POOL, and then unlock the transaction list.
+ Return what BODY returned. */
+static svn_error_t *
+with_txnlist_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(svn_fs_t *fs,
+ const void *baton,
+ apr_pool_t *pool),
+ const void *baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+
+ SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
+ body(fs, baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
+ which see. */
+struct unlock_proto_rev_baton
+{
+ svn_fs_fs__id_part_t txn_id;
+ void *lockcookie;
+};
+
+/* Callback used in the implementation of unlock_proto_rev(). */
+static svn_error_t *
+unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const struct unlock_proto_rev_baton *b = baton;
+ apr_file_t *lockfile = b->lockcookie;
+ fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, FALSE);
+ apr_status_t apr_err;
+
+ if (!txn)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't unlock unknown transaction '%s'"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
+ if (!txn->being_written)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't unlock nonlocked transaction '%s'"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
+
+ apr_err = apr_file_unlock(lockfile);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err,
+ _("Can't unlock prototype revision lockfile for transaction '%s'"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
+ apr_err = apr_file_close(lockfile);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err,
+ _("Can't close prototype revision lockfile for transaction '%s'"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
+
+ txn->being_written = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the prototype revision file for transaction TXN_ID in filesystem
+ FS using cookie LOCKCOOKIE. The original prototype revision file must
+ have been closed _before_ calling this function.
+
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+unlock_proto_rev(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ void *lockcookie,
+ apr_pool_t *pool)
+{
+ struct unlock_proto_rev_baton b;
+
+ b.txn_id = *txn_id;
+ b.lockcookie = lockcookie;
+ return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
+}
+
+/* A structure used by get_writable_proto_rev() and
+ get_writable_proto_rev_body(), which see. */
+struct get_writable_proto_rev_baton
+{
+ void **lockcookie;
+ svn_fs_fs__id_part_t txn_id;
+};
+
+/* Callback used in the implementation of get_writable_proto_rev(). */
+static svn_error_t *
+get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const struct get_writable_proto_rev_baton *b = baton;
+ void **lockcookie = b->lockcookie;
+ fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, TRUE);
+
+ /* First, ensure that no thread in this process (including this one)
+ is currently writing to this transaction's proto-rev file. */
+ if (txn->being_written)
+ return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
+ _("Cannot write to the prototype revision file "
+ "of transaction '%s' because a previous "
+ "representation is currently being written by "
+ "this process"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
+
+
+ /* We know that no thread in this process is writing to the proto-rev
+ file, and by extension, that no thread in this process is holding a
+ lock on the prototype revision lock file. It is therefore safe
+ for us to attempt to lock this file, to see if any other process
+ is holding a lock. */
+
+ {
+ apr_file_t *lockfile;
+ apr_status_t apr_err;
+ const char *lockfile_path
+ = svn_fs_fs__path_txn_proto_rev_lock(fs, &b->txn_id, pool);
+
+ /* Open the proto-rev lockfile, creating it if necessary, as it may
+ not exist if the transaction dates from before the lockfiles were
+ introduced.
+
+ ### We'd also like to use something like svn_io_file_lock2(), but
+ that forces us to create a subpool just to be able to unlock
+ the file, which seems a waste. */
+ SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
+
+ apr_err = apr_file_lock(lockfile,
+ APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
+ if (apr_err)
+ {
+ svn_error_clear(svn_io_file_close(lockfile, pool));
+
+ if (APR_STATUS_IS_EAGAIN(apr_err))
+ return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
+ _("Cannot write to the prototype revision "
+ "file of transaction '%s' because a "
+ "previous representation is currently "
+ "being written by another process"),
+ svn_fs_fs__id_txn_unparse(&b->txn_id,
+ pool));
+
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get exclusive lock on file '%s'"),
+ svn_dirent_local_style(lockfile_path, pool));
+ }
+
+ *lockcookie = lockfile;
+ }
+
+ /* We've successfully locked the transaction; mark it as such. */
+ txn->being_written = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
+ of transaction TXN_ID in filesystem FS matches the proto-index file.
+ Trim any crash / failure related extra data from the proto-rev file.
+
+ If the prototype revision file is too short, we can't do much but bail out.
+
+ Perform all allocations in POOL. */
+static svn_error_t *
+auto_truncate_proto_rev(svn_fs_t *fs,
+ apr_file_t *proto_rev,
+ apr_off_t actual_length,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ /* Only relevant for newer FSFS formats. */
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ /* Determine file range covered by the proto-index so far. Note that
+ we always append to both file, i.e. the last index entry also
+ corresponds to the last addition in the rev file. */
+ const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
+ apr_file_t *file;
+ apr_off_t indexed_length;
+
+ SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_next_offset(&indexed_length, file,
+ pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ /* Handle mismatches. */
+ if (indexed_length < actual_length)
+ SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, pool));
+ else if (indexed_length > actual_length)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l proto index offset %s beyond proto"
+ "rev file size %s for TXN %s"),
+ apr_off_t_toa(pool, indexed_length),
+ apr_off_t_toa(pool, actual_length),
+ svn_fs_fs__id_txn_unparse(txn_id, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Get a handle to the prototype revision file for transaction TXN_ID in
+ filesystem FS, and lock it for writing. Return FILE, a file handle
+ positioned at the end of the file, and LOCKCOOKIE, a cookie that
+ should be passed to unlock_proto_rev() to unlock the file once FILE
+ has been closed.
+
+ If the prototype revision file is already locked, return error
+ SVN_ERR_FS_REP_BEING_WRITTEN.
+
+ Perform all allocations in POOL. */
+static svn_error_t *
+get_writable_proto_rev(apr_file_t **file,
+ void **lockcookie,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ struct get_writable_proto_rev_baton b;
+ svn_error_t *err;
+ apr_off_t end_offset = 0;
+
+ b.lockcookie = lockcookie;
+ b.txn_id = *txn_id;
+
+ SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
+
+ /* Now open the prototype revision file and seek to the end. */
+ err = svn_io_file_open(file,
+ svn_fs_fs__path_txn_proto_rev(fs, txn_id, pool),
+ APR_READ | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT,
+ pool);
+
+ /* You might expect that we could dispense with the following seek
+ and achieve the same thing by opening the file using APR_APPEND.
+ Unfortunately, APR's buffered file implementation unconditionally
+ places its initial file pointer at the start of the file (even for
+ files opened with APR_APPEND), so we need this seek to reconcile
+ the APR file pointer to the OS file pointer (since we need to be
+ able to read the current file position later). */
+ if (!err)
+ err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
+
+ /* We don't want unused sections (such as leftovers from failed delta
+ stream) in our file. If we use log addressing, we would need an
+ index entry for the unused section and that section would need to
+ be all NUL by convention. So, detect and fix those cases by truncating
+ the protorev file. */
+ if (!err)
+ err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
+
+ if (err)
+ {
+ err = svn_error_compose_create(
+ err,
+ unlock_proto_rev(fs, txn_id, *lockcookie, pool));
+
+ *lockcookie = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Callback used in the implementation of purge_shared_txn(). */
+static svn_error_t *
+purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const svn_fs_fs__id_part_t *txn_id = baton;
+
+ free_shared_txn(fs, txn_id);
+ svn_fs_fs__reset_txn_caches(fs);
+
+ return SVN_NO_ERROR;
+}
+
+/* Purge the shared data for transaction TXN_ID in filesystem FS.
+ Perform all allocations in POOL. */
+static svn_error_t *
+purge_shared_txn(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ svn_boolean_t fresh_txn_root,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *noderev_file;
+
+ noderev->is_fresh_txn_root = fresh_txn_root;
+
+ if (! svn_fs_fs__id_is_txn(id))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempted to write to non-transaction '%s'"),
+ svn_fs_fs__id_unparse(id, pool)->data);
+
+ SVN_ERR(svn_io_file_open(&noderev_file,
+ svn_fs_fs__path_txn_node_rev(fs, id, pool),
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
+ pool),
+ noderev, ffd->format,
+ svn_fs_fs__fs_supports_mergeinfo(fs),
+ pool));
+
+ SVN_ERR(svn_io_file_close(noderev_file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
+ * file in the respective transaction, if rep sharing has been enabled etc.
+ * Use SCATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+store_sha1_rep_mapping(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* if rep sharing has been enabled and the noderev has a data rep and
+ * its SHA-1 is known, store the rep struct under its SHA1. */
+ if ( ffd->rep_sharing_allowed
+ && noderev->data_rep
+ && noderev->data_rep->has_sha1)
+ {
+ apr_file_t *rep_file;
+ const char *file_name = path_txn_sha1(fs,
+ &noderev->data_rep->txn_id,
+ noderev->data_rep->sha1_digest,
+ scratch_pool);
+ svn_stringbuf_t *rep_string
+ = svn_fs_fs__unparse_representation(noderev->data_rep,
+ ffd->format,
+ (noderev->kind == svn_node_dir),
+ scratch_pool, scratch_pool);
+ SVN_ERR(svn_io_file_open(&rep_file, file_name,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
+
+ SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
+ rep_string->len, NULL, scratch_pool));
+
+ SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unparse_dir_entry(svn_fs_dirent_t *dirent,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ const char *val
+ = apr_psprintf(pool, "%s %s",
+ (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
+ : SVN_FS_FS__KIND_DIR,
+ svn_fs_fs__id_unparse(dirent->id, pool)->data);
+
+ SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
+ "V %" APR_SIZE_T_FMT "\n%s\n",
+ strlen(dirent->name), dirent->name,
+ strlen(val), val));
+ return SVN_NO_ERROR;
+}
+
+/* Write the directory given as array of dirent structs in ENTRIES to STREAM.
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+unparse_dir_entries(apr_array_header_t *entries,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_dirent_t *dirent;
+
+ svn_pool_clear(iterpool);
+ dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
+ SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
+ }
+
+ SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
+ */
+static svn_fs_path_change2_t *
+path_change_dup(const svn_fs_path_change2_t *source,
+ apr_pool_t *result_pool)
+{
+ svn_fs_path_change2_t *result = apr_pmemdup(result_pool, source,
+ sizeof(*source));
+ result->node_rev_id = svn_fs_fs__id_copy(source->node_rev_id, result_pool);
+ if (source->copyfrom_path)
+ result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
+
+ return result;
+}
+
+/* Merge the internal-use-only CHANGE into a hash of public-FS
+ svn_fs_path_change2_t CHANGED_PATHS, collapsing multiple changes into a
+ single summarical (is that real word?) change per path. DELETIONS is
+ also a path->svn_fs_path_change2_t hash and contains all the deletions
+ that got turned into a replacement. */
+static svn_error_t *
+fold_change(apr_hash_t *changed_paths,
+ apr_hash_t *deletions,
+ const change_t *change)
+{
+ apr_pool_t *pool = apr_hash_pool_get(changed_paths);
+ svn_fs_path_change2_t *old_change, *new_change;
+ const svn_string_t *path = &change->path;
+ const svn_fs_path_change2_t *info = &change->info;
+
+ if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
+ {
+ /* This path already exists in the hash, so we have to merge
+ this change into the already existing one. */
+
+ /* Sanity check: only allow NULL node revision ID in the
+ `reset' case. */
+ if ((! info->node_rev_id)
+ && (info->change_kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing required node revision ID"));
+
+ /* Sanity check: we should be talking about the same node
+ revision ID as our last change except where the last change
+ was a deletion. */
+ if (info->node_rev_id
+ && (! svn_fs_fs__id_eq(old_change->node_rev_id, info->node_rev_id))
+ && (old_change->change_kind != svn_fs_path_change_delete))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: new node revision ID "
+ "without delete"));
+
+ /* Sanity check: an add, replacement, or reset must be the first
+ thing to follow a deletion. */
+ if ((old_change->change_kind == svn_fs_path_change_delete)
+ && (! ((info->change_kind == svn_fs_path_change_replace)
+ || (info->change_kind == svn_fs_path_change_reset)
+ || (info->change_kind == svn_fs_path_change_add))))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: non-add change on deleted path"));
+
+ /* Sanity check: an add can't follow anything except
+ a delete or reset. */
+ if ((info->change_kind == svn_fs_path_change_add)
+ && (old_change->change_kind != svn_fs_path_change_delete)
+ && (old_change->change_kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: add change on preexisting path"));
+
+ /* Now, merge that change in. */
+ switch (info->change_kind)
+ {
+ case svn_fs_path_change_reset:
+ /* A reset here will simply remove the path change from the
+ hash. */
+ apr_hash_set(changed_paths, path->data, path->len, NULL);
+ break;
+
+ case svn_fs_path_change_delete:
+ if (old_change->change_kind == svn_fs_path_change_add)
+ {
+ /* If the path was introduced in this transaction via an
+ add, and we are deleting it, just remove the path
+ altogether. (The caller will delete any child paths.) */
+ apr_hash_set(changed_paths, path->data, path->len, NULL);
+ }
+ else if (old_change->change_kind == svn_fs_path_change_replace)
+ {
+ /* A deleting a 'replace' restore the original deletion. */
+ new_change = apr_hash_get(deletions, path->data, path->len);
+ SVN_ERR_ASSERT(new_change);
+ apr_hash_set(changed_paths, path->data, path->len, new_change);
+ }
+ else
+ {
+ /* A deletion overrules a previous change (modify). */
+ new_change = path_change_dup(info, pool);
+ apr_hash_set(changed_paths, path->data, path->len, new_change);
+ }
+ break;
+
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ /* An add at this point must be following a previous delete,
+ so treat it just like a replace. Remember the original
+ deletion such that we are able to delete this path again
+ (the replacement may have changed node kind and id). */
+ new_change = path_change_dup(info, pool);
+ new_change->change_kind = svn_fs_path_change_replace;
+
+ apr_hash_set(changed_paths, path->data, path->len, new_change);
+
+ /* Remember the original change.
+ * Make sure to allocate the hash key in a durable pool. */
+ apr_hash_set(deletions,
+ apr_pstrmemdup(apr_hash_pool_get(deletions),
+ path->data, path->len),
+ path->len, old_change);
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ /* If the new change modifies some attribute of the node, set
+ the corresponding flag, whether it already was set or not.
+ Note: We do not reset a flag to FALSE if a change is undone. */
+ if (info->text_mod)
+ old_change->text_mod = TRUE;
+ if (info->prop_mod)
+ old_change->prop_mod = TRUE;
+ if (info->mergeinfo_mod == svn_tristate_true)
+ old_change->mergeinfo_mod = svn_tristate_true;
+ break;
+ }
+ }
+ else
+ {
+ /* Add this path. The API makes no guarantees that this (new) key
+ will not be retained. Thus, we copy the key into the target pool
+ to ensure a proper lifetime. */
+ apr_hash_set(changed_paths,
+ apr_pstrmemdup(pool, path->data, path->len), path->len,
+ path_change_dup(info, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton type to be used with process_changes(). */
+typedef struct process_changes_baton_t
+{
+ /* Folded list of path changes. */
+ apr_hash_t *changed_paths;
+
+ /* Path changes that are deletions and have been turned into
+ replacements. If those replacements get deleted again, this
+ container contains the record that we have to revert to. */
+ apr_hash_t *deletions;
+} process_changes_baton_t;
+
+/* An implementation of svn_fs_fs__change_receiver_t.
+ Examine all the changed path entries in CHANGES and store them in
+ *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
+ data. Do all allocations in POOL. */
+static svn_error_t *
+process_changes(void *baton_p,
+ change_t *change,
+ apr_pool_t *scratch_pool)
+{
+ process_changes_baton_t *baton = baton_p;
+
+ SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
+
+ /* Now, if our change was a deletion or replacement, we have to
+ blow away any changes thus far on paths that are (or, were)
+ children of this path.
+ ### i won't bother with another iteration pool here -- at
+ most we talking about a few extra dups of paths into what
+ is already a temporary subpool.
+ */
+
+ if ((change->info.change_kind == svn_fs_path_change_delete)
+ || (change->info.change_kind == svn_fs_path_change_replace))
+ {
+ apr_hash_index_t *hi;
+
+ /* a potential child path must contain at least 2 more chars
+ (the path separator plus at least one char for the name).
+ Also, we should not assume that all paths have been normalized
+ i.e. some might have trailing path separators.
+ */
+ apr_ssize_t path_len = change->path.len;
+ apr_ssize_t min_child_len = path_len == 0
+ ? 1
+ : change->path.data[path_len-1] == '/'
+ ? path_len + 1
+ : path_len + 2;
+
+ /* CAUTION: This is the inner loop of an O(n^2) algorithm.
+ The number of changes to process may be >> 1000.
+ Therefore, keep the inner loop as tight as possible.
+ */
+ for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ /* KEY is the path. */
+ const void *path;
+ apr_ssize_t klen;
+ svn_fs_path_change2_t *old_change;
+ apr_hash_this(hi, &path, &klen, (void**)&old_change);
+
+ /* If we come across a child of our path, remove it.
+ Call svn_fspath__skip_ancestor only if there is a chance that
+ this is actually a sub-path.
+ */
+ if (klen >= min_child_len)
+ {
+ const char *child;
+
+ child = svn_fspath__skip_ancestor(change->path.data, path);
+ if (child && child[0] != '\0')
+ {
+ apr_hash_set(baton->changed_paths, path, klen, NULL);
+ }
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ apr_hash_t *changed_paths = apr_hash_make(pool);
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ process_changes_baton_t baton;
+
+ baton.changed_paths = changed_paths;
+ baton.deletions = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_io_file_open(&file,
+ path_txn_changes(fs, txn_id, scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_fs__read_changes_incrementally(
+ svn_stream_from_aprfile2(file, TRUE,
+ scratch_pool),
+ process_changes, &baton,
+ scratch_pool));
+ svn_pool_destroy(scratch_pool);
+
+ *changed_paths_p = changed_paths;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changed_paths;
+ apr_array_header_t *changes;
+ int i;
+
+ SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool));
+
+ changed_paths = svn_hash__make(pool);
+ for (i = 0; i < changes->nelts; ++i)
+ {
+ change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
+ apr_hash_set(changed_paths, change->path.data, change->path.len,
+ &change->info);
+ }
+
+ *changed_paths_p = changed_paths;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy a revision node-rev SRC into the current transaction TXN_ID in
+ the filesystem FS. This is only used to create the root of a transaction.
+ Allocations are from POOL. */
+static svn_error_t *
+create_new_txn_noderev_from_rev(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ svn_fs_id_t *src,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ const svn_fs_fs__id_part_t *node_id, *copy_id;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool, pool));
+
+ if (svn_fs_fs__id_is_txn(noderev->id))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Copying from transactions not allowed"));
+
+ noderev->predecessor_id = noderev->id;
+ noderev->predecessor_count++;
+ noderev->copyfrom_path = NULL;
+ noderev->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* For the transaction root, the copyroot never changes. */
+
+ node_id = svn_fs_fs__id_node_id(noderev->id);
+ copy_id = svn_fs_fs__id_copy_id(noderev->id);
+ noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
+
+ return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
+}
+
+/* A structure used by get_and_increment_txn_key_body(). */
+struct get_and_increment_txn_key_baton {
+ svn_fs_t *fs;
+ apr_uint64_t txn_number;
+ apr_pool_t *pool;
+};
+
+/* Callback used in the implementation of create_txn_dir(). This gets
+ the current base 36 value in PATH_TXN_CURRENT and increments it.
+ It returns the original value by the baton. */
+static svn_error_t *
+get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
+{
+ struct get_and_increment_txn_key_baton *cb = baton;
+ const char *txn_current_filename
+ = svn_fs_fs__path_txn_current(cb->fs, pool);
+ char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */
+ apr_size_t line_length;
+
+ svn_stringbuf_t *buf;
+ SVN_ERR(svn_fs_fs__read_content(&buf, txn_current_filename, cb->pool));
+
+ /* assign the current txn counter value to our result */
+ cb->txn_number = svn__base36toui64(NULL, buf->data);
+
+ /* remove trailing newlines */
+ line_length = svn__ui64tobase36(new_id_str, cb->txn_number+1);
+ new_id_str[line_length] = '\n';
+
+ /* Increment the key and add a trailing \n to the string so the
+ txn-current file has a newline in it. */
+ SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str,
+ line_length + 1,
+ txn_current_filename /* copy_perms path */,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a unique directory for a transaction in FS based on revision REV.
+ Return the ID for this transaction in *ID_P and *TXN_ID. Use a sequence
+ value in the transaction ID to prevent reuse of transaction IDs. */
+static svn_error_t *
+create_txn_dir(const char **id_p,
+ svn_fs_fs__id_part_t *txn_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct get_and_increment_txn_key_baton cb;
+ const char *txn_dir;
+
+ /* Get the current transaction sequence value, which is a base-36
+ number, from the txn-current file, and write an
+ incremented value back out to the file. Place the revision
+ number the transaction is based off into the transaction id. */
+ cb.pool = pool;
+ cb.fs = fs;
+ SVN_ERR(svn_fs_fs__with_txn_current_lock(fs,
+ get_and_increment_txn_key_body,
+ &cb,
+ pool));
+ txn_id->revision = rev;
+ txn_id->number = cb.txn_number;
+
+ *id_p = svn_fs_fs__id_txn_unparse(txn_id, pool);
+ txn_dir = svn_fs_fs__path_txn_dir(fs, txn_id, pool);
+
+ return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
+}
+
+/* Create a unique directory for a transaction in FS based on revision
+ REV. Return the ID for this transaction in *ID_P and *TXN_ID. This
+ implementation is used in svn 1.4 and earlier repositories and is
+ kept in 1.5 and greater to support the --pre-1.4-compatible and
+ --pre-1.5-compatible repository creation options. Reused
+ transaction IDs are possible with this implementation. */
+static svn_error_t *
+create_txn_dir_pre_1_5(const char **id_p,
+ svn_fs_fs__id_part_t *txn_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ unsigned int i;
+ apr_pool_t *subpool;
+ const char *unique_path, *prefix;
+
+ /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
+ prefix = svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool),
+ apr_psprintf(pool, "%ld", rev), pool);
+
+ subpool = svn_pool_create(pool);
+ for (i = 1; i <= 99999; i++)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+ unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
+ err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
+ if (! err)
+ {
+ /* We succeeded. Return the basename minus the ".txn" extension. */
+ const char *name = svn_dirent_basename(unique_path, subpool);
+ *id_p = apr_pstrndup(pool, name,
+ strlen(name) - strlen(PATH_EXT_TXN));
+ SVN_ERR(svn_fs_fs__id_txn_parse(txn_id, *id_p));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ if (! APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ }
+
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to create transaction directory "
+ "in '%s' for revision %ld"),
+ svn_dirent_local_style(fs->path, pool),
+ rev);
+}
+
+svn_error_t *
+svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_txn_t *txn;
+ fs_txn_data_t *ftd;
+ svn_fs_id_t *root_id;
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+ ftd = apr_pcalloc(pool, sizeof(*ftd));
+
+ /* Get the txn_id. */
+ if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, rev, pool));
+ else
+ SVN_ERR(create_txn_dir_pre_1_5(&txn->id, &ftd->txn_id, fs, rev, pool));
+
+ txn->fs = fs;
+ txn->base_rev = rev;
+
+ txn->vtable = &txn_vtable;
+ txn->fsap_data = ftd;
+ *txn_p = txn;
+
+ /* Create a new root node for this transaction. */
+ SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool, pool));
+ SVN_ERR(create_new_txn_noderev_from_rev(fs, &ftd->txn_id, root_id, pool));
+
+ /* Create an empty rev file. */
+ SVN_ERR(svn_io_file_create_empty(
+ svn_fs_fs__path_txn_proto_rev(fs, &ftd->txn_id, pool),
+ pool));
+
+ /* Create an empty rev-lock file. */
+ SVN_ERR(svn_io_file_create_empty(
+ svn_fs_fs__path_txn_proto_rev_lock(fs, &ftd->txn_id, pool),
+ pool));
+
+ /* Create an empty changes file. */
+ SVN_ERR(svn_io_file_create_empty(path_txn_changes(fs, &ftd->txn_id, pool),
+ pool));
+
+ /* Create the next-ids file. */
+ return svn_io_file_create(path_txn_next_ids(fs, &ftd->txn_id, pool),
+ "0 0\n", pool);
+}
+
+/* Store the property list for transaction TXN_ID in PROPLIST.
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+get_txn_proplist(apr_hash_t *proplist,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ svn_error_t *err;
+
+ /* Check for issue #3696. (When we find and fix the cause, we can change
+ * this to an assertion.) */
+ if (!txn_id || !svn_fs_fs__id_txn_used(txn_id))
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Internal error: a null transaction id was "
+ "passed to get_txn_proplist()"));
+
+ /* Open the transaction properties file. */
+ SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
+ pool, pool));
+
+ /* Read in the property list. */
+ err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
+ if (err)
+ {
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_quick_wrapf(err,
+ _("malformed property list in transaction '%s'"),
+ path_txn_props(fs, txn_id, pool));
+ }
+
+ return svn_stream_close(stream);
+}
+
+/* Save the property list PROPS as the revprops for transaction TXN_ID
+ in FS. Perform temporary allocations in POOL. */
+static svn_error_t *
+set_txn_proplist(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_hash_t *props,
+ svn_boolean_t final,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf;
+ svn_stream_t *stream;
+
+ /* Write out the new file contents to BUF. */
+ buf = svn_stringbuf_create_ensure(1024, pool);
+ stream = svn_stream_from_stringbuf(buf, pool);
+ SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Open the transaction properties file and write new contents to it. */
+ SVN_ERR(svn_io_write_atomic((final
+ ? path_txn_props_final(fs, txn_id, pool)
+ : path_txn_props(fs, txn_id, pool)),
+ buf->data, buf->len,
+ NULL /* copy_perms_path */, pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+ svn_prop_t prop;
+
+ prop.name = name;
+ prop.value = value;
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+
+ return svn_fs_fs__change_txn_props(txn, props, pool);
+}
+
+svn_error_t *
+svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool)
+{
+ fs_txn_data_t *ftd = txn->fsap_data;
+ apr_hash_t *txn_prop = apr_hash_make(pool);
+ int i;
+ svn_error_t *err;
+
+ err = get_txn_proplist(txn_prop, txn->fs, &ftd->txn_id, pool);
+ /* Here - and here only - we need to deal with the possibility that the
+ transaction property file doesn't yet exist. The rest of the
+ implementation assumes that the file exists, but we're called to set the
+ initial transaction properties as the transaction is being created. */
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
+ svn_error_clear(err);
+ else if (err)
+ return svn_error_trace(err);
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
+ && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
+ svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
+ svn_string_create("1", pool));
+
+ svn_hash_sets(txn_prop, prop->name, prop->value);
+ }
+
+ /* Create a new version of the file and write out the new props. */
+ /* Open the transaction properties file. */
+ SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ node_revision_t *noderev;
+ svn_fs_id_t *root_id;
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+ root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool));
+
+ txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
+ txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
+ txn->copies = NULL;
+
+ *txn_p = txn;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the currently available next node_id NODE_ID and copy_id
+ COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
+ used both for creating new unique nodes for the given transaction, as
+ well as uniquifying representations. Perform temporary allocations in
+ POOL. */
+static svn_error_t *
+write_next_ids(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_uint64_t node_id,
+ apr_uint64_t copy_id,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
+ char *p = buffer;
+
+ p += svn__ui64tobase36(p, node_id);
+ *(p++) = ' ';
+ p += svn__ui64tobase36(p, copy_id);
+ *(p++) = '\n';
+ *(p++) = '\0';
+
+ SVN_ERR(svn_io_file_open(&file,
+ path_txn_next_ids(fs, txn_id, pool),
+ APR_WRITE | APR_TRUNCATE,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool));
+ return svn_io_file_close(file, pool);
+}
+
+/* Find out what the next unique node-id and copy-id are for
+ transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
+ and *COPY_ID. The next node-id is used both for creating new unique
+ nodes for the given transaction, as well as uniquifying representations.
+ Perform all allocations in POOL. */
+static svn_error_t *
+read_next_ids(apr_uint64_t *node_id,
+ apr_uint64_t *copy_id,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf;
+ const char *str;
+ SVN_ERR(svn_fs_fs__read_content(&buf,
+ path_txn_next_ids(fs, txn_id, pool),
+ pool));
+
+ /* Parse this into two separate strings. */
+
+ str = buf->data;
+ *node_id = svn__base36toui64(&str, str);
+ if (*str != ' ')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("next-id file corrupt"));
+
+ ++str;
+ *copy_id = svn__base36toui64(&str, str);
+ if (*str != '\n')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("next-id file corrupt"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Get a new and unique to this transaction node-id for transaction
+ TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
+ Node-ids are guaranteed to be unique to this transction, but may
+ not necessarily be sequential. Perform all allocations in POOL. */
+static svn_error_t *
+get_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ apr_uint64_t node_id, copy_id;
+
+ /* First read in the current next-ids file. */
+ SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
+
+ node_id_p->revision = SVN_INVALID_REVNUM;
+ node_id_p->number = node_id;
+
+ SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ apr_uint64_t node_id, copy_id;
+
+ /* First read in the current next-ids file. */
+ SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
+
+ /* this is an in-txn ID now */
+ copy_id_p->revision = SVN_INVALID_REVNUM;
+ copy_id_p->number = copy_id;
+
+ /* Update the ID counter file */
+ SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__id_part_t node_id;
+ const svn_fs_id_t *id;
+
+ /* Get a new node-id for this node. */
+ SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
+
+ id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool);
+
+ noderev->id = id;
+
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
+
+ *id_p = id;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__purge_txn(svn_fs_t *fs,
+ const char *txn_id_str,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_fs__id_part_t txn_id;
+ SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str));
+
+ /* Remove the shared transaction object associated with this transaction. */
+ SVN_ERR(purge_shared_txn(fs, &txn_id, pool));
+ /* Remove the directory associated with this transaction. */
+ SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
+ FALSE, NULL, NULL, pool));
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ {
+ /* Delete protorev and its lock, which aren't in the txn
+ directory. It's OK if they don't exist (for example, if this
+ is post-commit and the proto-rev has been moved into
+ place). */
+ SVN_ERR(svn_io_remove_file2(
+ svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool),
+ TRUE, pool));
+ SVN_ERR(svn_io_remove_file2(
+ svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool),
+ TRUE, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
+
+ /* Now, purge the transaction. */
+ SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
+ apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
+ txn->id));
+
+ return SVN_NO_ERROR;
+}
+
+/* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID
+ * in FS. Allocate the uniquifier in POOL.
+ */
+static svn_error_t *
+set_uniquifier(svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__id_part_t temp;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ {
+ SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool));
+ rep->uniquifier.noderev_txn_id = rep->txn_id;
+ rep->uniquifier.number = temp.number;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE if the TXN_ID member of REP is in use.
+ */
+static svn_boolean_t
+is_txn_rep(const representation_t *rep)
+{
+ return svn_fs_fs__id_txn_used(&rep->txn_id);
+}
+
+/* Mark the TXN_ID member of REP as "unused".
+ */
+static void
+reset_txn_in_rep(representation_t *rep)
+{
+ svn_fs_fs__id_txn_reset(&rep->txn_id);
+}
+
+svn_error_t *
+svn_fs_fs__set_entry(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ node_revision_t *parent_noderev,
+ const char *name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ apr_pool_t *pool)
+{
+ representation_t *rep = parent_noderev->data_rep;
+ const char *filename
+ = svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
+ apr_file_t *file;
+ svn_stream_t *out;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (!rep || !is_txn_rep(rep))
+ {
+ apr_array_header_t *entries;
+
+ /* Before we can modify the directory, we need to dump its old
+ contents into a mutable representation file. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
+ subpool, subpool));
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(unparse_dir_entries(entries, out, subpool));
+
+ svn_pool_clear(subpool);
+
+ /* Mark the node-rev's data rep as mutable. */
+ rep = apr_pcalloc(pool, sizeof(*rep));
+ rep->revision = SVN_INVALID_REVNUM;
+ rep->txn_id = *txn_id;
+ SVN_ERR(set_uniquifier(fs, rep, pool));
+ parent_noderev->data_rep = rep;
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
+ parent_noderev, FALSE, pool));
+ }
+ else
+ {
+ /* The directory rep is already mutable, so just open it for append. */
+ SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
+ APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ }
+
+ /* if we have a directory cache for this transaction, update it */
+ if (ffd->txn_dir_cache)
+ {
+ /* build parameters: (name, new entry) pair */
+ const char *key =
+ svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
+ replace_baton_t baton;
+
+ baton.name = name;
+ baton.new_entry = NULL;
+
+ if (id)
+ {
+ baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
+ baton.new_entry->name = name;
+ baton.new_entry->kind = kind;
+ baton.new_entry->id = id;
+ }
+
+ /* actually update the cached directory (if cached) */
+ SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
+ svn_fs_fs__replace_dir_entry, &baton,
+ subpool));
+ }
+ svn_pool_clear(subpool);
+
+ /* Append an incremental hash entry for the entry change. */
+ if (id)
+ {
+ svn_fs_dirent_t entry;
+ entry.name = name;
+ entry.id = id;
+ entry.kind = kind;
+
+ SVN_ERR(unparse_dir_entry(&entry, out, subpool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
+ strlen(name), name));
+ }
+
+ SVN_ERR(svn_io_file_close(file, subpool));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__add_change(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ const char *path,
+ const svn_fs_id_t *id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ svn_boolean_t mergeinfo_mod,
+ svn_node_kind_t node_kind,
+ svn_revnum_t copyfrom_rev,
+ const char *copyfrom_path,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ svn_fs_path_change2_t *change;
+ apr_hash_t *changes = apr_hash_make(pool);
+
+ /* Not using APR_BUFFERED to append change in one atomic write operation. */
+ SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
+ APR_APPEND | APR_WRITE | APR_CREATE,
+ APR_OS_DEFAULT, pool));
+
+ change = svn_fs__path_change_create_internal(id, change_kind, pool);
+ change->text_mod = text_mod;
+ change->prop_mod = prop_mod;
+ change->mergeinfo_mod = mergeinfo_mod
+ ? svn_tristate_true
+ : svn_tristate_false;
+ change->node_kind = node_kind;
+ change->copyfrom_known = TRUE;
+ change->copyfrom_rev = copyfrom_rev;
+ if (copyfrom_path)
+ change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+
+ svn_hash_sets(changes, path, change);
+ SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
+ fs, changes, FALSE, pool));
+
+ return svn_io_file_close(file, pool);
+}
+
+/* If the transaction TXN_ID in FS uses logical addressing, store the
+ * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+store_l2p_index_entry(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_off_t offset,
+ apr_uint64_t item_index,
+ apr_pool_t *pool)
+{
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
+ apr_file_t *file;
+ SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
+ item_index, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
+ * in the phys-to-log proto index file of transaction TXN_ID.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+store_p2l_index_entry(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
+ apr_file_t *file;
+ SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID
+ * of file system FS and return it in *ITEM_INDEX. For old formats, it
+ * will simply return the offset as item index; in new formats, it will
+ * increment the txn's item index counter file and store the mapping in
+ * the proto index file. Use POOL for allocations.
+ */
+static svn_error_t *
+allocate_item_index(apr_uint64_t *item_index,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_off_t my_offset,
+ apr_pool_t *pool)
+{
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ apr_file_t *file;
+ char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
+ svn_boolean_t eof = FALSE;
+ apr_size_t to_write;
+ apr_size_t read;
+ apr_off_t offset = 0;
+
+ /* read number, increment it and write it back to disk */
+ SVN_ERR(svn_io_file_open(&file,
+ svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
+ APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
+ &read, &eof, pool));
+ if (read)
+ SVN_ERR(svn_cstring_atoui64(item_index, buffer));
+ else
+ *item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
+
+ to_write = svn__ui64toa(buffer, *item_index + 1);
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
+ SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ /* write log-to-phys index */
+ SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool));
+ }
+ else
+ {
+ *item_index = (apr_uint64_t)my_offset;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton used by fnv1a_write_handler to calculate the FNV checksum
+ * before passing the data on to the INNER_STREAM.
+ */
+typedef struct fnv1a_stream_baton_t
+{
+ svn_stream_t *inner_stream;
+ svn_checksum_ctx_t *context;
+} fnv1a_stream_baton_t;
+
+/* Implement svn_write_fn_t.
+ * Update checksum and pass data on to inner stream.
+ */
+static svn_error_t *
+fnv1a_write_handler(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ fnv1a_stream_baton_t *b = baton;
+
+ SVN_ERR(svn_checksum_update(b->context, data, *len));
+ SVN_ERR(svn_stream_write(b->inner_stream, data, len));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a stream that calculates a FNV checksum in *CONTEXT
+ * over all data written to the stream and passes that data on
+ * to INNER_STREAM. Allocate objects in POOL.
+ */
+static svn_stream_t *
+fnv1a_wrap_stream(svn_checksum_ctx_t **context,
+ svn_stream_t *inner_stream,
+ apr_pool_t *pool)
+{
+ svn_stream_t *outer_stream;
+
+ fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->inner_stream = inner_stream;
+ baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
+ *context = baton->context;
+
+ outer_stream = svn_stream_create(baton, pool);
+ svn_stream_set_write(outer_stream, fnv1a_write_handler);
+
+ return outer_stream;
+}
+
+/* Set *DIGEST to the FNV checksum calculated in CONTEXT.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+fnv1a_checksum_finalize(apr_uint32_t *digest,
+ svn_checksum_ctx_t *context,
+ apr_pool_t *scratch_pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
+ SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4);
+ *digest = ntohl(*(const apr_uint32_t *)(checksum->digest));
+
+ return SVN_NO_ERROR;
+}
+
+/* This baton is used by the representation writing streams. It keeps
+ track of the checksum information as well as the total size of the
+ representation so far. */
+struct rep_write_baton
+{
+ /* The FS we are writing to. */
+ svn_fs_t *fs;
+
+ /* Actual file to which we are writing. */
+ svn_stream_t *rep_stream;
+
+ /* A stream from the delta combiner. Data written here gets
+ deltified, then eventually written to rep_stream. */
+ svn_stream_t *delta_stream;
+
+ /* Where is this representation header stored. */
+ apr_off_t rep_offset;
+
+ /* Start of the actual data. */
+ apr_off_t delta_start;
+
+ /* How many bytes have been written to this rep already. */
+ svn_filesize_t rep_size;
+
+ /* The node revision for which we're writing out info. */
+ node_revision_t *noderev;
+
+ /* Actual output file. */
+ apr_file_t *file;
+ /* Lock 'cookie' used to unlock the output file once we've finished
+ writing to it. */
+ void *lockcookie;
+
+ svn_checksum_ctx_t *md5_checksum_ctx;
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+
+ /* calculate a modified FNV-1a checksum of the on-disk representation */
+ svn_checksum_ctx_t *fnv1a_checksum_ctx;
+
+ /* Local / scratch pool, available for temporary allocations. */
+ apr_pool_t *scratch_pool;
+
+ /* Outer / result pool. */
+ apr_pool_t *result_pool;
+};
+
+/* Handler for the write method of the representation writable stream.
+ BATON is a rep_write_baton, DATA is the data to write, and *LEN is
+ the length of this data. */
+static svn_error_t *
+rep_write_contents(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct rep_write_baton *b = baton;
+
+ SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
+ SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
+ b->rep_size += *len;
+
+ /* If we are writing a delta, use that stream. */
+ if (b->delta_stream)
+ return svn_stream_write(b->delta_stream, data, len);
+ else
+ return svn_stream_write(b->rep_stream, data, len);
+}
+
+/* Set *SPANNED to the number of shards touched when walking WALK steps on
+ * NODEREV's predecessor chain in FS. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+shards_spanned(int *spanned,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ int walk,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1;
+ apr_pool_t *iterpool;
+
+ int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
+ svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
+ iterpool = svn_pool_create(pool);
+ while (walk-- && noderev->predecessor_count)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs,
+ noderev->predecessor_id, pool,
+ iterpool));
+ shard = svn_fs_fs__id_rev(noderev->id) / shard_size;
+ if (shard != last_shard)
+ {
+ ++count;
+ last_shard = shard;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *spanned = count;
+ return SVN_NO_ERROR;
+}
+
+/* Given a node-revision NODEREV in filesystem FS, return the
+ representation in *REP to use as the base for a text representation
+ delta if PROPS is FALSE. If PROPS has been set, a suitable props
+ base representation will be returned. Perform temporary allocations
+ in *POOL. */
+static svn_error_t *
+choose_delta_base(representation_t **rep,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_boolean_t props,
+ apr_pool_t *pool)
+{
+ /* The zero-based index (counting from the "oldest" end), along NODEREVs line
+ * predecessors, of the node-rev we will use as delta base. */
+ int count;
+ /* The length of the linear part of a delta chain. (Delta chains use
+ * skip-delta bits for the high-order bits and are linear in the low-order
+ * bits.) */
+ int walk;
+ node_revision_t *base;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool;
+
+ /* If we have no predecessors, or that one is empty, then use the empty
+ * stream as a base. */
+ if (! noderev->predecessor_count)
+ {
+ *rep = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Flip the rightmost '1' bit of the predecessor count to determine
+ which file rev (counting from 0) we want to use. (To see why
+ count & (count - 1) unsets the rightmost set bit, think about how
+ you decrement a binary number.) */
+ count = noderev->predecessor_count;
+ count = count & (count - 1);
+
+ /* Finding the delta base over a very long distance can become extremely
+ expensive for very deep histories, possibly causing client timeouts etc.
+ OTOH, this is a rare operation and its gains are minimal. Lets simply
+ start deltification anew close every other 1000 changes or so. */
+ walk = noderev->predecessor_count - count;
+ if (walk > (int)ffd->max_deltification_walk)
+ {
+ *rep = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* We use skip delta for limiting the number of delta operations
+ along very long node histories. Close to HEAD however, we create
+ a linear history to minimize delta size. */
+ if (walk < (int)ffd->max_linear_deltification)
+ {
+ int shards;
+ SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
+
+ /* We also don't want the linear deltification to span more shards
+ than if deltas we used in a simple skip-delta scheme. */
+ if ((1 << (--shards)) <= walk)
+ count = noderev->predecessor_count - 1;
+ }
+
+ /* Walk back a number of predecessors equal to the difference
+ between count and the original predecessor count. (For example,
+ if noderev has ten predecessors and we want the eighth file rev,
+ walk back two predecessors.) */
+ base = noderev;
+ iterpool = svn_pool_create(pool);
+ while ((count++) < noderev->predecessor_count)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
+ base->predecessor_id, pool,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* return a suitable base representation */
+ *rep = props ? base->prop_rep : base->data_rep;
+
+ /* if we encountered a shared rep, its parent chain may be different
+ * from the node-rev parent chain. */
+ if (*rep)
+ {
+ int chain_length = 0;
+ int shard_count = 0;
+
+ /* Very short rep bases are simply not worth it as we are unlikely
+ * to re-coup the deltification space overhead of 20+ bytes. */
+ svn_filesize_t rep_size = (*rep)->expanded_size
+ ? (*rep)->expanded_size
+ : (*rep)->size;
+ if (rep_size < 64)
+ {
+ *rep = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Check whether the length of the deltification chain is acceptable.
+ * Otherwise, shared reps may form a non-skipping delta chain in
+ * extreme cases. */
+ SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count,
+ *rep, fs, pool));
+
+ /* Some reasonable limit, depending on how acceptable longer linear
+ * chains are in this repo. Also, allow for some minimal chain. */
+ if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
+ *rep = NULL;
+ else
+ /* To make it worth opening additional shards / pack files, we
+ * require that the reps have a certain minimal size. To deltify
+ * against a rep in different shard, the lower limit is 512 bytes
+ * and doubles with every extra shard to visit along the delta
+ * chain. */
+ if ( shard_count > 1
+ && ((svn_filesize_t)128 << shard_count) >= rep_size)
+ *rep = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Something went wrong and the pool for the rep write is being
+ cleared before we've finished writing the rep. So we need
+ to remove the rep from the protorevfile and we need to unlock
+ the protorevfile. */
+static apr_status_t
+rep_write_cleanup(void *data)
+{
+ struct rep_write_baton *b = data;
+ svn_error_t *err;
+
+ /* Truncate and close the protorevfile. */
+ err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool);
+ err = svn_error_compose_create(err, svn_io_file_close(b->file,
+ b->scratch_pool));
+
+ /* Remove our lock regardless of any preceding errors so that the
+ being_written flag is always removed and stays consistent with the
+ file lock which will be removed no matter what since the pool is
+ going away. */
+ err = svn_error_compose_create(err,
+ unlock_proto_rev(b->fs,
+ svn_fs_fs__id_txn_id(b->noderev->id),
+ b->lockcookie, b->scratch_pool));
+ if (err)
+ {
+ apr_status_t rc = err->apr_err;
+ svn_error_clear(err);
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Get a rep_write_baton and store it in *WB_P for the representation
+ indicated by NODEREV in filesystem FS. Perform allocations in
+ POOL. Only appropriate for file contents, not for props or
+ directory contents. */
+static svn_error_t *
+rep_write_get_baton(struct rep_write_baton **wb_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *b;
+ apr_file_t *file;
+ representation_t *base_rep;
+ svn_stream_t *source;
+ svn_txdelta_window_handler_t wh;
+ void *whb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
+ svn_fs_fs__rep_header_t header = { 0 };
+
+ b = apr_pcalloc(pool, sizeof(*b));
+
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+
+ b->fs = fs;
+ b->result_pool = pool;
+ b->scratch_pool = svn_pool_create(pool);
+ b->rep_size = 0;
+ b->noderev = noderev;
+
+ /* Open the prototype rev file and seek to its end. */
+ SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
+ fs, svn_fs_fs__id_txn_id(noderev->id),
+ b->scratch_pool));
+
+ b->file = file;
+ b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
+ svn_stream_from_aprfile2(file, TRUE,
+ b->scratch_pool),
+ b->scratch_pool);
+
+ SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
+
+ /* Get the base for this delta. */
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
+ SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE,
+ b->scratch_pool));
+
+ /* Write out the rep header. */
+ if (base_rep)
+ {
+ header.base_revision = base_rep->revision;
+ header.base_item_index = base_rep->item_index;
+ header.base_length = base_rep->size;
+ header.type = svn_fs_fs__rep_delta;
+ }
+ else
+ {
+ header.type = svn_fs_fs__rep_self_delta;
+ }
+ SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream,
+ b->scratch_pool));
+
+ /* Now determine the offset of the actual svndiff data. */
+ SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
+ b->scratch_pool));
+
+ /* Cleanup in case something goes wrong. */
+ apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
+ apr_pool_cleanup_null);
+
+ /* Prepare to write the svndiff data. */
+ svn_txdelta_to_svndiff3(&wh,
+ &whb,
+ b->rep_stream,
+ diff_version,
+ ffd->delta_compression_level,
+ pool);
+
+ b->delta_stream = svn_txdelta_target_push(wh, whb, source,
+ b->scratch_pool);
+
+ *wb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* For REP->SHA1_CHECKSUM, try to find an already existing representation
+ in FS and return it in *OUT_REP. If no such representation exists or
+ if rep sharing has been disabled for FS, NULL will be returned. Since
+ there may be new duplicate representations within the same uncommitted
+ revision, those can be passed in REPS_HASH (maps a sha1 digest onto
+ representation_t*), otherwise pass in NULL for REPS_HASH.
+
+ The content of both representations will be compared, taking REP's content
+ from FILE at OFFSET. Only if they actually match, will *OLD_REP not be
+ NULL.
+
+ Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries.
+ The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
+ */
+static svn_error_t *
+get_shared_rep(representation_t **old_rep,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_file_t *file,
+ apr_off_t offset,
+ apr_hash_t *reps_hash,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ svn_checksum_t checksum;
+ checksum.digest = rep->sha1_digest;
+ checksum.kind = svn_checksum_sha1;
+
+ /* Return NULL, if rep sharing has been disabled. */
+ *old_rep = NULL;
+ if (!ffd->rep_sharing_allowed)
+ return SVN_NO_ERROR;
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. Start with the hash lookup
+ because it is cheepest. */
+ if (reps_hash)
+ *old_rep = apr_hash_get(reps_hash,
+ rep->sha1_digest,
+ APR_SHA1_DIGESTSIZE);
+
+ /* If we haven't found anything yet, try harder and consult our DB. */
+ if (*old_rep == NULL)
+ {
+ err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool);
+ /* ### Other error codes that we shouldn't mask out? */
+ if (err == SVN_NO_ERROR)
+ {
+ if (*old_rep)
+ SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool));
+ }
+ else if (err->apr_err == SVN_ERR_FS_CORRUPT
+ || SVN_ERROR_IN_CATEGORY(err->apr_err,
+ SVN_ERR_MALFUNC_CATEGORY_START))
+ {
+ /* Fatal error; don't mask it.
+
+ In particular, this block is triggered when the rep-cache refers
+ to revisions in the future. We signal that as a corruption situation
+ since, once those revisions are less than youngest (because of more
+ commits), the rep-cache would be invalid.
+ */
+ SVN_ERR(err);
+ }
+ else
+ {
+ /* Something's wrong with the rep-sharing index. We can continue
+ without rep-sharing, but warn.
+ */
+ (fs->warning)(fs->warning_baton, err);
+ svn_error_clear(err);
+ *old_rep = NULL;
+ }
+ }
+
+ /* look for intra-revision matches (usually data reps but not limited
+ to them in case props happen to look like some data rep)
+ */
+ if (*old_rep == NULL && is_txn_rep(rep))
+ {
+ svn_node_kind_t kind;
+ const char *file_name
+ = path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool);
+
+ /* in our txn, is there a rep file named with the wanted SHA1?
+ If so, read it and use that rep.
+ */
+ SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
+ if (kind == svn_node_file)
+ {
+ svn_stringbuf_t *rep_string;
+ SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string,
+ result_pool, scratch_pool));
+ }
+ }
+
+ if (!*old_rep)
+ return SVN_NO_ERROR;
+
+ /* We don't want 0-length PLAIN representations to replace non-0-length
+ ones (see issue #4554). Take into account that EXPANDED_SIZE may be
+ 0 in which case we have to check the on-disk SIZE. Also, this doubles
+ as a simple guard against general rep-cache induced corruption. */
+ if ( ((*old_rep)->expanded_size != rep->expanded_size)
+ || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
+ {
+ *old_rep = NULL;
+ }
+ else
+ {
+ /* Add information that is missing in the cached data.
+ Use the old rep for this content. */
+ memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
+ (*old_rep)->uniquifier = rep->uniquifier;
+ }
+
+ /* If we (very likely) found a matching representation, compare the actual
+ * contents such that we can be sure that no rep-cache.db corruption or
+ * hash collision produced a false positive. */
+ if (*old_rep)
+ {
+ apr_off_t old_position;
+ svn_stream_t *contents;
+ svn_stream_t *old_contents;
+ svn_boolean_t same;
+
+ /* The existing representation may itself be part of the current
+ * transaction. In that case, it may be in different stages of
+ * the commit finalization process.
+ *
+ * OLD_REP_NORM is the same as that OLD_REP but it is assigned
+ * explicitly to REP's transaction if OLD_REP does not point
+ * to an already committed revision. This then prevents the
+ * revision lookup and the txn data will be accessed.
+ */
+ representation_t old_rep_norm = **old_rep;
+ if ( !SVN_IS_VALID_REVNUM(old_rep_norm.revision)
+ || old_rep_norm.revision > ffd->youngest_rev_cache)
+ old_rep_norm.txn_id = rep->txn_id;
+
+ /* Make sure we can later restore FILE's current position. */
+ SVN_ERR(svn_fs_fs__get_file_offset(&old_position, file, scratch_pool));
+
+ /* Compare the two representations.
+ * Note that the stream comparison might also produce MD5 checksum
+ * errors or other failures in case of SHA1 collisions. */
+ SVN_ERR(svn_fs_fs__get_contents_from_file(&contents, fs, rep, file,
+ offset, scratch_pool));
+ SVN_ERR(svn_fs_fs__get_contents(&old_contents, fs, &old_rep_norm,
+ FALSE, scratch_pool));
+ err = svn_stream_contents_same2(&same, contents, old_contents,
+ scratch_pool);
+
+ /* A mismatch should be extremely rare.
+ * If it does happen, reject the commit. */
+ if (!same || err)
+ {
+ /* SHA1 collision or worse. */
+ svn_stringbuf_t *old_rep_str
+ = svn_fs_fs__unparse_representation(*old_rep,
+ ffd->format, FALSE,
+ scratch_pool,
+ scratch_pool);
+ svn_stringbuf_t *rep_str
+ = svn_fs_fs__unparse_representation(rep,
+ ffd->format, FALSE,
+ scratch_pool,
+ scratch_pool);
+ const char *checksum__str
+ = svn_checksum_to_cstring_display(&checksum, scratch_pool);
+
+ return svn_error_createf(SVN_ERR_FS_GENERAL,
+ err, "SHA1 of reps '%s' and '%s' "
+ "matches (%s) but contents differ",
+ old_rep_str->data, rep_str->data,
+ checksum__str);
+ }
+
+ /* Restore FILE's read / write position. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &old_position, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+digests_final(representation_t *rep,
+ const svn_checksum_ctx_t *md5_ctx,
+ const svn_checksum_ctx_t *sha1_ctx,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
+ memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
+ SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
+ rep->has_sha1 = checksum != NULL;
+ if (rep->has_sha1)
+ memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
+
+ return SVN_NO_ERROR;
+}
+
+/* Close handler for the representation write stream. BATON is a
+ rep_write_baton. Writes out a new node-rev that correctly
+ references the representation we just finished writing. */
+static svn_error_t *
+rep_write_contents_close(void *baton)
+{
+ struct rep_write_baton *b = baton;
+ representation_t *rep;
+ representation_t *old_rep;
+ apr_off_t offset;
+
+ rep = apr_pcalloc(b->result_pool, sizeof(*rep));
+
+ /* Close our delta stream so the last bits of svndiff are written
+ out. */
+ if (b->delta_stream)
+ SVN_ERR(svn_stream_close(b->delta_stream));
+
+ /* Determine the length of the svndiff data. */
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
+ rep->size = offset - b->delta_start;
+
+ /* Fill in the rest of the representation field. */
+ rep->expanded_size = b->rep_size;
+ rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id);
+ SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool));
+ rep->revision = SVN_INVALID_REVNUM;
+
+ /* Finalize the checksum. */
+ SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
+ b->result_pool));
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, b->file, b->rep_offset, NULL,
+ b->result_pool, b->scratch_pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool));
+
+ /* Use the old rep for this content. */
+ b->noderev->data_rep = old_rep;
+ }
+ else
+ {
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
+ SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id,
+ b->rep_offset, b->scratch_pool));
+
+ b->noderev->data_rep = rep;
+ }
+
+ /* Remove cleanup callback. */
+ apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup);
+
+ /* Write out the new node-rev information. */
+ SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
+ FALSE, b->scratch_pool));
+ if (!old_rep)
+ {
+ svn_fs_fs__p2l_entry_t entry;
+
+ entry.offset = b->rep_offset;
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
+ entry.size = offset - b->rep_offset;
+ entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = rep->item_index;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ b->fnv1a_checksum_ctx,
+ b->scratch_pool));
+
+ SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry,
+ b->scratch_pool));
+ }
+
+ SVN_ERR(svn_io_file_close(b->file, b->scratch_pool));
+
+ /* Write the sha1->rep mapping *after* we successfully written node
+ * revision to disk. */
+ if (!old_rep)
+ SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool));
+
+ SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie,
+ b->scratch_pool));
+ svn_pool_destroy(b->scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Store a writable stream in *CONTENTS_P that will receive all data
+ written and store it as the file data representation referenced by
+ NODEREV in filesystem FS. Perform temporary allocations in
+ POOL. Only appropriate for file data, not props or directory
+ contents. */
+static svn_error_t *
+set_representation(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *wb;
+
+ if (! svn_fs_fs__id_is_txn(noderev->id))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempted to write to non-transaction '%s'"),
+ svn_fs_fs__id_unparse(noderev->id, pool)->data);
+
+ SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
+
+ *contents_p = svn_stream_create(wb, pool);
+ svn_stream_set_write(*contents_p, rep_write_contents);
+ svn_stream_set_close(*contents_p, rep_write_contents_close);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__set_contents(svn_stream_t **stream,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ if (noderev->kind != svn_node_file)
+ return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
+ _("Can't set text contents of a directory"));
+
+ return set_representation(stream, fs, noderev, pool);
+}
+
+svn_error_t *
+svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_idp,
+ node_revision_t *new_noderev,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (! copy_id)
+ copy_id = svn_fs_fs__id_copy_id(old_idp);
+ id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
+ txn_id, pool);
+
+ new_noderev->id = id;
+
+ if (! new_noderev->copyroot_path)
+ {
+ new_noderev->copyroot_path = apr_pstrdup(pool,
+ new_noderev->created_path);
+ new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
+ }
+
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
+ pool));
+
+ *new_id_p = id;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__set_proplist(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ const char *filename
+ = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
+ apr_file_t *file;
+ svn_stream_t *out;
+
+ /* Dump the property list to the mutable property file. */
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ /* Mark the node-rev's prop rep as mutable, if not already done. */
+ if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep))
+ {
+ noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
+ noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This baton is used by the stream created for write_container_rep. */
+struct write_container_baton
+{
+ svn_stream_t *stream;
+
+ apr_size_t size;
+
+ svn_checksum_ctx_t *md5_ctx;
+ svn_checksum_ctx_t *sha1_ctx;
+};
+
+/* The handler for the write_container_rep stream. BATON is a
+ write_container_baton, DATA has the data to write and *LEN is the number
+ of bytes to write. */
+static svn_error_t *
+write_container_handler(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct write_container_baton *whb = baton;
+
+ SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
+ SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
+
+ SVN_ERR(svn_stream_write(whb->stream, data, len));
+ whb->size += *len;
+
+ return SVN_NO_ERROR;
+}
+
+/* Callback function type. Write the data provided by BATON into STREAM. */
+typedef svn_error_t *
+(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
+
+/* Implement collection_writer_t writing the C string->svn_string_t hash
+ given as BATON. */
+static svn_error_t *
+write_hash_to_stream(svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *hash = baton;
+ SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement collection_writer_t writing the svn_fs_dirent_t* array given
+ as BATON. */
+static svn_error_t *
+write_directory_to_stream(svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *dir = baton;
+ SVN_ERR(unparse_dir_entries(dir, stream, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the COLLECTION as a text representation to file FILE using
+ WRITER. In the process, record position, the total size of the dump and
+ MD5 as well as SHA1 in REP. Add the representation of type ITEM_TYPE to
+ the indexes if necessary.
+
+ If ALLOW_REP_SHARING is FALSE, rep-sharing will not be used, regardless
+ of any other option and rep-sharing settings. If rep sharing has been
+ enabled and REPS_HASH is not NULL, it will be used in addition to the
+ on-disk cache to find earlier reps with the same content. If such
+ existing reps can be found, we will truncate the one just written from
+ the file and return the existing rep.
+
+ Perform temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+write_container_rep(representation_t *rep,
+ apr_file_t *file,
+ void *collection,
+ collection_writer_t writer,
+ svn_fs_t *fs,
+ apr_hash_t *reps_hash,
+ svn_boolean_t allow_rep_sharing,
+ apr_uint32_t item_type,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ struct write_container_baton *whb;
+ svn_checksum_ctx_t *fnv1a_checksum_ctx;
+ apr_off_t offset = 0;
+ svn_fs_fs__p2l_entry_t entry;
+
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+
+ whb = apr_pcalloc(scratch_pool, sizeof(*whb));
+
+ whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
+ svn_stream_from_aprfile2(file, TRUE,
+ scratch_pool),
+ scratch_pool);
+ whb->size = 0;
+ whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
+
+ stream = svn_stream_create(whb, scratch_pool);
+ svn_stream_set_write(stream, write_container_handler);
+
+ SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
+
+ SVN_ERR(writer(stream, collection, scratch_pool));
+
+ /* Store the results. */
+ SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
+
+ /* Update size info. */
+ rep->expanded_size = whb->size;
+ rep->size = whb->size;
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ if (allow_rep_sharing)
+ {
+ representation_t *old_rep;
+ SVN_ERR(get_shared_rep(&old_rep, fs, rep, file, offset, reps_hash,
+ scratch_pool, scratch_pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
+
+ /* Use the old rep for this content. */
+ memcpy(rep, old_rep, sizeof (*rep));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
+
+ SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
+ offset, scratch_pool));
+
+ entry.offset = offset;
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+ entry.size = offset - entry.offset;
+ entry.type = item_type;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = rep->item_index;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ scratch_pool));
+
+ SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
+ text representation to file FILE using WRITER. In the process, record the
+ total size and the md5 digest in REP and add the representation of type
+ ITEM_TYPE to the indexes if necessary.
+
+ If ALLOW_REP_SHARING is FALSE, rep-sharing will not be used, regardless
+ of any other option and rep-sharing settings. If rep sharing has been
+ enabled and REPS_HASH is not NULL, it will be used in addition to the
+ on-disk cache to find earlier reps with the same content. If such
+ existing reps can be found, we will truncate the one just written from
+ the file and return the existing rep.
+
+ If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
+ that we want to a props representation as the base for our delta.
+ Perform temporary allocations in SCRATCH_POOL.
+ */
+static svn_error_t *
+write_container_delta_rep(representation_t *rep,
+ apr_file_t *file,
+ void *collection,
+ collection_writer_t writer,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *reps_hash,
+ svn_boolean_t allow_rep_sharing,
+ apr_uint32_t item_type,
+ apr_pool_t *scratch_pool)
+{
+ svn_txdelta_window_handler_t diff_wh;
+ void *diff_whb;
+
+ svn_stream_t *file_stream;
+ svn_stream_t *stream;
+ representation_t *base_rep;
+ svn_checksum_ctx_t *fnv1a_checksum_ctx;
+ svn_stream_t *source;
+ svn_fs_fs__rep_header_t header = { 0 };
+ svn_fs_fs__p2l_entry_t entry;
+
+ apr_off_t rep_end = 0;
+ apr_off_t delta_start = 0;
+ apr_off_t offset = 0;
+
+ struct write_container_baton *whb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
+ svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
+ || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
+
+ /* Get the base for this delta. */
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
+ SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
+
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+
+ /* Write out the rep header. */
+ if (base_rep)
+ {
+ header.base_revision = base_rep->revision;
+ header.base_item_index = base_rep->item_index;
+ header.base_length = base_rep->size;
+ header.type = svn_fs_fs__rep_delta;
+ }
+ else
+ {
+ header.type = svn_fs_fs__rep_self_delta;
+ }
+
+ file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
+ svn_stream_from_aprfile2(file, TRUE,
+ scratch_pool),
+ scratch_pool);
+ SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
+ SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
+
+ /* Prepare to write the svndiff data. */
+ svn_txdelta_to_svndiff3(&diff_wh,
+ &diff_whb,
+ file_stream,
+ diff_version,
+ ffd->delta_compression_level,
+ scratch_pool);
+
+ whb = apr_pcalloc(scratch_pool, sizeof(*whb));
+ whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
+ scratch_pool);
+ whb->size = 0;
+ whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
+
+ /* serialize the hash */
+ stream = svn_stream_create(whb, scratch_pool);
+ svn_stream_set_write(stream, write_container_handler);
+
+ SVN_ERR(writer(stream, collection, scratch_pool));
+ SVN_ERR(svn_stream_close(whb->stream));
+
+ /* Store the results. */
+ SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
+
+ /* Update size info. */
+ SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
+ rep->size = rep_end - delta_start;
+ rep->expanded_size = whb->size;
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ if (allow_rep_sharing)
+ {
+ representation_t *old_rep;
+ SVN_ERR(get_shared_rep(&old_rep, fs, rep, file, offset, reps_hash,
+ scratch_pool, scratch_pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
+
+ /* Use the old rep for this content. */
+ memcpy(rep, old_rep, sizeof (*rep));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
+
+ SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
+ offset, scratch_pool));
+
+ entry.offset = offset;
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+ entry.size = offset - entry.offset;
+ entry.type = item_type;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = rep->item_index;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ scratch_pool));
+
+ SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
+ of (not yet committed) revision REV in FS. Use POOL for temporary
+ allocations.
+
+ If you change this function, consider updating svn_fs_fs__verify() too.
+ */
+static svn_error_t *
+validate_root_noderev(svn_fs_t *fs,
+ node_revision_t *root_noderev,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_revnum = rev-1;
+ int head_predecessor_count;
+
+ SVN_ERR_ASSERT(rev > 0);
+
+ /* Compute HEAD_PREDECESSOR_COUNT. */
+ {
+ svn_fs_root_t *head_revision;
+ const svn_fs_id_t *head_root_id;
+ node_revision_t *head_root_noderev;
+
+ /* Get /@HEAD's noderev. */
+ SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
+ SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
+ SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
+ pool, pool));
+ head_predecessor_count = head_root_noderev->predecessor_count;
+ }
+
+ /* Check that the root noderev's predecessor count equals REV.
+
+ This kind of corruption was seen on svn.apache.org (both on
+ the root noderev and on other fspaths' noderevs); see
+ issue #4129.
+
+ Normally (rev == root_noderev->predecessor_count), but here we
+ use a more roundabout check that should only trigger on new instances
+ of the corruption, rather then trigger on each and every new commit
+ to a repository that has triggered the bug somewhere in its root
+ noderev's history.
+ */
+ if (root_noderev->predecessor_count != -1
+ && (root_noderev->predecessor_count - head_predecessor_count)
+ != (rev - head_revnum))
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("predecessor count for "
+ "the root node-revision is wrong: "
+ "found (%d+%ld != %d), committing r%ld"),
+ head_predecessor_count,
+ rev - head_revnum, /* This is equal to 1. */
+ root_noderev->predecessor_count,
+ rev);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Given the potentially txn-local id PART, update that to a permanent ID
+ * based on the REVISION currently being written and the START_ID for that
+ * revision. Use the repo FORMAT to decide which implementation to use.
+ */
+static void
+get_final_id(svn_fs_fs__id_part_t *part,
+ svn_revnum_t revision,
+ apr_uint64_t start_id,
+ int format)
+{
+ if (part->revision == SVN_INVALID_REVNUM)
+ {
+ if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ part->revision = revision;
+ }
+ else
+ {
+ part->revision = 0;
+ part->number += start_id;
+ }
+ }
+}
+
+/* Copy a node-revision specified by id ID in fileystem FS from a
+ transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
+ pointer to the new node-id which will be allocated in POOL.
+ If this is a directory, copy all children as well.
+
+ START_NODE_ID and START_COPY_ID are
+ the first available node and copy ids for this filesystem, for older
+ FS formats.
+
+ REV is the revision number that this proto-rev-file will represent.
+
+ INITIAL_OFFSET is the offset of the proto-rev-file on entry to
+ commit_body.
+
+ If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
+ REPS_POOL) of each data rep that is new in this revision.
+
+ If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
+ of the representations of each property rep that is new in this
+ revision.
+
+ AT_ROOT is true if the node revision being written is the root
+ node-revision. It is only controls additional sanity checking
+ logic.
+
+ Temporary allocations are also from POOL. */
+static svn_error_t *
+write_final_rev(const svn_fs_id_t **new_id_p,
+ apr_file_t *file,
+ svn_revnum_t rev,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_uint64_t start_node_id,
+ apr_uint64_t start_copy_id,
+ apr_off_t initial_offset,
+ apr_array_header_t *reps_to_cache,
+ apr_hash_t *reps_hash,
+ apr_pool_t *reps_pool,
+ svn_boolean_t at_root,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_off_t my_offset;
+ const svn_fs_id_t *new_id;
+ svn_fs_fs__id_part_t node_id, copy_id, rev_item;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id);
+ svn_stream_t *file_stream;
+ svn_checksum_ctx_t *fnv1a_checksum_ctx;
+ apr_pool_t *subpool;
+
+ *new_id_p = NULL;
+
+ /* Check to see if this is a transaction node. */
+ if (! svn_fs_fs__id_is_txn(id))
+ return SVN_NO_ERROR;
+
+ subpool = svn_pool_create(pool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool));
+
+ if (noderev->kind == svn_node_dir)
+ {
+ apr_array_header_t *entries;
+ int i;
+
+ /* This is a directory. Write out all the children first. */
+
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
+ subpool));
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_dirent_t *dirent
+ = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
+
+ svn_pool_clear(subpool);
+ SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
+ start_node_id, start_copy_id, initial_offset,
+ reps_to_cache, reps_hash, reps_pool, FALSE,
+ subpool));
+ if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
+ dirent->id = svn_fs_fs__id_copy(new_id, pool);
+ }
+
+ if (noderev->data_rep && is_txn_rep(noderev->data_rep))
+ {
+ /* Write out the contents of this directory as a text rep. */
+ noderev->data_rep->revision = rev;
+ if (ffd->deltify_directories)
+ SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
+ entries,
+ write_directory_to_stream,
+ fs, noderev, NULL, FALSE,
+ SVN_FS_FS__ITEM_TYPE_DIR_REP,
+ pool));
+ else
+ SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
+ write_directory_to_stream, fs, NULL,
+ FALSE, SVN_FS_FS__ITEM_TYPE_DIR_REP,
+ pool));
+
+ reset_txn_in_rep(noderev->data_rep);
+ }
+ }
+ else
+ {
+ /* This is a file. We should make sure the data rep, if it
+ exists in a "this" state, gets rewritten to our new revision
+ num. */
+
+ if (noderev->data_rep && is_txn_rep(noderev->data_rep))
+ {
+ reset_txn_in_rep(noderev->data_rep);
+ noderev->data_rep->revision = rev;
+
+ if (!svn_fs_fs__use_log_addressing(fs))
+ {
+ /* See issue 3845. Some unknown mechanism caused the
+ protorev file to get truncated, so check for that
+ here. */
+ if (noderev->data_rep->item_index + noderev->data_rep->size
+ > initial_offset)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Truncated protorev file detected"));
+ }
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ /* Fix up the property reps. */
+ if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
+ {
+ apr_hash_t *proplist;
+ apr_uint32_t item_type = noderev->kind == svn_node_dir
+ ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
+ : SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
+
+ noderev->prop_rep->revision = rev;
+
+ if (ffd->deltify_properties)
+ SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
+ write_hash_to_stream, fs, noderev,
+ reps_hash, TRUE, item_type, pool));
+ else
+ SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
+ write_hash_to_stream, fs, reps_hash,
+ TRUE, item_type, pool));
+
+ reset_txn_in_rep(noderev->prop_rep);
+ }
+
+ /* Convert our temporary ID into a permanent revision one. */
+ node_id = *svn_fs_fs__id_node_id(noderev->id);
+ get_final_id(&node_id, rev, start_node_id, ffd->format);
+ copy_id = *svn_fs_fs__id_copy_id(noderev->id);
+ get_final_id(&copy_id, rev, start_copy_id, ffd->format);
+
+ if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
+ noderev->copyroot_rev = rev;
+
+ /* root nodes have a fixed ID in log addressing mode */
+ SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
+ if (svn_fs_fs__use_log_addressing(fs) && at_root)
+ {
+ /* reference the root noderev from the log-to-phys index */
+ rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
+ SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
+ rev_item.number, pool));
+ }
+ else
+ SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id,
+ my_offset, pool));
+
+ rev_item.revision = rev;
+ new_id = svn_fs_fs__id_rev_create(&node_id, &copy_id, &rev_item, pool);
+
+ noderev->id = new_id;
+
+ if (ffd->rep_sharing_allowed)
+ {
+ /* Save the data representation's hash in the rep cache. */
+ if ( noderev->data_rep && noderev->kind == svn_node_file
+ && noderev->data_rep->revision == rev)
+ {
+ SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+ APR_ARRAY_PUSH(reps_to_cache, representation_t *)
+ = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
+ }
+
+ if (noderev->prop_rep && noderev->prop_rep->revision == rev)
+ {
+ /* Add new property reps to hash and on-disk cache. */
+ representation_t *copy
+ = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
+
+ SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+ APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
+
+ apr_hash_set(reps_hash,
+ copy->sha1_digest,
+ APR_SHA1_DIGESTSIZE,
+ copy);
+ }
+ }
+
+ /* don't serialize SHA1 for dirs to disk (waste of space) */
+ if (noderev->data_rep && noderev->kind == svn_node_dir)
+ noderev->data_rep->has_sha1 = FALSE;
+
+ /* don't serialize SHA1 for props to disk (waste of space) */
+ if (noderev->prop_rep)
+ noderev->prop_rep->has_sha1 = FALSE;
+
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ noderev->is_fresh_txn_root = FALSE;
+
+ /* Write out our new node-revision. */
+ if (at_root)
+ SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
+
+ file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
+ svn_stream_from_aprfile2(file, TRUE, pool),
+ pool);
+ SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
+ svn_fs_fs__fs_supports_mergeinfo(fs),
+ pool));
+
+ /* reference the root noderev from the log-to-phys index */
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ svn_fs_fs__p2l_entry_t entry;
+ rev_item.revision = SVN_INVALID_REVNUM;
+
+ entry.offset = my_offset;
+ SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
+ entry.size = my_offset - entry.offset;
+ entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
+ entry.item = rev_item;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ pool));
+
+ SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
+ }
+
+ /* Return our ID that references the revision file. */
+ *new_id_p = noderev->id;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
+ permanent rev-file FILE in filesystem FS. *OFFSET_P is set the to offset
+ in the file of the beginning of this information. Perform temporary
+ allocations in POOL. */
+static svn_error_t *
+write_final_changed_path_info(apr_off_t *offset_p,
+ apr_file_t *file,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_hash_t *changed_paths,
+ apr_pool_t *pool)
+{
+ apr_off_t offset;
+ svn_stream_t *stream;
+ svn_checksum_ctx_t *fnv1a_checksum_ctx;
+
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+
+ /* write to target file & calculate checksum */
+ stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
+ svn_stream_from_aprfile2(file, TRUE, pool),
+ pool);
+ SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
+
+ *offset_p = offset;
+
+ /* reference changes from the indexes */
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ svn_fs_fs__p2l_entry_t entry;
+
+ entry.offset = offset;
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+ entry.size = offset - entry.offset;
+ entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ pool));
+
+ SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
+ SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
+ SVN_FS_FS__ITEM_INDEX_CHANGES, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
+ youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
+ NEW_REV's revision root.
+
+ Intended to be called as the very last step in a commit before 'current'
+ is bumped. This implies that we are holding the write lock. */
+static svn_error_t *
+verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
+ svn_revnum_t new_rev,
+ apr_pool_t *pool)
+{
+#ifdef SVN_DEBUG
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_t *ft; /* fs++ == ft */
+ svn_fs_root_t *root;
+ fs_fs_data_t *ft_ffd;
+ apr_hash_t *fs_config;
+
+ SVN_ERR_ASSERT(ffd->svn_fs_open_);
+
+ /* make sure FT does not simply return data cached by other instances
+ * but actually retrieves it from disk at least once.
+ */
+ fs_config = apr_hash_make(pool);
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
+ svn_uuid_generate(pool));
+ SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
+ fs_config,
+ pool,
+ pool));
+ ft_ffd = ft->fsap_data;
+ /* Don't let FT consult rep-cache.db, either. */
+ ft_ffd->rep_sharing_allowed = FALSE;
+
+ /* Time travel! */
+ ft_ffd->youngest_rev_cache = new_rev;
+
+ SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
+ SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
+ SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
+ SVN_ERR(svn_fs_fs__verify_root(root, pool));
+#endif /* SVN_DEBUG */
+
+ return SVN_NO_ERROR;
+}
+
+/* Update the 'current' file to hold the correct next node and copy_ids
+ from transaction TXN_ID in filesystem FS. The current revision is
+ set to REV. Perform temporary allocations in POOL. */
+static svn_error_t *
+write_final_current(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ svn_revnum_t rev,
+ apr_uint64_t start_node_id,
+ apr_uint64_t start_copy_id,
+ apr_pool_t *pool)
+{
+ apr_uint64_t txn_node_id;
+ apr_uint64_t txn_copy_id;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ return svn_fs_fs__write_current(fs, rev, 0, 0, pool);
+
+ /* To find the next available ids, we add the id that used to be in
+ the 'current' file, to the next ids from the transaction file. */
+ SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
+
+ start_node_id += txn_node_id;
+ start_copy_id += txn_copy_id;
+
+ return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id,
+ pool);
+}
+
+/* Verify that the user registered with FS has all the locks necessary to
+ permit all the changes associated with TXN_NAME.
+ The FS write lock is assumed to be held by the caller. */
+static svn_error_t *
+verify_locks(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_hash_t *changed_paths,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ apr_array_header_t *changed_paths_sorted;
+ svn_stringbuf_t *last_recursed = NULL;
+ int i;
+
+ /* Make an array of the changed paths, and sort them depth-first-ily. */
+ changed_paths_sorted = svn_sort__hash(changed_paths,
+ svn_sort_compare_items_as_paths,
+ pool);
+
+ /* Now, traverse the array of changed paths, verify locks. Note
+ that if we need to do a recursive verification a path, we'll skip
+ over children of that path when we get to them. */
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < changed_paths_sorted->nelts; i++)
+ {
+ const svn_sort__item_t *item;
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t recurse = TRUE;
+
+ svn_pool_clear(iterpool);
+
+ item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
+
+ /* Fetch the change associated with our path. */
+ path = item->key;
+ change = item->value;
+
+ /* If this path has already been verified as part of a recursive
+ check of one of its parents, no need to do it again. */
+ if (last_recursed
+ && svn_fspath__skip_ancestor(last_recursed->data, path))
+ continue;
+
+ /* What does it mean to succeed at lock verification for a given
+ path? For an existing file or directory getting modified
+ (text, props), it means we hold the lock on the file or
+ directory. For paths being added or removed, we need to hold
+ the locks for that path and any children of that path.
+
+ WHEW! We have no reliable way to determine the node kind
+ of deleted items, but fortunately we are going to do a
+ recursive check on deleted paths regardless of their kind. */
+ if (change->change_kind == svn_fs_path_change_modify)
+ recurse = FALSE;
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
+ iterpool));
+
+ /* If we just did a recursive check, remember the path we
+ checked (so children can be skipped). */
+ if (recurse)
+ {
+ if (! last_recursed)
+ last_recursed = svn_stringbuf_create(path, pool);
+ else
+ svn_stringbuf_set(last_recursed, path);
+ }
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Return in *PATH the path to a file containing the properties that
+ make up the final revision properties file. This involves setting
+ svn:date and removing any temporary properties associated with the
+ commit flags. */
+static svn_error_t *
+write_final_revprop(const char **path,
+ svn_fs_txn_t *txn,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ apr_hash_t *txnprops;
+ svn_boolean_t final_mods = FALSE;
+ svn_string_t date;
+ svn_string_t *client_date;
+
+ SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
+
+ /* Remove any temporary txn props representing 'flags'. */
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ {
+ svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
+ final_mods = TRUE;
+ }
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ {
+ svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
+ final_mods = TRUE;
+ }
+
+ client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
+ if (client_date)
+ {
+ svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
+ final_mods = TRUE;
+ }
+
+ /* Update commit time to ensure that svn:date revprops remain ordered if
+ requested. */
+ if (!client_date || strcmp(client_date->data, "1"))
+ {
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
+ final_mods = TRUE;
+ }
+
+ if (final_mods)
+ {
+ SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
+ *path = path_txn_props_final(txn->fs, txn_id, pool);
+ }
+ else
+ {
+ *path = path_txn_props(txn->fs, txn_id, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__add_index_data(svn_fs_t *fs,
+ apr_file_t *file,
+ const char *l2p_proto_index,
+ const char *p2l_proto_index,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_off_t l2p_offset;
+ apr_off_t p2l_offset;
+ svn_stringbuf_t *footer;
+ unsigned char footer_length;
+ svn_checksum_t *l2p_checksum;
+ svn_checksum_t *p2l_checksum;
+
+ /* Append the actual index data to the pack file. */
+ l2p_offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool));
+ SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file,
+ l2p_proto_index, revision,
+ pool, pool));
+
+ p2l_offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool));
+ SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file,
+ p2l_proto_index, revision,
+ pool, pool));
+
+ /* Append footer. */
+ footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum,
+ p2l_offset, p2l_checksum, pool, pool);
+ SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
+ pool));
+
+ footer_length = footer->len;
+ SVN_ERR_ASSERT(footer_length == footer->len);
+ SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for commit_body below. */
+struct commit_baton {
+ svn_revnum_t *new_rev_p;
+ svn_fs_t *fs;
+ svn_fs_txn_t *txn;
+ apr_array_header_t *reps_to_cache;
+ apr_hash_t *reps_hash;
+ apr_pool_t *reps_pool;
+};
+
+/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type. BATON is a 'struct commit_baton *'. */
+static svn_error_t *
+commit_body(void *baton, apr_pool_t *pool)
+{
+ struct commit_baton *cb = baton;
+ fs_fs_data_t *ffd = cb->fs->fsap_data;
+ const char *old_rev_filename, *rev_filename, *proto_filename;
+ const char *revprop_filename, *final_revprop;
+ const svn_fs_id_t *root_id, *new_root_id;
+ apr_uint64_t start_node_id;
+ apr_uint64_t start_copy_id;
+ svn_revnum_t old_rev, new_rev;
+ apr_file_t *proto_file;
+ void *proto_file_lockcookie;
+ apr_off_t initial_offset, changed_path_offset;
+ const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
+ apr_hash_t *changed_paths;
+
+ /* Re-Read the current repository format. All our repo upgrade and
+ config evaluation strategies are such that existing information in
+ FS and FFD remains valid.
+
+ Although we don't recommend upgrading hot repositories, people may
+ still do it and we must make sure to either handle them gracefully
+ or to error out.
+
+ Committing pre-format 3 txns will fail after upgrade to format 3+
+ because the proto-rev cannot be found; no further action needed.
+ Upgrades from pre-f7 to f7+ means a potential change in addressing
+ mode for the final rev. We must be sure to detect that cause because
+ the failure would only manifest once the new revision got committed.
+ */
+ SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool));
+
+ /* Read the current youngest revision and, possibly, the next available
+ node id and copy id (for old format filesystems). Update the cached
+ value for the youngest revision, because we have just checked it. */
+ SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id,
+ cb->fs, pool));
+ ffd->youngest_rev_cache = old_rev;
+
+ /* Check to make sure this transaction is based off the most recent
+ revision. */
+ if (cb->txn->base_rev != old_rev)
+ return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ _("Transaction out of date"));
+
+ /* We need the changes list for verification as well as for writing it
+ to the final rev file. */
+ SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
+ pool));
+
+ /* Locks may have been added (or stolen) between the calling of
+ previous svn_fs.h functions and svn_fs_commit_txn(), so we need
+ to re-examine every changed-path in the txn and re-verify all
+ discovered locks. */
+ SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool));
+
+ /* We are going to be one better than this puny old revision. */
+ new_rev = old_rev + 1;
+
+ /* Get a write handle on the proto revision file. */
+ SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
+ cb->fs, txn_id, pool));
+ SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
+
+ /* Write out all the node-revisions and directory contents. */
+ root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
+ SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
+ start_node_id, start_copy_id, initial_offset,
+ cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
+ TRUE, pool));
+
+ /* Write the changed-path information. */
+ SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
+ cb->fs, txn_id, changed_paths,
+ pool));
+
+ if (svn_fs_fs__use_log_addressing(cb->fs))
+ {
+ /* Append the index data to the rev file. */
+ SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file,
+ svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool),
+ svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool),
+ new_rev, pool));
+ }
+ else
+ {
+ /* Write the final line. */
+
+ svn_stringbuf_t *trailer
+ = svn_fs_fs__unparse_revision_trailer
+ ((apr_off_t)svn_fs_fs__id_item(new_root_id),
+ changed_path_offset,
+ pool);
+ SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len,
+ NULL, pool));
+ }
+
+ SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
+ SVN_ERR(svn_io_file_close(proto_file, pool));
+
+ /* We don't unlock the prototype revision file immediately to avoid a
+ race with another caller writing to the prototype revision file
+ before we commit it. */
+
+ /* Create the shard for the rev and revprop file, if we're sharding and
+ this is the first revision of a new shard. We don't care if this
+ fails because the shard already existed for some reason. */
+ if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
+ {
+ /* Create the revs shard. */
+ {
+ const char *new_dir
+ = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool);
+ svn_error_t *err
+ = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
+ if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
+ PATH_REVS_DIR,
+ pool),
+ new_dir, pool));
+ }
+
+ /* Create the revprops shard. */
+ SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
+ {
+ const char *new_dir
+ = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool);
+ svn_error_t *err
+ = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
+ if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
+ PATH_REVPROPS_DIR,
+ pool),
+ new_dir, pool));
+ }
+ }
+
+ /* Move the finished rev file into place.
+
+ ### This "breaks" the transaction by removing the protorev file
+ ### but the revision is not yet complete. If this commit does
+ ### not complete for any reason the transaction will be lost. */
+ old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool);
+ rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
+ proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
+ SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
+ old_rev_filename, pool));
+
+ /* Now that we've moved the prototype revision file out of the way,
+ we can unlock it (since further attempts to write to the file
+ will fail as it no longer exists). We must do this so that we can
+ remove the transaction directory later. */
+ SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
+
+ /* Move the revprops file into place. */
+ SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
+ SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
+ final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
+ SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
+ old_rev_filename, pool));
+
+ /* Update the 'current' file. */
+ SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
+ SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
+ start_copy_id, pool));
+
+ /* At this point the new revision is committed and globally visible
+ so let the caller know it succeeded by giving it the new revision
+ number, which fulfills svn_fs_commit_txn() contract. Any errors
+ after this point do not change the fact that a new revision was
+ created. */
+ *cb->new_rev_p = new_rev;
+
+ ffd->youngest_rev_cache = new_rev;
+
+ /* Remove this transaction directory. */
+ SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
+ * to the rep-cache database of FS. */
+static svn_error_t *
+write_reps_to_cache(svn_fs_t *fs,
+ const apr_array_header_t *reps_to_cache,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ for (i = 0; i < reps_to_cache->nelts; i++)
+ {
+ representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
+
+ SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__commit(svn_revnum_t *new_rev_p,
+ svn_fs_t *fs,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ struct commit_baton cb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ cb.new_rev_p = new_rev_p;
+ cb.fs = fs;
+ cb.txn = txn;
+
+ if (ffd->rep_sharing_allowed)
+ {
+ cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
+ cb.reps_hash = apr_hash_make(pool);
+ cb.reps_pool = pool;
+ }
+ else
+ {
+ cb.reps_to_cache = NULL;
+ cb.reps_hash = NULL;
+ cb.reps_pool = NULL;
+ }
+
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
+
+ /* At this point, *NEW_REV_P has been set, so errors below won't affect
+ the success of the commit. (See svn_fs_commit_txn().) */
+
+ if (ffd->rep_sharing_allowed)
+ {
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ /* Write new entries to the rep-sharing database.
+ *
+ * We use an sqlite transaction to speed things up;
+ * see <http://www.sqlite.org/faq.html#q19>.
+ */
+ /* ### A commit that touches thousands of files will starve other
+ (reader/writer) commits for the duration of the below call.
+ Maybe write in batches? */
+ SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db));
+ err = write_reps_to_cache(fs, cb.reps_to_cache, pool);
+ err = svn_sqlite__finish_transaction(ffd->rep_cache_db, err);
+
+ if (svn_error_find_cause(err, SVN_SQLITE__ERR_ROLLBACK_FAILED))
+ {
+ /* Failed rollback means that our db connection is unusable, and
+ the only thing we can do is close it. The connection will be
+ reopened during the next operation with rep-cache.db. */
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_fs_fs__close_rep_cache(fs)));
+ }
+ else if (err)
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ const char *txn_dir;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ apr_array_header_t *names;
+ apr_size_t ext_len = strlen(PATH_EXT_TXN);
+
+ names = apr_array_make(pool, 1, sizeof(const char *));
+
+ /* Get the transactions directory. */
+ txn_dir = svn_fs_fs__path_txns_dir(fs, pool);
+
+ /* Now find a listing of this directory. */
+ SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
+
+ /* Loop through all the entries and return anything that ends with '.txn'. */
+ for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = apr_hash_this_key(hi);
+ apr_ssize_t klen = apr_hash_this_key_len(hi);
+ const char *id;
+
+ /* The name must end with ".txn" to be considered a transaction. */
+ if ((apr_size_t) klen <= ext_len
+ || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
+ continue;
+
+ /* Truncate the ".txn" extension and store the ID. */
+ id = apr_pstrndup(pool, name, strlen(name) - ext_len);
+ APR_ARRAY_PUSH(names, const char *) = id;
+ }
+
+ *names_p = names;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ fs_txn_data_t *ftd;
+ svn_node_kind_t kind;
+ transaction_t *local_txn;
+ svn_fs_fs__id_part_t txn_id;
+
+ SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name));
+
+ /* First check to see if the directory exists. */
+ SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
+ &kind, pool));
+
+ /* Did we find it? */
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
+ _("No such transaction '%s'"),
+ name);
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+ ftd = apr_pcalloc(pool, sizeof(*ftd));
+ ftd->txn_id = txn_id;
+
+ /* Read in the root node of this transaction. */
+ txn->id = apr_pstrdup(pool, name);
+ txn->fs = fs;
+
+ SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool));
+
+ txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
+
+ txn->vtable = &txn_vtable;
+ txn->fsap_data = ftd;
+ *txn_p = txn;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ apr_hash_t *proplist = apr_hash_make(pool);
+ SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn),
+ pool));
+ *table_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool));
+
+ /* Delete any mutable property representation. */
+ if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
+ SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool),
+ FALSE, pool));
+
+ /* Delete any mutable data representation. */
+ if (noderev->data_rep && is_txn_rep(noderev->data_rep)
+ && noderev->kind == svn_node_dir)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id,
+ pool),
+ FALSE, pool));
+
+ /* remove the corresponding entry from the cache, if such exists */
+ if (ffd->txn_dir_cache)
+ {
+ const char *key = svn_fs_fs__id_unparse(id, pool)->data;
+ SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
+ }
+ }
+
+ return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool),
+ FALSE, pool);
+}
+
+
+
+/*** Transactions ***/
+
+svn_error_t *
+svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool));
+ *root_id_p = txn->root_id;
+ *base_root_id_p = txn->base_id;
+ return SVN_NO_ERROR;
+}
+
+
+/* Generic transaction operations. */
+
+svn_error_t *
+svn_fs_fs__txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
+
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_string_t date;
+ fs_txn_data_t *ftd;
+ apr_hash_t *props = apr_hash_make(pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
+
+ /* Put a datestamp on the newly created txn, so we always know
+ exactly how old it is. (This will help sysadmins identify
+ long-abandoned txns that may need to be manually removed.) When
+ a txn is promoted to a revision, this property will be
+ automatically overwritten with a revision datestamp. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+
+ svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
+
+ /* Set temporary txn props that represent the requested 'flags'
+ behaviors. */
+ if (flags & SVN_FS_TXN_CHECK_OOD)
+ svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
+ svn_string_create("true", pool));
+
+ if (flags & SVN_FS_TXN_CHECK_LOCKS)
+ svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
+ svn_string_create("true", pool));
+
+ if (flags & SVN_FS_TXN_CLIENT_DATE)
+ svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
+ svn_string_create("0", pool));
+
+ ftd = (*txn_p)->fsap_data;
+ return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,
+ pool));
+}
diff --git a/subversion/libsvn_fs_fs/transaction.h b/subversion/libsvn_fs_fs/transaction.h
new file mode 100644
index 0000000..f96b04d
--- /dev/null
+++ b/subversion/libsvn_fs_fs/transaction.h
@@ -0,0 +1,294 @@
+/* transaction.h --- transaction-related functions of FSFS
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__TRANSACTION_H
+#define SVN_LIBSVN_FS__TRANSACTION_H
+
+#include "fs.h"
+
+/* Return the transaction ID of TXN.
+ */
+const svn_fs_fs__id_part_t *
+svn_fs_fs__txn_get_id(svn_fs_txn_t *txn);
+
+/* Store NODEREV as the node-revision for the node whose id is ID in
+ FS, after setting its is_fresh_txn_root to FRESH_TXN_ROOT. Do any
+ necessary temporary allocation in POOL. */
+svn_error_t *
+svn_fs_fs__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ svn_boolean_t fresh_txn_root,
+ apr_pool_t *pool);
+
+/* Find the paths which were changed in transaction TXN_ID of
+ filesystem FS and store them in *CHANGED_PATHS_P.
+ Get any temporary allocations from POOL. */
+svn_error_t *
+svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Find the paths which were changed in revision REV of filesystem FS
+ and store them in *CHANGED_PATHS_P. Get any temporary allocations
+ from POOL. */
+svn_error_t *
+svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Create a new transaction in filesystem FS, based on revision REV,
+ and store it in *TXN_P. Allocate all necessary variables from
+ POOL. */
+svn_error_t *
+svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set the transaction property NAME to the value VALUE in transaction
+ TXN. Perform temporary allocations from POOL. */
+svn_error_t *
+svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/* Change transaction properties in transaction TXN based on PROPS.
+ Perform temporary allocations from POOL. */
+svn_error_t *
+svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+/* Store a transaction record in *TXN_P for the transaction identified
+ by TXN_ID in filesystem FS. Allocate everything from POOL. */
+svn_error_t *
+svn_fs_fs__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the next available copy_id in *COPY_ID for the transaction
+ TXN_ID in filesystem FS. Allocate space in POOL. */
+svn_error_t *
+svn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Create an entirely new mutable node in the filesystem FS, whose
+ node-revision is NODEREV. Set *ID_P to the new node revision's ID.
+ Use POOL for any temporary allocation. COPY_ID is the copy_id to
+ use in the node revision ID. TXN_ID is the Subversion transaction
+ under which this occurs. */
+svn_error_t *
+svn_fs_fs__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Remove all references to the transaction TXN_ID from filesystem FS.
+ Temporary allocations are from POOL. */
+svn_error_t *
+svn_fs_fs__purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Abort the existing transaction TXN, performing any temporary
+ allocations in POOL. */
+svn_error_t *
+svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Add or set in filesystem FS, transaction TXN_ID, in directory
+ PARENT_NODEREV a directory entry for NAME pointing to ID of type
+ KIND. Allocations are done in POOL. */
+svn_error_t *
+svn_fs_fs__set_entry(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ node_revision_t *parent_noderev,
+ const char *name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ apr_pool_t *pool);
+
+/* Add a change to the changes record for filesystem FS in transaction
+ TXN_ID. Mark path PATH, having node-id ID, as changed according to
+ the type in CHANGE_KIND. If the text representation was changed set
+ TEXT_MOD to TRUE, and likewise for PROP_MOD as well as MERGEINFO_MOD.
+ If this change was the result of a copy, set COPYFROM_REV and
+ COPYFROM_PATH to the revision and path of the copy source, otherwise
+ they should be set to SVN_INVALID_REVNUM and NULL. Perform any
+ temporary allocations from POOL. */
+svn_error_t *
+svn_fs_fs__add_change(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ const char *path,
+ const svn_fs_id_t *id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ svn_boolean_t mergeinfo_mod,
+ svn_node_kind_t node_kind,
+ svn_revnum_t copyfrom_rev,
+ const char *copyfrom_path,
+ apr_pool_t *pool);
+
+/* Return a writable stream in *STREAM that allows storing the text
+ representation of node-revision NODEREV in filesystem FS.
+ Allocations are from POOL. */
+svn_error_t *
+svn_fs_fs__set_contents(svn_stream_t **stream,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Create a node revision in FS which is an immediate successor of
+ OLD_ID, whose contents are NEW_NR. Set *NEW_ID_P to the new node
+ revision's ID. Use POOL for any temporary allocation.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was. If
+ COPY_ID is NULL, then re-use the copy ID from the predecessor node.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the deltification code assumes that the new node's
+ contents will change frequently, and will avoid representing other
+ nodes as deltas against this node's contents. */
+svn_error_t *
+svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_idp,
+ node_revision_t *new_noderev,
+ const svn_fs_fs__id_part_t *copy_id,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Write a new property list PROPLIST for node-revision NODEREV in
+ filesystem FS. Perform any temporary allocations in POOL. */
+svn_error_t *
+svn_fs_fs__set_proplist(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool);
+
+/* Append the L2P and P2L indexes given by their proto index file names
+ * L2P_PROTO_INDEX and P2L_PROTO_INDEX to the revision / pack FILE.
+ * The latter contains revision(s) starting at REVISION in FS.
+ * Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__add_index_data(svn_fs_t *fs,
+ apr_file_t *file,
+ const char *l2p_proto_index,
+ const char *p2l_proto_index,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+/* Commit the transaction TXN in filesystem FS and return its new
+ revision number in *REV. If the transaction is out of date, return
+ the error SVN_ERR_FS_TXN_OUT_OF_DATE. Use POOL for temporary
+ allocations. */
+svn_error_t *
+svn_fs_fs__commit(svn_revnum_t *new_rev_p,
+ svn_fs_t *fs,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Set *NAMES_P to an array of names which are all the active
+ transactions in filesystem FS. Allocate the array from POOL. */
+svn_error_t *
+svn_fs_fs__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Open the transaction named NAME in filesystem FS. Set *TXN_P to
+ * the transaction. If there is no such transaction, return
+` * SVN_ERR_FS_NO_SUCH_TRANSACTION. Allocate the new transaction in
+ * POOL. */
+svn_error_t *
+svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool);
+
+/* Return the property list from transaction TXN and store it in
+ *PROPLIST. Allocate the property list from POOL. */
+svn_error_t *
+svn_fs_fs__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Delete the mutable node-revision referenced by ID, along with any
+ mutable props or directory contents associated with it. Perform
+ temporary allocations in POOL. */
+svn_error_t *
+svn_fs_fs__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Retrieve information about the Subversion transaction SVN_TXN from
+ the `transactions' table of FS, allocating from POOL. Set
+ *ROOT_ID_P to the ID of the transaction's root directory. Set
+ *BASE_ROOT_ID_P to the ID of the root directory of the
+ transaction's base revision.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed.
+
+ Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */
+svn_error_t *
+svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_name,
+ apr_pool_t *pool);
+
+/* Find the value of the property named PROPNAME in transaction TXN.
+ Return the contents in *VALUE_P. The contents will be allocated
+ from POOL. */
+svn_error_t *
+svn_fs_fs__txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool);
+
+/* Begin a new transaction in filesystem FS, based on existing
+ revision REV. The new transaction is returned in *TXN_P. Allocate
+ the new transaction structure from POOL. */
+svn_error_t *
+svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c
index acd1eb4..5893da2 100644
--- a/subversion/libsvn_fs_fs/tree.c
+++ b/subversion/libsvn_fs_fs/tree.c
@@ -49,15 +49,19 @@
#include "svn_mergeinfo.h"
#include "svn_fs.h"
#include "svn_props.h"
+#include "svn_sorts.h"
#include "fs.h"
-#include "key-gen.h"
+#include "cached_data.h"
#include "dag.h"
#include "lock.h"
#include "tree.h"
#include "fs_fs.h"
#include "id.h"
+#include "pack.h"
#include "temp_serializer.h"
+#include "transaction.h"
+#include "util.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_subr_private.h"
@@ -66,26 +70,6 @@
#include "../libsvn_fs/fs-loader.h"
-/* ### I believe this constant will become internal to reps-strings.c.
- ### see the comment in window_consumer() for more information. */
-
-/* ### the comment also seems to need tweaking: the log file stuff
- ### is no longer an issue... */
-/* Data written to the filesystem through the svn_fs_apply_textdelta()
- interface is cached in memory until the end of the data stream, or
- until a size trigger is hit. Define that trigger here (in bytes).
- Setting the value to 0 will result in no filesystem buffering at
- all. The value only really matters when dealing with file contents
- bigger than the value itself. Above that point, large values here
- allow the filesystem to buffer more data in memory before flushing
- to the database, which increases memory usage but greatly decreases
- the amount of disk access (and log-file generation) in database.
- Smaller values will limit your overall memory consumption, but can
- drastically hurt throughput by necessitating more write operations
- to the database (which also generates more log-files). */
-#define WRITE_BUFFER_SIZE 512000
-
-
/* The root structures.
@@ -100,23 +84,12 @@
kept in the FS object and shared among multiple revision root
objects.
*/
-typedef struct fs_rev_root_data_t
-{
- /* A dag node for the revision's root directory. */
- dag_node_t *root_dir;
-
- /* Cache structure for mapping const char * PATH to const char
- *COPYFROM_STRING, so that paths_changed can remember all the
- copyfrom information in the changes file.
- COPYFROM_STRING has the format "REV PATH", or is the empty string if
- the path was added without history. */
- apr_hash_t *copyfrom_cache;
-
-} fs_rev_root_data_t;
+typedef dag_node_t fs_rev_root_data_t;
typedef struct fs_txn_root_data_t
{
- const char *txn_id;
+ /* TXN_ID value from the main struct but as a struct instead of a string */
+ svn_fs_fs__id_part_t txn_id;
/* Cache of txn DAG nodes (without their nested noderevs, because
* it's mutable). Same keys/values as ffd->rev_node_cache. */
@@ -134,10 +107,18 @@ static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
apr_pool_t *pool);
static svn_error_t *make_txn_root(svn_fs_root_t **root_p,
- svn_fs_t *fs, const char *txn,
- svn_revnum_t base_rev, apr_uint32_t flags,
+ svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn,
+ svn_revnum_t base_rev,
+ apr_uint32_t flags,
apr_pool_t *pool);
+static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
/*** Node Caching ***/
@@ -196,6 +177,13 @@ struct fs_fs_dag_cache_t
/* Property lookups etc. have a very high locality (75% re-hit).
Thus, remember the last hit location for optimistic lookup. */
apr_size_t last_hit;
+
+ /* Position of the last bucket hit that actually had a DAG node in it.
+ LAST_HIT may refer to a bucket that matches path@rev but has not
+ its NODE element set, yet.
+ This value is a mere hint for optimistic lookup and any value is
+ valid (as long as it is < BUCKET_COUNT). */
+ apr_size_t last_non_empty;
};
fs_fs_dag_cache_t*
@@ -245,11 +233,36 @@ cache_lookup( fs_fs_dag_cache_t *cache
&& (result->path_len == path_len)
&& !memcmp(result->path, path, path_len))
{
+ /* Remember the position of the last node we found in this cache. */
+ if (result->node)
+ cache->last_non_empty = cache->last_hit;
+
return result;
}
/* need to do a full lookup. Calculate the hash value
- (HASH_VALUE has been initialized to REVISION). */
+ (HASH_VALUE has been initialized to REVISION).
+
+ Note that the actual hash function is arbitrary as long as its result
+ in HASH_VALUE only depends on REVISION and *PATH. However, we try to
+ make as much of *PATH influence the result as possible to get an "even"
+ spread across the hash buckets (maximizes our cache retention rate and
+ thus the hit rates).
+
+ When chunked access is possible (independent of the PATH pointer's
+ value!), we read 4 bytes at once and multiply the hash value with a
+ FACTOR that mirror / pattern / shift all 4 input bytes to various bits
+ of the result. The final result will be taken from the MSBs.
+
+ When chunked access is not possible (not supported by CPU or odd bytes
+ at the end of *PATH), we use the simple traditional "* 33" hash
+ function that works very well with texts / paths and that e.g. APR uses.
+
+ Please note that the bytewise and the chunked calculation are *NOT*
+ interchangeable as they will yield different results for the same input.
+ For any given machine and *PATH, we must use a fixed combination of the
+ two functions.
+ */
i = 0;
#if SVN_UNALIGNED_ACCESS_IS_OK
/* We relax the dependency chain between iterations by processing
@@ -295,14 +308,44 @@ cache_lookup( fs_fs_dag_cache_t *cache
cache->insertions++;
}
+ else if (result->node)
+ {
+ /* This bucket is valid & has a suitable DAG node in it.
+ Remember its location. */
+ cache->last_non_empty = bucket_index;
+ }
return result;
}
+/* Optimistic lookup using the last seen non-empty location in CACHE.
+ Return the node of that entry, if it is still in use and matches PATH.
+ Return NULL otherwise. Since the caller usually already knows the path
+ length, provide it in PATH_LEN. */
+static dag_node_t *
+cache_lookup_last_path(fs_fs_dag_cache_t *cache,
+ const char *path,
+ apr_size_t path_len)
+{
+ cache_entry_t *result = &cache->buckets[cache->last_non_empty];
+ assert(strlen(path) == path_len);
+
+ if ( result->node
+ && (result->path_len == path_len)
+ && !memcmp(result->path, path, path_len))
+ {
+ return result->node;
+ }
+
+ return NULL;
+}
+
/* 2nd level cache */
/* Find and return the DAG node cache for ROOT and the key that
- should be used for PATH. */
+ should be used for PATH.
+
+ Pool will only be used for allocating a new keys if necessary */
static void
locate_cache(svn_cache__t **cache,
const char **key,
@@ -313,20 +356,25 @@ locate_cache(svn_cache__t **cache,
if (root->is_txn_root)
{
fs_txn_root_data_t *frd = root->fsap_data;
- if (cache) *cache = frd->txn_node_cache;
- if (key && path) *key = path;
+
+ if (cache)
+ *cache = frd->txn_node_cache;
+ if (key && path)
+ *key = path;
}
else
{
fs_fs_data_t *ffd = root->fs->fsap_data;
- if (cache) *cache = ffd->rev_node_cache;
- if (key && path) *key
- = svn_fs_fs__combine_number_and_string(root->rev, path, pool);
+
+ if (cache)
+ *cache = ffd->rev_node_cache;
+ if (key && path)
+ *key = svn_fs_fs__combine_number_and_string(root->rev, path, pool);
}
}
-/* Return NODE_P for PATH from ROOT's node cache, or NULL if the node
- isn't cached; read it from the FS. *NODE_P is allocated in POOL. */
+/* In *NODE_P, return the DAG node for PATH from ROOT's node cache, or NULL
+ if the node isn't cached. *NODE_P is allocated in POOL. */
static svn_error_t *
dag_node_cache_get(dag_node_t **node_p,
svn_fs_root_t *root,
@@ -356,7 +404,7 @@ dag_node_cache_get(dag_node_t **node_p,
if (found && node)
{
/* Patch up the FS, since this might have come from an old FS
- * object. */
+ * object. */
svn_fs_fs__dag_set_fs(node, root->fs);
/* Retain the DAG node in L1 cache. */
@@ -380,7 +428,7 @@ dag_node_cache_get(dag_node_t **node_p,
if (found && node)
{
/* Patch up the FS, since this might have come from an old FS
- * object. */
+ * object. */
svn_fs_fs__dag_set_fs(node, root->fs);
}
}
@@ -403,27 +451,23 @@ dag_node_cache_set(svn_fs_root_t *root,
SVN_ERR_ASSERT(*path == '/');
- /* Do *not* attempt to dup and put the node into L1.
- * dup() is twice as expensive as an L2 lookup (which will set also L1).
- */
locate_cache(&cache, &key, root, path, pool);
-
return svn_cache__set(cache, key, node, pool);
}
-/* Baton for find_descendents_in_cache. */
+/* Baton for find_descendants_in_cache. */
struct fdic_baton {
const char *path;
apr_array_header_t *list;
apr_pool_t *pool;
};
-/* If the given item is a descendent of BATON->PATH, push
+/* If the given item is a descendant of BATON->PATH, push
* it onto BATON->LIST (copying into BATON->POOL). Implements
* the svn_iter_apr_hash_cb_t prototype. */
static svn_error_t *
-find_descendents_in_cache(void *baton,
+find_descendants_in_cache(void *baton,
const void *key,
apr_ssize_t klen,
void *val,
@@ -458,16 +502,16 @@ dag_node_cache_invalidate(svn_fs_root_t *root,
locate_cache(&cache, NULL, root, NULL, b.pool);
- SVN_ERR(svn_cache__iter(NULL, cache, find_descendents_in_cache,
+ SVN_ERR(svn_cache__iter(NULL, cache, find_descendants_in_cache,
&b, b.pool));
iterpool = svn_pool_create(b.pool);
for (i = 0; i < b.list->nelts; i++)
{
- const char *descendent = APR_ARRAY_IDX(b.list, i, const char *);
+ const char *descendant = APR_ARRAY_IDX(b.list, i, const char *);
svn_pool_clear(iterpool);
- SVN_ERR(svn_cache__set(cache, descendent, NULL, iterpool));
+ SVN_ERR(svn_cache__set(cache, descendant, NULL, iterpool));
}
svn_pool_destroy(iterpool);
@@ -498,7 +542,8 @@ svn_fs_fs__txn_root(svn_fs_root_t **root_p,
flags |= SVN_FS_TXN_CHECK_LOCKS;
}
- return make_txn_root(root_p, txn->fs, txn->id, txn->base_rev, flags, pool);
+ return make_txn_root(root_p, txn->fs, svn_fs_fs__txn_get_id(txn),
+ txn->base_rev, flags, pool);
}
@@ -523,6 +568,15 @@ svn_fs_fs__revision_root(svn_fs_root_t **root_p,
/* Getting dag nodes for roots. */
+/* Return the transaction ID to a given transaction ROOT. */
+static const svn_fs_fs__id_part_t *
+root_txn_id(svn_fs_root_t *root)
+{
+ fs_txn_root_data_t *frd = root->fsap_data;
+ assert(root->is_txn_root);
+
+ return &frd->txn_id;
+}
/* Set *NODE_P to a freshly opened dag node referring to the root
directory of ROOT, allocating from POOL. */
@@ -534,14 +588,15 @@ root_node(dag_node_t **node_p,
if (root->is_txn_root)
{
/* It's a transaction root. Open a fresh copy. */
- return svn_fs_fs__dag_txn_root(node_p, root->fs, root->txn, pool);
+ return svn_fs_fs__dag_txn_root(node_p, root->fs, root_txn_id(root),
+ pool);
}
else
{
/* It's a revision root, so we already have its root directory
opened. */
- fs_rev_root_data_t *frd = root->fsap_data;
- *node_p = svn_fs_fs__dag_dup(frd->root_dir, pool);
+ dag_node_t *root_dir = root->fsap_data;
+ *node_p = svn_fs_fs__dag_dup(root_dir, pool);
return SVN_NO_ERROR;
}
}
@@ -557,7 +612,11 @@ mutable_root_node(dag_node_t **node_p,
apr_pool_t *pool)
{
if (root->is_txn_root)
- return svn_fs_fs__dag_clone_root(node_p, root->fs, root->txn, pool);
+ {
+ /* It's a transaction root. Open a fresh copy. */
+ return svn_fs_fs__dag_clone_root(node_p, root->fs, root_txn_id(root),
+ pool);
+ }
else
/* If it's not a transaction root, we can't change its contents. */
return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
@@ -645,25 +704,23 @@ parent_path_relpath(parent_path_t *child,
the inheritance method is copy_id_inherit_new, also return a
*COPY_SRC_PATH on which to base the new copy ID (else return NULL
for that path). CHILD must have a parent (it cannot be the root
- node). TXN_ID is the transaction in which these items might be
- mutable. Allocations are taken from POOL. */
+ node). Allocations are taken from POOL. */
static svn_error_t *
get_copy_inheritance(copy_id_inherit_t *inherit_p,
const char **copy_src_path,
svn_fs_t *fs,
parent_path_t *child,
- const char *txn_id,
apr_pool_t *pool)
{
const svn_fs_id_t *child_id, *parent_id, *copyroot_id;
- const char *child_copy_id, *parent_copy_id;
+ const svn_fs_fs__id_part_t *child_copy_id, *parent_copy_id;
const char *id_path = NULL;
svn_fs_root_t *copyroot_root;
dag_node_t *copyroot_node;
svn_revnum_t copyroot_rev;
const char *copyroot_path;
- SVN_ERR_ASSERT(child && child->parent && txn_id);
+ SVN_ERR_ASSERT(child && child->parent);
/* Initialize some convenience variables. */
child_id = svn_fs_fs__dag_get_id(child->node);
@@ -672,7 +729,7 @@ get_copy_inheritance(copy_id_inherit_t *inherit_p,
parent_copy_id = svn_fs_fs__id_copy_id(parent_id);
/* If this child is already mutable, we have nothing to do. */
- if (svn_fs_fs__id_txn_id(child_id))
+ if (svn_fs_fs__id_is_txn(child_id))
{
*inherit_p = copy_id_inherit_self;
*copy_src_path = NULL;
@@ -686,14 +743,14 @@ get_copy_inheritance(copy_id_inherit_t *inherit_p,
/* Special case: if the child's copy ID is '0', use the parent's
copy ID. */
- if (strcmp(child_copy_id, "0") == 0)
+ if (svn_fs_fs__id_part_is_root(child_copy_id))
return SVN_NO_ERROR;
/* Compare the copy IDs of the child and its parent. If they are
the same, then the child is already on the same branch as the
parent, and should use the same mutability copy ID that the
parent will use. */
- if (svn_fs_fs__key_compare(child_copy_id, parent_copy_id) == 0)
+ if (svn_fs_fs__id_part_eq(child_copy_id, parent_copy_id))
return SVN_NO_ERROR;
/* If the child is on the same branch that the parent is on, the
@@ -709,7 +766,7 @@ get_copy_inheritance(copy_id_inherit_t *inherit_p,
SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, pool));
copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
- if (svn_fs_fs__id_compare(copyroot_id, child_id) == -1)
+ if (svn_fs_fs__id_compare(copyroot_id, child_id) == svn_fs_node_unrelated)
return SVN_NO_ERROR;
/* Determine if we are looking at the child via its original path or
@@ -763,9 +820,63 @@ typedef enum open_path_flags_t {
/* The caller does not care about the parent node chain but only
the final DAG node. */
- open_path_node_only = 4
+ open_path_node_only = 4,
+
+ /* The caller wants a NULL path object instead of an error if the
+ path cannot be found. */
+ open_path_allow_null = 8
} open_path_flags_t;
+/* Try a short-cut for the open_path() function using the last node accessed.
+ * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is
+ * its "created path", return the node in *NODE_P. Set it to NULL otherwise.
+ *
+ * This function is used to support ra_serf-style access patterns where we
+ * are first asked for path@rev and then for path@c_rev of the same node.
+ * The shortcut works by ignoring the "rev" part of the cache key and then
+ * checking whether we got lucky. Lookup and verification are both quick
+ * plus there are many early outs for common types of mismatch.
+ */
+static svn_error_t *
+try_match_last_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_size_t path_len,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = root->fs->fsap_data;
+
+ /* Optimistic lookup: if the last node returned from the cache applied to
+ the same PATH, return it in NODE. */
+ dag_node_t *node
+ = cache_lookup_last_path(ffd->dag_node_cache, path, path_len);
+
+ /* Did we get a bucket with a committed node? */
+ if (node && !svn_fs_fs__dag_check_mutable(node))
+ {
+ /* Get the path&rev pair at which this node was created.
+ This is repository location for which this node is _known_ to be
+ the right lookup result irrespective of how we found it. */
+ const char *created_path
+ = svn_fs_fs__dag_get_created_path(node);
+ svn_revnum_t revision;
+ SVN_ERR(svn_fs_fs__dag_get_revision(&revision, node, scratch_pool));
+
+ /* Is it an exact match? */
+ if (revision == root->rev && strcmp(created_path, path) == 0)
+ {
+ /* Cache it under its full path@rev access path. */
+ SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool));
+
+ *node_p = node;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *node_p = NULL;
+ return SVN_NO_ERROR;
+}
+
/* Open the node identified by PATH in ROOT, allocating in POOL. Set
*PARENT_PATH_P to a path from the node up to ROOT. The resulting
@@ -773,10 +884,9 @@ typedef enum open_path_flags_t {
*element, for the root directory. PATH must be in canonical form.
If resulting *PARENT_PATH_P will eventually be made mutable and
- modified, or if copy ID inheritance information is otherwise
- needed, TXN_ID should be the ID of the mutability transaction. If
- TXN_ID is NULL, no copy ID inheritance information will be
- calculated for the *PARENT_PATH_P chain.
+ modified, or if copy ID inheritance information is otherwise needed,
+ IS_TXN_PATH must be set. If IS_TXN_PATH is FALSE, no copy ID
+ inheritance information will be calculated for the *PARENT_PATH_P chain.
If FLAGS & open_path_last_optional is zero, return the error
SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
@@ -800,38 +910,80 @@ open_path(parent_path_t **parent_path_p,
svn_fs_root_t *root,
const char *path,
int flags,
- const char *txn_id,
+ svn_boolean_t is_txn_path,
apr_pool_t *pool)
{
svn_fs_t *fs = root->fs;
dag_node_t *here = NULL; /* The directory we're currently looking at. */
parent_path_t *parent_path; /* The path from HERE up to the root. */
- const char *rest; /* The portion of PATH we haven't traversed yet. */
-
- /* ensure a canonical path representation */
- const char *path_so_far = "/";
+ const char *rest = NULL; /* The portion of PATH we haven't traversed yet. */
apr_pool_t *iterpool = svn_pool_create(pool);
- /* callers often traverse the tree in some path-based order. That means
- a sibling of PATH has been presently accessed. Try to start the lookup
- directly at the parent node, if the caller did not requested the full
- parent chain. */
- const char *directory;
+ /* path to the currently processed entry without trailing '/'.
+ We will reuse this across iterations by simply putting a NUL terminator
+ at the respective position and replacing that with a '/' in the next
+ iteration. This is correct as we assert() PATH to be canonical. */
+ svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool);
+ apr_size_t path_len = path_so_far->len;
+
+ /* Callers often traverse the DAG in some path-based order or along the
+ history segments. That allows us to try a few guesses about where to
+ find the next item. This is only useful if the caller didn't request
+ the full parent chain. */
assert(svn_fs__is_canonical_abspath(path));
+ path_so_far->len = 0; /* "" */
if (flags & open_path_node_only)
{
+ const char *directory;
+
+ /* First attempt: Assume that we access the DAG for the same path as
+ in the last lookup but for a different revision that happens to be
+ the last revision that touched the respective node. This is a
+ common pattern when e.g. checking out over ra_serf. Note that this
+ will only work for committed data as the revision info for nodes in
+ txns is bogus.
+
+ This shortcut is quick and will exit this function upon success.
+ So, try it first. */
+ if (!root->is_txn_root)
+ {
+ dag_node_t *node;
+ SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool));
+
+ /* Did the shortcut work? */
+ if (node)
+ {
+ /* Construct and return the result. */
+ svn_pool_destroy(iterpool);
+
+ parent_path = make_parent_path(node, 0, 0, pool);
+ parent_path->copy_inherit = copy_id_inherit_self;
+ *parent_path_p = parent_path;
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Second attempt: Try starting the lookup immediately at the parent
+ node. We will often have recently accessed either a sibling or
+ said parent DIRECTORY itself for the same revision. */
directory = svn_dirent_dirname(path, pool);
if (directory[1] != 0) /* root nodes are covered anyway */
- SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
+ {
+ SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
+
+ /* Did the shortcut work? */
+ if (here)
+ {
+ apr_size_t dirname_len = strlen(directory);
+ path_so_far->len = dirname_len;
+ rest = path + dirname_len + 1;
+ }
+ }
}
/* did the shortcut work? */
- if (here)
- {
- path_so_far = directory;
- rest = path + strlen(directory) + 1;
- }
- else
+ if (!here)
{
/* Make a parent_path item for the root node, using its own current
copy id. */
@@ -839,6 +991,7 @@ open_path(parent_path_t **parent_path_p,
rest = path + 1; /* skip the leading '/', it saves in iteration */
}
+ path_so_far->data[path_so_far->len] = '\0';
parent_path = make_parent_path(here, 0, 0, pool);
parent_path->copy_inherit = copy_id_inherit_self;
@@ -858,8 +1011,10 @@ open_path(parent_path_t **parent_path_p,
/* Parse out the next entry from the path. */
entry = svn_fs__next_entry_name(&next, rest, pool);
- /* Calculate the path traversed thus far. */
- path_so_far = svn_fspath__join(path_so_far, entry, pool);
+ /* Update the path traversed thus far. */
+ path_so_far->data[path_so_far->len] = '/';
+ path_so_far->len += strlen(entry) + 1;
+ path_so_far->data[path_so_far->len] = '\0';
if (*entry == '\0')
{
@@ -873,7 +1028,6 @@ open_path(parent_path_t **parent_path_p,
{
copy_id_inherit_t inherit;
const char *copy_path = NULL;
- svn_error_t *err = SVN_NO_ERROR;
dag_node_t *cached_node = NULL;
/* If we found a directory entry, follow it. First, we
@@ -882,22 +1036,20 @@ open_path(parent_path_t **parent_path_p,
element if we already know the lookup to fail for the
complete path. */
if (next || !(flags & open_path_uncached))
- SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool));
-
+ SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data,
+ pool));
if (cached_node)
child = cached_node;
else
- err = svn_fs_fs__dag_open(&child, here, entry, pool, iterpool);
+ SVN_ERR(svn_fs_fs__dag_open(&child, here, entry, pool, iterpool));
/* "file not found" requires special handling. */
- if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ if (child == NULL)
{
/* If this was the last path component, and the caller
said it was optional, then don't return an error;
just put a NULL node pointer in the path. */
- svn_error_clear(err);
-
if ((flags & open_path_last_optional)
&& (! next || *next == '\0'))
{
@@ -905,6 +1057,11 @@ open_path(parent_path_t **parent_path_p,
pool);
break;
}
+ else if (flags & open_path_allow_null)
+ {
+ parent_path = NULL;
+ break;
+ }
else
{
/* Build a better error message than svn_fs_fs__dag_open
@@ -913,22 +1070,19 @@ open_path(parent_path_t **parent_path_p,
}
}
- /* Other errors we return normally. */
- SVN_ERR(err);
-
if (flags & open_path_node_only)
{
- /* Shortcut: the caller only wan'ts the final DAG node. */
+ /* Shortcut: the caller only wants the final DAG node. */
parent_path->node = child;
}
else
{
/* Now, make a parent_path item for CHILD. */
parent_path = make_parent_path(child, entry, parent_path, pool);
- if (txn_id)
+ if (is_txn_path)
{
SVN_ERR(get_copy_inheritance(&inherit, &copy_path, fs,
- parent_path, txn_id, iterpool));
+ parent_path, iterpool));
parent_path->copy_inherit = inherit;
parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
}
@@ -936,7 +1090,8 @@ open_path(parent_path_t **parent_path_p,
/* Cache the node we found (if it wasn't already cached). */
if (! cached_node)
- SVN_ERR(dag_node_cache_set(root, path_so_far, child, iterpool));
+ SVN_ERR(dag_node_cache_set(root, path_so_far->data, child,
+ iterpool));
}
/* Are we finished traversing the path? */
@@ -945,7 +1100,7 @@ open_path(parent_path_t **parent_path_p,
/* The path isn't finished yet; we'd better be in a directory. */
if (svn_fs_fs__dag_node_kind(child) != svn_node_dir)
- SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far),
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data),
apr_psprintf(iterpool, _("Failure opening '%s'"), path));
rest = next;
@@ -970,7 +1125,7 @@ make_path_mutable(svn_fs_root_t *root,
apr_pool_t *pool)
{
dag_node_t *clone;
- const char *txn_id = root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(root);
/* Is the node mutable already? */
if (svn_fs_fs__dag_check_mutable(parent_path->node))
@@ -980,7 +1135,8 @@ make_path_mutable(svn_fs_root_t *root,
if (parent_path->parent)
{
const svn_fs_id_t *parent_id, *child_id, *copyroot_id;
- const char *copy_id = NULL;
+ svn_fs_fs__id_part_t copy_id = { SVN_INVALID_REVNUM, 0 };
+ svn_fs_fs__id_part_t *copy_id_ptr = &copy_id;
copy_id_inherit_t inherit = parent_path->copy_inherit;
const char *clone_path, *copyroot_path;
svn_revnum_t copyroot_rev;
@@ -997,7 +1153,7 @@ make_path_mutable(svn_fs_root_t *root,
{
case copy_id_inherit_parent:
parent_id = svn_fs_fs__dag_get_id(parent_path->parent->node);
- copy_id = svn_fs_fs__id_copy_id(parent_id);
+ copy_id = *svn_fs_fs__id_copy_id(parent_id);
break;
case copy_id_inherit_new:
@@ -1006,7 +1162,7 @@ make_path_mutable(svn_fs_root_t *root,
break;
case copy_id_inherit_self:
- copy_id = NULL;
+ copy_id_ptr = NULL;
break;
case copy_id_inherit_unknown:
@@ -1024,8 +1180,8 @@ make_path_mutable(svn_fs_root_t *root,
child_id = svn_fs_fs__dag_get_id(parent_path->node);
copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
- if (strcmp(svn_fs_fs__id_node_id(child_id),
- svn_fs_fs__id_node_id(copyroot_id)) != 0)
+ if (!svn_fs_fs__id_part_eq(svn_fs_fs__id_node_id(child_id),
+ svn_fs_fs__id_node_id(copyroot_id)))
is_parent_copyroot = TRUE;
/* Now make this node mutable. */
@@ -1034,7 +1190,7 @@ make_path_mutable(svn_fs_root_t *root,
parent_path->parent->node,
clone_path,
parent_path->entry,
- copy_id, txn_id,
+ copy_id_ptr, txn_id,
is_parent_copyroot,
pool));
@@ -1074,14 +1230,13 @@ get_dag(dag_node_t **dag_node_p,
if (! node)
{
- /* Canonicalize the input PATH. */
- if (! svn_fs__is_canonical_abspath(path))
- {
- path = svn_fs__canonicalize_abspath(path, pool);
-
- /* Try again with the corrected path. */
- SVN_ERR(dag_node_cache_get(&node, root, path, pool));
- }
+ /* Canonicalize the input PATH. As it turns out, >95% of all paths
+ * seen here during e.g. svnadmin verify are non-canonical, i.e.
+ * miss the leading '/'. Unconditional canonicalization has a net
+ * performance benefit over previously checking path for being
+ * canonical. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(dag_node_cache_get(&node, root, path, pool));
if (! node)
{
@@ -1089,7 +1244,7 @@ get_dag(dag_node_t **dag_node_p,
* error if the node for which we are searching doesn't exist. */
SVN_ERR(open_path(&parent_path, root, path,
open_path_uncached | open_path_node_only,
- NULL, pool));
+ FALSE, pool));
node = parent_path->node;
/* No need to cache our find -- open_path() will do that for us. */
@@ -1107,19 +1262,20 @@ get_dag(dag_node_t **dag_node_p,
/* Add a change to the changes table in FS, keyed on transaction id
TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
PATH (whose node revision id is--or was, in the case of a
- deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
- occurred. If the change resulted from a copy, COPYFROM_REV and
- COPYFROM_PATH specify under which revision and path the node was
- copied from. If this was not part of a copy, COPYFROM_REV should
- be SVN_INVALID_REVNUM. Do all this as part of POOL. */
+ deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or
+ MERGEINFO_MODs occurred. If the change resulted from a copy,
+ COPYFROM_REV and COPYFROM_PATH specify under which revision and path
+ the node was copied from. If this was not part of a copy, COPYFROM_REV
+ should be SVN_INVALID_REVNUM. Do all this as part of POOL. */
static svn_error_t *
add_change(svn_fs_t *fs,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
const char *path,
const svn_fs_id_t *noderev_id,
svn_fs_path_change_kind_t change_kind,
svn_boolean_t text_mod,
svn_boolean_t prop_mod,
+ svn_boolean_t mergeinfo_mod,
svn_node_kind_t node_kind,
svn_revnum_t copyfrom_rev,
const char *copyfrom_path,
@@ -1127,7 +1283,8 @@ add_change(svn_fs_t *fs,
{
return svn_fs_fs__add_change(fs, txn_id,
svn_fs__canonicalize_abspath(path, pool),
- noderev_id, change_kind, text_mod, prop_mod,
+ noderev_id, change_kind,
+ text_mod, prop_mod, mergeinfo_mod,
node_kind, copyfrom_rev, copyfrom_path,
pool);
}
@@ -1151,8 +1308,8 @@ svn_fs_fs__node_id(const svn_fs_id_t **id_p,
The root directory ("" or "/") node is stored in the
svn_fs_root_t object, and never changes when it's a revision
root, so we can just reach in and grab it directly. */
- fs_rev_root_data_t *frd = root->fsap_data;
- *id_p = svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(frd->root_dir), pool);
+ dag_node_t *root_dir = root->fsap_data;
+ *id_p = svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(root_dir), pool);
}
else
{
@@ -1164,6 +1321,82 @@ svn_fs_fs__node_id(const svn_fs_id_t **id_p,
return SVN_NO_ERROR;
}
+static svn_error_t *
+fs_node_relation(svn_fs_node_relation_t *relation,
+ svn_fs_root_t *root_a, const char *path_a,
+ svn_fs_root_t *root_b, const char *path_b,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ const svn_fs_id_t *id_a, *id_b;
+ svn_fs_fs__id_part_t node_id_a, node_id_b;
+
+ /* Root paths are a common special case. */
+ svn_boolean_t a_is_root_dir
+ = (path_a[0] == '\0') || ((path_a[0] == '/') && (path_a[1] == '\0'));
+ svn_boolean_t b_is_root_dir
+ = (path_b[0] == '\0') || ((path_b[0] == '/') && (path_b[1] == '\0'));
+
+ /* Another useful thing to know: Both are txns but not the same txn. */
+ svn_boolean_t different_txn
+ = root_a->is_txn_root && root_b->is_txn_root
+ && strcmp(root_a->txn, root_b->txn);
+
+ /* Path from different repository are always unrelated. */
+ if (root_a->fs != root_b->fs)
+ {
+ *relation = svn_fs_node_unrelated;
+ return SVN_NO_ERROR;
+ }
+
+ /* Are both (!) root paths? Then, they are related and we only test how
+ * direct the relation is. */
+ if (a_is_root_dir && b_is_root_dir)
+ {
+ /* For txn roots, root->REV is the base revision of that TXN. */
+ *relation = ( (root_a->rev == root_b->rev)
+ && (root_a->is_txn_root == root_b->is_txn_root)
+ && !different_txn)
+ ? svn_fs_node_unchanged
+ : svn_fs_node_common_ancestor;
+ return SVN_NO_ERROR;
+ }
+
+ /* We checked for all separations between ID spaces (repos, txn).
+ * Now, we can simply test for the ID values themselves. */
+ SVN_ERR(get_dag(&node, root_a, path_a, pool));
+ id_a = svn_fs_fs__dag_get_id(node);
+ node_id_a = *svn_fs_fs__id_node_id(id_a);
+
+ SVN_ERR(get_dag(&node, root_b, path_b, pool));
+ id_b = svn_fs_fs__dag_get_id(node);
+ node_id_b = *svn_fs_fs__id_node_id(id_b);
+
+ /* Noderevs from different nodes are unrelated. */
+ if (!svn_fs_fs__id_part_eq(&node_id_a, &node_id_b))
+ {
+ *relation = svn_fs_node_unrelated;
+ return SVN_NO_ERROR;
+ }
+
+ /* Noderevs have the same node-ID now. So, they *seem* to be related.
+ *
+ * Special case: Different txns may create the same (txn-local) node ID.
+ * Only when they are committed can they actually be related to others. */
+ if (different_txn && node_id_a.revision == SVN_INVALID_REVNUM)
+ {
+ *relation = svn_fs_node_unrelated;
+ return SVN_NO_ERROR;
+ }
+
+ /* The noderevs are actually related. Are they the same? */
+ if (svn_fs_fs__id_eq(id_a, id_b))
+ *relation = svn_fs_node_unchanged;
+ else
+ *relation = svn_fs_node_common_ancestor;
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_fs_fs__node_created_rev(svn_revnum_t *revision,
@@ -1282,6 +1515,19 @@ fs_node_proplist(apr_hash_t **table_p,
return SVN_NO_ERROR;
}
+static svn_error_t *
+fs_node_has_props(svn_boolean_t *has_props,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, root, path, scratch_pool));
+
+ return svn_error_trace(svn_fs_fs__dag_has_props(has_props, node,
+ scratch_pool));
+}
static svn_error_t *
increment_mergeinfo_up_tree(parent_path_t *pp,
@@ -1310,14 +1556,15 @@ fs_change_node_prop(svn_fs_root_t *root,
{
parent_path_t *parent_path;
apr_hash_t *proplist;
- const char *txn_id;
+ const svn_fs_fs__id_part_t *txn_id;
+ svn_boolean_t mergeinfo_mod = FALSE;
if (! root->is_txn_root)
return SVN_FS__NOT_TXN(root);
- txn_id = root->txn;
+ txn_id = root_txn_id(root);
path = svn_fs__canonicalize_abspath(path, pool);
- SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, pool));
/* Check (non-recursively) to see if path is locked; if so, check
that we can use it. */
@@ -1354,6 +1601,8 @@ fs_change_node_prop(svn_fs_root_t *root,
SVN_ERR(svn_fs_fs__dag_set_has_mergeinfo(parent_path->node,
(value != NULL), pool));
}
+
+ mergeinfo_mod = TRUE;
}
/* Set the property. */
@@ -1366,7 +1615,7 @@ fs_change_node_prop(svn_fs_root_t *root,
/* Make a record of this modification in the changes table. */
return add_change(root->fs, txn_id, path,
svn_fs_fs__dag_get_id(parent_path->node),
- svn_fs_path_change_modify, FALSE, TRUE,
+ svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
svn_fs_fs__dag_node_kind(parent_path->node),
SVN_INVALID_REVNUM, NULL, pool);
}
@@ -1382,6 +1631,7 @@ fs_props_changed(svn_boolean_t *changed_p,
const char *path1,
svn_fs_root_t *root2,
const char *path2,
+ svn_boolean_t strict,
apr_pool_t *pool)
{
dag_node_t *node1, *node2;
@@ -1395,7 +1645,7 @@ fs_props_changed(svn_boolean_t *changed_p,
SVN_ERR(get_dag(&node1, root1, path1, pool));
SVN_ERR(get_dag(&node2, root2, path2, pool));
return svn_fs_fs__dag_things_different(changed_p, NULL,
- node1, node2);
+ node1, node2, strict, pool);
}
@@ -1423,6 +1673,53 @@ conflict_err(svn_stringbuf_t *conflict_path,
_("Conflict at '%s'"), path);
}
+/* Compare the directory representations at nodes LHS and RHS and set
+ * *CHANGED to TRUE, if at least one entry has been added or removed them.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+compare_dir_structure(svn_boolean_t *changed,
+ dag_node_t *lhs,
+ dag_node_t *rhs,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *lhs_entries;
+ apr_array_header_t *rhs_entries;
+ int i;
+
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&lhs_entries, lhs, pool));
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&rhs_entries, rhs, pool));
+
+ /* different number of entries -> some addition / removal */
+ if (lhs_entries->nelts != rhs_entries->nelts)
+ {
+ *changed = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Since directories are sorted by name, we can simply compare their
+ entries one-by-one without binary lookup etc. */
+ for (i = 0; i < lhs_entries->nelts; ++i)
+ {
+ svn_fs_dirent_t *lhs_entry
+ = APR_ARRAY_IDX(lhs_entries, i, svn_fs_dirent_t *);
+ svn_fs_dirent_t *rhs_entry
+ = APR_ARRAY_IDX(rhs_entries, i, svn_fs_dirent_t *);
+
+ if (strcmp(lhs_entry->name, rhs_entry->name)
+ || !svn_fs_fs__id_part_eq(svn_fs_fs__id_node_id(lhs_entry->id),
+ svn_fs_fs__id_node_id(rhs_entry->id))
+ || !svn_fs_fs__id_part_eq(svn_fs_fs__id_copy_id(lhs_entry->id),
+ svn_fs_fs__id_copy_id(rhs_entry->id)))
+ {
+ *changed = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *changed = FALSE;
+ return SVN_NO_ERROR;
+}
/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
* and TARGET must be distinct node revisions. TARGET_PATH should
@@ -1452,13 +1749,13 @@ merge(svn_stringbuf_t *conflict_p,
dag_node_t *target,
dag_node_t *source,
dag_node_t *ancestor,
- const char *txn_id,
+ const svn_fs_fs__id_part_t *txn_id,
apr_int64_t *mergeinfo_increment_out,
apr_pool_t *pool)
{
const svn_fs_id_t *source_id, *target_id, *ancestor_id;
- apr_hash_t *s_entries, *t_entries, *a_entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *s_entries, *t_entries, *a_entries;
+ int i, s_idx = -1, t_idx = -1;
svn_fs_t *fs;
apr_pool_t *iterpool;
apr_int64_t mergeinfo_increment = 0;
@@ -1585,22 +1882,43 @@ merge(svn_stringbuf_t *conflict_p,
*/
{
node_revision_t *tgt_nr, *anc_nr, *src_nr;
+ svn_boolean_t same;
+ apr_pool_t *scratch_pool;
/* Get node revisions for our id's. */
- SVN_ERR(svn_fs_fs__get_node_revision(&tgt_nr, fs, target_id, pool));
- SVN_ERR(svn_fs_fs__get_node_revision(&anc_nr, fs, ancestor_id, pool));
- SVN_ERR(svn_fs_fs__get_node_revision(&src_nr, fs, source_id, pool));
+ scratch_pool = svn_pool_create(pool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&tgt_nr, fs, target_id, pool,
+ scratch_pool));
+ svn_pool_clear(scratch_pool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&anc_nr, fs, ancestor_id, pool,
+ scratch_pool));
+ svn_pool_clear(scratch_pool);
+ SVN_ERR(svn_fs_fs__get_node_revision(&src_nr, fs, source_id, pool,
+ scratch_pool));
+ svn_pool_destroy(scratch_pool);
/* Now compare the prop-keys of the skels. Note that just because
the keys are different -doesn't- mean the proplists have
- different contents. But merge() isn't concerned with contents;
- it doesn't do a brute-force comparison on textual contents, so
- it won't do that here either. Checking to see if the propkey
- atoms are `equal' is enough. */
- if (! svn_fs_fs__noderev_same_rep_key(tgt_nr->prop_rep, anc_nr->prop_rep))
- return conflict_err(conflict_p, target_path);
- if (! svn_fs_fs__noderev_same_rep_key(src_nr->prop_rep, anc_nr->prop_rep))
+ different contents. */
+ SVN_ERR(svn_fs_fs__prop_rep_equal(&same, fs, src_nr, anc_nr, pool));
+ if (! same)
return conflict_err(conflict_p, target_path);
+
+ /* The directory entries got changed in the repository but the directory
+ properties did not. */
+ SVN_ERR(svn_fs_fs__prop_rep_equal(&same, fs, tgt_nr, anc_nr, pool));
+ if (! same)
+ {
+ /* There is an incoming prop change for this directory.
+ We will accept it only if the directory changes were mere updates
+ to its entries, i.e. there were no additions or removals.
+ Those could cause update problems to the working copy. */
+ svn_boolean_t changed;
+ SVN_ERR(compare_dir_structure(&changed, source, ancestor, pool));
+
+ if (changed)
+ return conflict_err(conflict_p, target_path);
+ }
}
/* ### todo: it would be more efficient to simply check for a NULL
@@ -1614,27 +1932,19 @@ merge(svn_stringbuf_t *conflict_p,
/* for each entry E in a_entries... */
iterpool = svn_pool_create(pool);
- for (hi = apr_hash_first(pool, a_entries);
- hi;
- hi = apr_hash_next(hi))
+ for (i = 0; i < a_entries->nelts; ++i)
{
svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
- const char *name;
- apr_ssize_t klen;
-
svn_pool_clear(iterpool);
- name = svn__apr_hash_index_key(hi);
- klen = svn__apr_hash_index_klen(hi);
- a_entry = svn__apr_hash_index_val(hi);
-
- s_entry = apr_hash_get(s_entries, name, klen);
- t_entry = apr_hash_get(t_entries, name, klen);
+ a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_dirent_t *);
+ s_entry = svn_fs_fs__find_dir_entry(s_entries, a_entry->name, &s_idx);
+ t_entry = svn_fs_fs__find_dir_entry(t_entries, a_entry->name, &t_idx);
/* No changes were made to this entry while the transaction was
in progress, so do nothing to the target. */
if (s_entry && svn_fs_fs__id_eq(a_entry->id, s_entry->id))
- goto end;
+ continue;
/* A change was made to this entry while the transaction was in
process, but the transaction did not touch this entry. */
@@ -1665,15 +1975,16 @@ merge(svn_stringbuf_t *conflict_p,
mergeinfo_increment += mergeinfo_end;
}
- SVN_ERR(svn_fs_fs__dag_set_entry(target, name,
+ SVN_ERR(svn_fs_fs__dag_set_entry(target, a_entry->name,
s_entry->id,
s_entry->kind,
txn_id,
- iterpool));
+ pool));
}
else
{
- SVN_ERR(svn_fs_fs__dag_delete(target, name, txn_id, iterpool));
+ SVN_ERR(svn_fs_fs__dag_delete(target, a_entry->name, txn_id,
+ iterpool));
}
}
@@ -1706,14 +2017,14 @@ merge(svn_stringbuf_t *conflict_p,
/* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
modification of ANCESTOR-ENTRY, declare a conflict. */
- if (strcmp(svn_fs_fs__id_node_id(s_entry->id),
- svn_fs_fs__id_node_id(a_entry->id)) != 0
- || strcmp(svn_fs_fs__id_copy_id(s_entry->id),
- svn_fs_fs__id_copy_id(a_entry->id)) != 0
- || strcmp(svn_fs_fs__id_node_id(t_entry->id),
- svn_fs_fs__id_node_id(a_entry->id)) != 0
- || strcmp(svn_fs_fs__id_copy_id(t_entry->id),
- svn_fs_fs__id_copy_id(a_entry->id)) != 0)
+ if (!svn_fs_fs__id_part_eq(svn_fs_fs__id_node_id(s_entry->id),
+ svn_fs_fs__id_node_id(a_entry->id))
+ || !svn_fs_fs__id_part_eq(svn_fs_fs__id_copy_id(s_entry->id),
+ svn_fs_fs__id_copy_id(a_entry->id))
+ || !svn_fs_fs__id_part_eq(svn_fs_fs__id_node_id(t_entry->id),
+ svn_fs_fs__id_node_id(a_entry->id))
+ || !svn_fs_fs__id_part_eq(svn_fs_fs__id_copy_id(t_entry->id),
+ svn_fs_fs__id_copy_id(a_entry->id)))
return conflict_err(conflict_p,
svn_fspath__join(target_path,
a_entry->name,
@@ -1737,29 +2048,23 @@ merge(svn_stringbuf_t *conflict_p,
if (fs_supports_mergeinfo)
mergeinfo_increment += sub_mergeinfo_increment;
}
-
- /* We've taken care of any possible implications E could have.
- Remove it from source_entries, so it's easy later to loop
- over all the source entries that didn't exist in
- ancestor_entries. */
- end:
- apr_hash_set(s_entries, name, klen, NULL);
}
/* For each entry E in source but not in ancestor */
- for (hi = apr_hash_first(pool, s_entries);
- hi;
- hi = apr_hash_next(hi))
+ for (i = 0; i < s_entries->nelts; ++i)
{
- svn_fs_dirent_t *s_entry, *t_entry;
- const char *name = svn__apr_hash_index_key(hi);
- apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_fs_dirent_t *a_entry, *s_entry, *t_entry;
dag_node_t *s_ent_node;
svn_pool_clear(iterpool);
- s_entry = svn__apr_hash_index_val(hi);
- t_entry = apr_hash_get(t_entries, name, klen);
+ s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_dirent_t *);
+ a_entry = svn_fs_fs__find_dir_entry(a_entries, s_entry->name, &s_idx);
+ t_entry = svn_fs_fs__find_dir_entry(t_entries, s_entry->name, &t_idx);
+
+ /* Process only entries in source that are NOT in ancestor. */
+ if (a_entry)
+ continue;
/* If NAME exists in TARGET, declare a conflict. */
if (t_entry)
@@ -1816,7 +2121,7 @@ merge_changes(dag_node_t *ancestor_node,
{
dag_node_t *txn_root_node;
svn_fs_t *fs = txn->fs;
- const char *txn_id = txn->id;
+ const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(txn);
SVN_ERR(svn_fs_fs__dag_txn_root(&txn_root_node, fs, txn_id, pool));
@@ -1891,6 +2196,7 @@ svn_fs_fs__commit_txn(const char **conflict_p,
svn_error_t *err = SVN_NO_ERROR;
svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
svn_fs_t *fs = txn->fs;
+ fs_fs_data_t *ffd = fs->fsap_data;
/* Limit memory usage when the repository has a high commit rate and
needs to run the following while loop multiple times. The memory
@@ -1973,7 +2279,15 @@ svn_fs_fs__commit_txn(const char **conflict_p,
svn_fs_fs__reset_txn_caches(fs);
svn_pool_destroy(iterpool);
- return svn_error_trace(err);
+
+ SVN_ERR(err);
+
+ if (ffd->pack_after_commit)
+ {
+ SVN_ERR(svn_fs_fs__pack(fs, 0, NULL, NULL, NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
}
@@ -2068,10 +2382,36 @@ fs_dir_entries(apr_hash_t **table_p,
apr_pool_t *pool)
{
dag_node_t *node;
+ apr_hash_t *hash = svn_hash__make(pool);
+ apr_array_header_t *table;
+ int i;
/* Get the entries for this path in the caller's pool. */
SVN_ERR(get_dag(&node, root, path, pool));
- return svn_fs_fs__dag_dir_entries(table_p, node, pool);
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&table, node, pool));
+
+ /* Convert directory array to hash. */
+ for (i = 0; i < table->nelts; ++i)
+ {
+ svn_fs_dirent_t *entry = APR_ARRAY_IDX(table, i, svn_fs_dirent_t *);
+ svn_hash_sets(hash, entry->name, entry);
+ }
+
+ *table_p = hash;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fs_dir_optimal_order(apr_array_header_t **ordered_p,
+ svn_fs_root_t *root,
+ apr_hash_t *entries,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *ordered_p = svn_fs_fs__order_dir_entries(root->fs, entries, result_pool,
+ scratch_pool);
+
+ return SVN_NO_ERROR;
}
/* Raise an error if PATH contains a newline because FSFS cannot handle
@@ -2100,13 +2440,13 @@ fs_make_dir(svn_fs_root_t *root,
{
parent_path_t *parent_path;
dag_node_t *sub_dir;
- const char *txn_id = root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(root);
SVN_ERR(check_newline(path, pool));
path = svn_fs__canonicalize_abspath(path, pool);
SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
- txn_id, pool));
+ TRUE, pool));
/* Check (recursively) to see if some lock is 'reserving' a path at
that location, or even some child-path; if so, check that we can
@@ -2136,8 +2476,8 @@ fs_make_dir(svn_fs_root_t *root,
/* Make a record of this modification in the changes table. */
return add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(sub_dir),
- svn_fs_path_change_add, FALSE, FALSE, svn_node_dir,
- SVN_INVALID_REVNUM, NULL, pool);
+ svn_fs_path_change_add, FALSE, FALSE, FALSE,
+ svn_node_dir, SVN_INVALID_REVNUM, NULL, pool);
}
@@ -2149,15 +2489,16 @@ fs_delete_node(svn_fs_root_t *root,
apr_pool_t *pool)
{
parent_path_t *parent_path;
- const char *txn_id = root->txn;
+ const svn_fs_fs__id_part_t *txn_id;
apr_int64_t mergeinfo_count = 0;
svn_node_kind_t kind;
if (! root->is_txn_root)
return SVN_FS__NOT_TXN(root);
+ txn_id = root_txn_id(root);
path = svn_fs__canonicalize_abspath(path, pool);
- SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, pool));
kind = svn_fs_fs__dag_node_kind(parent_path->node);
/* We can't remove the root of the filesystem. */
@@ -2193,7 +2534,7 @@ fs_delete_node(svn_fs_root_t *root,
/* Make a record of this modification in the changes table. */
return add_change(root->fs, txn_id, path,
svn_fs_fs__dag_get_id(parent_path->node),
- svn_fs_path_change_delete, FALSE, FALSE, kind,
+ svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
SVN_INVALID_REVNUM, NULL, pool);
}
@@ -2224,7 +2565,7 @@ copy_helper(svn_fs_root_t *from_root,
{
dag_node_t *from_node;
parent_path_t *to_parent_path;
- const char *txn_id = to_root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(to_root);
svn_boolean_t same_p;
/* Use an error check, not an assert, because even the caller cannot
@@ -2236,11 +2577,17 @@ copy_helper(svn_fs_root_t *from_root,
_("Cannot copy between two different filesystems ('%s' and '%s')"),
from_root->fs->path, to_root->fs->path);
+ /* more things that we can't do ATM */
if (from_root->is_txn_root)
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Copy from mutable tree not currently supported"));
+ if (! to_root->is_txn_root)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copy immutable tree not supported"));
+
/* Get the NODE for FROM_PATH in FROM_ROOT.*/
SVN_ERR(get_dag(&from_node, from_root, from_path, pool));
@@ -2248,7 +2595,7 @@ copy_helper(svn_fs_root_t *from_root,
component does not exist, it's not that big a deal. We'll just
make one there. */
SVN_ERR(open_path(&to_parent_path, to_root, to_path,
- open_path_last_optional, txn_id, pool));
+ open_path_last_optional, TRUE, pool));
/* Check to see if path (or any child thereof) is locked; if so,
check that we can use the existing lock(s). */
@@ -2307,7 +2654,7 @@ copy_helper(svn_fs_root_t *from_root,
from_canonpath,
txn_id, pool));
- if (kind == svn_fs_path_change_replace)
+ if (kind != svn_fs_path_change_add)
SVN_ERR(dag_node_cache_invalidate(to_root,
parent_path_path(to_parent_path,
pool), pool));
@@ -2321,8 +2668,8 @@ copy_helper(svn_fs_root_t *from_root,
/* Make a record of this modification in the changes table. */
SVN_ERR(get_dag(&new_node, to_root, to_path, pool));
SVN_ERR(add_change(to_root->fs, txn_id, to_path,
- svn_fs_fs__dag_get_id(new_node), kind, FALSE, FALSE,
- svn_fs_fs__dag_node_kind(from_node),
+ svn_fs_fs__dag_get_id(new_node), kind, FALSE,
+ FALSE, FALSE, svn_fs_fs__dag_node_kind(from_node),
from_root->rev, from_canonpath, pool));
}
else
@@ -2397,46 +2744,12 @@ fs_copied_from(svn_revnum_t *rev_p,
apr_pool_t *pool)
{
dag_node_t *node;
- const char *copyfrom_path, *copyfrom_str = NULL;
- svn_revnum_t copyfrom_rev;
- char *str, *buf;
-
- /* Check to see if there is a cached version of this copyfrom
- entry. */
- if (! root->is_txn_root) {
- fs_rev_root_data_t *frd = root->fsap_data;
- copyfrom_str = svn_hash_gets(frd->copyfrom_cache, path);
- }
-
- if (copyfrom_str)
- {
- if (*copyfrom_str == 0)
- {
- /* We have a cached entry that says there is no copyfrom
- here. */
- copyfrom_rev = SVN_INVALID_REVNUM;
- copyfrom_path = NULL;
- }
- else
- {
- /* Parse the copyfrom string for our cached entry. */
- buf = apr_pstrdup(pool, copyfrom_str);
- str = svn_cstring_tokenize(" ", &buf);
- copyfrom_rev = SVN_STR_TO_REV(str);
- copyfrom_path = buf;
- }
- }
- else
- {
- /* There is no cached entry, look it up the old-fashioned
- way. */
- SVN_ERR(get_dag(&node, root, path, pool));
- SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&copyfrom_rev, node));
- SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copyfrom_path, node));
- }
- *rev_p = copyfrom_rev;
- *path_p = copyfrom_path;
+ /* There is no cached entry, look it up the old-fashioned
+ way. */
+ SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(rev_p, node));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(path_p, node));
return SVN_NO_ERROR;
}
@@ -2454,13 +2767,13 @@ fs_make_file(svn_fs_root_t *root,
{
parent_path_t *parent_path;
dag_node_t *child;
- const char *txn_id = root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(root);
SVN_ERR(check_newline(path, pool));
path = svn_fs__canonicalize_abspath(path, pool);
SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
- txn_id, pool));
+ TRUE, pool));
/* If there's already a file by that name, complain.
This also catches the case of trying to make a file named `/'. */
@@ -2489,8 +2802,8 @@ fs_make_file(svn_fs_root_t *root,
/* Make a record of this modification in the changes table. */
return add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(child),
- svn_fs_path_change_add, TRUE, FALSE, svn_node_file,
- SVN_INVALID_REVNUM, NULL, pool);
+ svn_fs_path_change_add, TRUE, FALSE, FALSE,
+ svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
}
@@ -2597,8 +2910,6 @@ typedef struct txdelta_baton_t
svn_stream_t *source_stream;
svn_stream_t *target_stream;
- svn_stream_t *string_stream;
- svn_stringbuf_t *target_string;
/* MD5 digest for the base text against which a delta is to be
applied, and for the resultant fulltext, respectively. Either or
@@ -2612,20 +2923,6 @@ typedef struct txdelta_baton_t
} txdelta_baton_t;
-/* ### see comment in window_consumer() regarding this function. */
-
-/* Helper function of generic type `svn_write_fn_t'. Implements a
- writable stream which appends to an svn_stringbuf_t. */
-static svn_error_t *
-write_to_string(void *baton, const char *data, apr_size_t *len)
-{
- txdelta_baton_t *tb = (txdelta_baton_t *) baton;
- svn_stringbuf_appendbytes(tb->target_string, data, *len);
- return SVN_NO_ERROR;
-}
-
-
-
/* The main window handler returned by svn_fs_apply_textdelta. */
static svn_error_t *
window_consumer(svn_txdelta_window_t *window, void *baton)
@@ -2637,48 +2934,11 @@ window_consumer(svn_txdelta_window_t *window, void *baton)
cb->target_string. */
SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
- /* ### the write_to_string() callback for the txdelta's output stream
- ### should be doing all the flush determination logic, not here.
- ### in a drastic case, a window could generate a LOT more than the
- ### maximum buffer size. we want to flush to the underlying target
- ### stream much sooner (e.g. also in a streamy fashion). also, by
- ### moving this logic inside the stream, the stream becomes nice
- ### and encapsulated: it holds all the logic about buffering and
- ### flushing.
- ###
- ### further: I believe the buffering should be removed from tree.c
- ### the buffering should go into the target_stream itself, which
- ### is defined by reps-string.c. Specifically, I think the
- ### rep_write_contents() function will handle the buffering and
- ### the spill to the underlying DB. by locating it there, then
- ### anybody who gets a writable stream for FS content can take
- ### advantage of the buffering capability. this will be important
- ### when we export an FS API function for writing a fulltext into
- ### the FS, rather than forcing that fulltext thru apply_textdelta.
- */
-
- /* Check to see if we need to purge the portion of the contents that
- have been written thus far. */
- if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE))
- {
- apr_size_t len = tb->target_string->len;
- SVN_ERR(svn_stream_write(tb->target_stream,
- tb->target_string->data,
- &len));
- svn_stringbuf_setempty(tb->target_string);
- }
-
- /* Is the window NULL? If so, we're done. */
+ /* Is the window NULL? If so, we're done. The stream has already been
+ closed by the interpreter. */
if (! window)
- {
- /* Close the internal-use stream. ### This used to be inside of
- txn_body_fulltext_finalize_edits(), but that invoked a nested
- Berkeley DB transaction -- scandalous! */
- SVN_ERR(svn_stream_close(tb->target_stream));
-
- SVN_ERR(svn_fs_fs__dag_finalize_edits(tb->node, tb->result_checksum,
- tb->pool));
- }
+ SVN_ERR(svn_fs_fs__dag_finalize_edits(tb->node, tb->result_checksum,
+ tb->pool));
return SVN_NO_ERROR;
}
@@ -2690,11 +2950,11 @@ apply_textdelta(void *baton, apr_pool_t *pool)
{
txdelta_baton_t *tb = (txdelta_baton_t *) baton;
parent_path_t *parent_path;
- const char *txn_id = tb->root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(tb->root);
/* Call open_path with no flags, as we want this to return an error
if the node for which we are searching doesn't exist. */
- SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, pool));
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, pool));
/* Check (non-recursively) to see if path is locked; if so, check
that we can use it. */
@@ -2730,15 +2990,9 @@ apply_textdelta(void *baton, apr_pool_t *pool)
SVN_ERR(svn_fs_fs__dag_get_edit_stream(&(tb->target_stream), tb->node,
tb->pool));
- /* Make a writable "string" stream which writes data to
- tb->target_string. */
- tb->target_string = svn_stringbuf_create_empty(tb->pool);
- tb->string_stream = svn_stream_create(tb, tb->pool);
- svn_stream_set_write(tb->string_stream, write_to_string);
-
/* Now, create a custom window handler that uses our two streams. */
svn_txdelta_apply(tb->source_stream,
- tb->string_stream,
+ tb->target_stream,
NULL,
tb->path,
tb->pool,
@@ -2748,8 +3002,8 @@ apply_textdelta(void *baton, apr_pool_t *pool)
/* Make a record of this modification in the changes table. */
return add_change(tb->root->fs, txn_id, tb->path,
svn_fs_fs__dag_get_id(tb->node),
- svn_fs_path_change_modify, TRUE, FALSE, svn_node_file,
- SVN_INVALID_REVNUM, NULL, pool);
+ svn_fs_path_change_modify, TRUE, FALSE, FALSE,
+ svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
}
@@ -2855,11 +3109,11 @@ apply_text(void *baton, apr_pool_t *pool)
{
struct text_baton_t *tb = baton;
parent_path_t *parent_path;
- const char *txn_id = tb->root->txn;
+ const svn_fs_fs__id_part_t *txn_id = root_txn_id(tb->root);
/* Call open_path with no flags, as we want this to return an error
if the node for which we are searching doesn't exist. */
- SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, pool));
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, pool));
/* Check (non-recursively) to see if path is locked; if so, check
that we can use it. */
@@ -2883,8 +3137,8 @@ apply_text(void *baton, apr_pool_t *pool)
/* Make a record of this modification in the changes table. */
return add_change(tb->root->fs, txn_id, tb->path,
svn_fs_fs__dag_get_id(tb->node),
- svn_fs_path_change_modify, TRUE, FALSE, svn_node_file,
- SVN_INVALID_REVNUM, NULL, pool);
+ svn_fs_path_change_modify, TRUE, FALSE, FALSE,
+ svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
}
@@ -2923,6 +3177,7 @@ fs_contents_changed(svn_boolean_t *changed_p,
const char *path1,
svn_fs_root_t *root2,
const char *path2,
+ svn_boolean_t strict,
apr_pool_t *pool)
{
dag_node_t *node1, *node2;
@@ -2951,7 +3206,7 @@ fs_contents_changed(svn_boolean_t *changed_p,
SVN_ERR(get_dag(&node1, root1, path1, pool));
SVN_ERR(get_dag(&node2, root2, path2, pool));
return svn_fs_fs__dag_things_different(NULL, changed_p,
- node1, node2);
+ node1, node2, strict, pool);
}
@@ -2993,14 +3248,11 @@ fs_paths_changed(apr_hash_t **changed_paths_p,
apr_pool_t *pool)
{
if (root->is_txn_root)
- return svn_fs_fs__txn_changes_fetch(changed_paths_p, root->fs, root->txn,
- pool);
+ return svn_fs_fs__txn_changes_fetch(changed_paths_p, root->fs,
+ root_txn_id(root), pool);
else
- {
- fs_rev_root_data_t *frd = root->fsap_data;
- return svn_fs_fs__paths_changed(changed_paths_p, root->fs, root->rev,
- frd->copyfrom_cache, pool);
- }
+ return svn_fs_fs__paths_changed(changed_paths_p, root->fs, root->rev,
+ pool);
}
@@ -3040,7 +3292,8 @@ static svn_error_t *
fs_node_history(svn_fs_history_t **history_p,
svn_fs_root_t *root,
const char *path,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_node_kind_t kind;
@@ -3049,15 +3302,13 @@ fs_node_history(svn_fs_history_t **history_p,
return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
/* And we require that the path exist in the root. */
- SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, path, scratch_pool));
if (kind == svn_node_none)
return SVN_FS__NOT_FOUND(root, path);
/* Okay, all seems well. Build our history object and return it. */
- *history_p = assemble_history(root->fs,
- svn_fs__canonicalize_abspath(path, pool),
- root->rev, FALSE, NULL,
- SVN_INVALID_REVNUM, pool);
+ *history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
+ SVN_INVALID_REVNUM, result_pool);
return SVN_NO_ERROR;
}
@@ -3115,14 +3366,13 @@ static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p,
const char *copy_dst_path;
svn_fs_root_t *copy_dst_root;
dag_node_t *copy_dst_node;
- svn_node_kind_t kind;
/* Initialize return values. */
*root_p = NULL;
*path_p = NULL;
path = svn_fs__canonicalize_abspath(path, pool);
- SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, pool));
/* Find the youngest copyroot in the path of this node-rev, which
will indicate the target of the innermost copy affecting the
@@ -3136,11 +3386,11 @@ static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p,
revision between COPY_DST_REV and REV. Make sure that PATH
exists as of COPY_DST_REV and is related to this node-rev. */
SVN_ERR(svn_fs_fs__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));
- SVN_ERR(svn_fs_fs__check_path(&kind, copy_dst_root, path, pool));
- if (kind == svn_node_none)
- return SVN_NO_ERROR;
SVN_ERR(open_path(&copy_dst_parent_path, copy_dst_root, path,
- open_path_node_only, NULL, pool));
+ open_path_node_only | open_path_allow_null, FALSE, pool));
+ if (copy_dst_parent_path == NULL)
+ return SVN_NO_ERROR;
+
copy_dst_node = copy_dst_parent_path->node;
if (! svn_fs_fs__id_check_related(svn_fs_fs__dag_get_id(copy_dst_node),
svn_fs_fs__dag_get_id(parent_path->node)))
@@ -3236,7 +3486,7 @@ fs_node_origin_rev(svn_revnum_t *revision,
{
svn_fs_t *fs = root->fs;
const svn_fs_id_t *given_noderev_id, *cached_origin_id;
- const char *node_id, *dash;
+ const svn_fs_fs__id_part_t *node_id;
path = svn_fs__canonicalize_abspath(path, pool);
@@ -3244,27 +3494,13 @@ fs_node_origin_rev(svn_revnum_t *revision,
SVN_ERR(svn_fs_fs__node_id(&given_noderev_id, root, path, pool));
node_id = svn_fs_fs__id_node_id(given_noderev_id);
- /* Is it a brand new uncommitted node? */
- if (node_id[0] == '_')
- {
- *revision = SVN_INVALID_REVNUM;
- return SVN_NO_ERROR;
- }
-
- /* Maybe this is a new-style node ID that just has the revision
- sitting right in it. */
- dash = strchr(node_id, '-');
- if (dash && *(dash+1))
- {
- *revision = SVN_STR_TO_REV(dash + 1);
- return SVN_NO_ERROR;
- }
-
- /* The root node always has ID 0, created in revision 0 and will never
- use the new-style ID format. */
- if (strcmp(node_id, "0") == 0)
+ /* Is it a brand new uncommitted node or a new-style node ID?
+ * (committed old-style nodes will have a 0 revision value;
+ * rev 0, number 0 is rev 0 root node). Note that != 0 includes
+ * SVN_INVALID_REVNUM for uncommitted nodes. */
+ if (node_id->revision != 0 || node_id->number == 0)
{
- *revision = 0;
+ *revision = node_id->revision;
return SVN_NO_ERROR;
}
@@ -3344,7 +3580,7 @@ fs_node_origin_rev(svn_revnum_t *revision,
/* Wow, I don't want to have to do all that again. Let's cache
the result. */
- if (node_id[0] != '_')
+ if (node_id->revision != SVN_INVALID_REVNUM)
SVN_ERR(svn_fs_fs__set_node_origin(fs, node_id,
svn_fs_fs__dag_get_id(node), pool));
@@ -3355,26 +3591,17 @@ fs_node_origin_rev(svn_revnum_t *revision,
}
-struct history_prev_args
-{
- svn_fs_history_t **prev_history_p;
- svn_fs_history_t *history;
- svn_boolean_t cross_copies;
- apr_pool_t *pool;
-};
-
-
static svn_error_t *
-history_prev(void *baton, apr_pool_t *pool)
+history_prev(svn_fs_history_t **prev_history,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- struct history_prev_args *args = baton;
- svn_fs_history_t **prev_history = args->prev_history_p;
- svn_fs_history_t *history = args->history;
fs_history_data_t *fhd = history->fsap_data;
const char *commit_path, *src_path, *path = fhd->path;
svn_revnum_t commit_rev, src_rev, dst_rev;
svn_revnum_t revision = fhd->revision;
- apr_pool_t *retpool = args->pool;
svn_fs_t *fs = fhd->fs;
parent_path_t *parent_path;
dag_node_t *node;
@@ -3393,21 +3620,21 @@ history_prev(void *baton, apr_pool_t *pool)
if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
{
reported = FALSE;
- if (! args->cross_copies)
+ if (! cross_copies)
return SVN_NO_ERROR;
path = fhd->path_hint;
revision = fhd->rev_hint;
}
/* Construct a ROOT for the current revision. */
- SVN_ERR(svn_fs_fs__revision_root(&root, fs, revision, pool));
+ SVN_ERR(svn_fs_fs__revision_root(&root, fs, revision, scratch_pool));
/* Open PATH/REVISION, and get its node and a bunch of other
goodies. */
- SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, scratch_pool));
node = parent_path->node;
commit_path = svn_fs_fs__dag_get_created_path(node);
- SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, pool));
+ SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, scratch_pool));
/* The Subversion filesystem is written in such a way that a given
line of history may have at most one interesting history point
@@ -3422,10 +3649,9 @@ history_prev(void *baton, apr_pool_t *pool)
{
/* ... we either have not yet reported on this revision (and
need now to do so) ... */
- *prev_history = assemble_history(fs,
- apr_pstrdup(retpool, commit_path),
+ *prev_history = assemble_history(fs, commit_path,
commit_rev, TRUE, NULL,
- SVN_INVALID_REVNUM, retpool);
+ SVN_INVALID_REVNUM, result_pool);
return SVN_NO_ERROR;
}
else
@@ -3441,16 +3667,16 @@ history_prev(void *baton, apr_pool_t *pool)
/* Replace NODE and friends with the information from its
predecessor. */
- SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, pred_id, pool));
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, pred_id, scratch_pool));
commit_path = svn_fs_fs__dag_get_created_path(node);
- SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, pool));
+ SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, scratch_pool));
}
}
/* Find the youngest copyroot in the path of this node, including
itself. */
SVN_ERR(find_youngest_copyroot(&copyroot_rev, &copyroot_path, fs,
- parent_path, pool));
+ parent_path, scratch_pool));
/* Initialize some state variables. */
src_path = NULL;
@@ -3464,8 +3690,8 @@ history_prev(void *baton, apr_pool_t *pool)
svn_fs_root_t *copyroot_root;
SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, fs, copyroot_rev,
- pool));
- SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, pool));
+ scratch_pool));
+ SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, scratch_pool));
copy_dst = svn_fs_fs__dag_get_created_path(node);
/* If our current path was the very destination of the copy,
@@ -3487,7 +3713,7 @@ history_prev(void *baton, apr_pool_t *pool)
SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copy_src, node));
dst_rev = copyroot_rev;
- src_path = svn_fspath__join(copy_src, remainder_path, pool);
+ src_path = svn_fspath__join(copy_src, remainder_path, scratch_pool);
}
}
@@ -3504,15 +3730,13 @@ history_prev(void *baton, apr_pool_t *pool)
if ((dst_rev == revision) && reported)
retry = TRUE;
- *prev_history = assemble_history(fs, apr_pstrdup(retpool, path),
- dst_rev, ! retry,
- src_path, src_rev, retpool);
+ *prev_history = assemble_history(fs, path, dst_rev, ! retry,
+ src_path, src_rev, result_pool);
}
else
{
- *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path),
- commit_rev, TRUE, NULL,
- SVN_INVALID_REVNUM, retpool);
+ *prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
+ NULL, SVN_INVALID_REVNUM, result_pool);
}
return SVN_NO_ERROR;
@@ -3527,7 +3751,8 @@ static svn_error_t *
fs_history_prev(svn_fs_history_t **prev_history_p,
svn_fs_history_t *history,
svn_boolean_t cross_copies,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_fs_history_t *prev_history = NULL;
fs_history_data_t *fhd = history->fsap_data;
@@ -3541,23 +3766,23 @@ fs_history_prev(svn_fs_history_t **prev_history_p,
{
if (! fhd->is_interesting)
prev_history = assemble_history(fs, "/", fhd->revision,
- 1, NULL, SVN_INVALID_REVNUM, pool);
+ 1, NULL, SVN_INVALID_REVNUM,
+ result_pool);
else if (fhd->revision > 0)
prev_history = assemble_history(fs, "/", fhd->revision - 1,
- 1, NULL, SVN_INVALID_REVNUM, pool);
+ 1, NULL, SVN_INVALID_REVNUM,
+ result_pool);
}
else
{
- struct history_prev_args args;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
prev_history = history;
while (1)
{
- args.prev_history_p = &prev_history;
- args.history = prev_history;
- args.cross_copies = cross_copies;
- args.pool = pool;
- SVN_ERR(history_prev(&args, pool));
+ svn_pool_clear(iterpool);
+ SVN_ERR(history_prev(&prev_history, prev_history, cross_copies,
+ result_pool, iterpool));
if (! prev_history)
break;
@@ -3565,6 +3790,8 @@ fs_history_prev(svn_fs_history_t **prev_history_p,
if (fhd->is_interesting)
break;
}
+
+ svn_pool_destroy(iterpool);
}
*prev_history_p = prev_history;
@@ -3594,9 +3821,8 @@ static history_vtable_t history_vtable = {
/* Return a new history object (marked as "interesting") for PATH and
REVISION, allocated in POOL, and with its members set to the values
- of the parameters provided. Note that PATH and PATH_HINT are not
- duped into POOL -- it is the responsibility of the caller to ensure
- that this happens. */
+ of the parameters provided. Note that PATH and PATH_HINT get
+ normalized and duplicated in POOL. */
static svn_fs_history_t *
assemble_history(svn_fs_t *fs,
const char *path,
@@ -3611,7 +3837,8 @@ assemble_history(svn_fs_t *fs,
fhd->path = svn_fs__canonicalize_abspath(path, pool);
fhd->revision = revision;
fhd->is_interesting = is_interesting;
- fhd->path_hint = path_hint;
+ fhd->path_hint = path_hint ? svn_fs__canonicalize_abspath(path_hint, pool)
+ : NULL;
fhd->rev_hint = rev_hint;
fhd->fs = fs;
@@ -3643,18 +3870,14 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *entries;
+ int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag,
- scratch_pool));
-
- for (hi = apr_hash_first(scratch_pool, entries);
- hi;
- hi = apr_hash_next(hi))
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag, scratch_pool));
+ for (i = 0; i < entries->nelts; ++i)
{
- svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+ svn_fs_dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
const char *kid_path;
dag_node_t *kid_dag;
svn_boolean_t has_mergeinfo, go_down;
@@ -3758,7 +3981,7 @@ get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
path = svn_fs__canonicalize_abspath(path, scratch_pool);
- SVN_ERR(open_path(&parent_path, rev_root, path, 0, NULL, scratch_pool));
+ SVN_ERR(open_path(&parent_path, rev_root, path, 0, FALSE, scratch_pool));
if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent)
return SVN_NO_ERROR;
@@ -4017,20 +4240,23 @@ static root_vtable_t root_vtable = {
svn_fs_fs__check_path,
fs_node_history,
svn_fs_fs__node_id,
+ fs_node_relation,
svn_fs_fs__node_created_rev,
fs_node_origin_rev,
fs_node_created_path,
fs_delete_node,
+ fs_copy,
+ fs_revision_link,
fs_copied_from,
fs_closest_copy,
fs_node_prop,
fs_node_proplist,
+ fs_node_has_props,
fs_change_node_prop,
fs_props_changed,
fs_dir_entries,
+ fs_dir_optimal_order,
fs_make_dir,
- fs_copy,
- fs_revision_link,
fs_file_length,
fs_file_checksum,
fs_file_contents,
@@ -4068,15 +4294,10 @@ make_revision_root(svn_fs_t *fs,
apr_pool_t *pool)
{
svn_fs_root_t *root = make_root(fs, pool);
- fs_rev_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
root->is_txn_root = FALSE;
root->rev = rev;
-
- frd->root_dir = root_dir;
- frd->copyfrom_cache = svn_hash__make(root->pool);
-
- root->fsap_data = frd;
+ root->fsap_data = root_dir;
return root;
}
@@ -4088,21 +4309,20 @@ make_revision_root(svn_fs_t *fs,
static svn_error_t *
make_txn_root(svn_fs_root_t **root_p,
svn_fs_t *fs,
- const char *txn,
+ const svn_fs_fs__id_part_t *txn,
svn_revnum_t base_rev,
apr_uint32_t flags,
apr_pool_t *pool)
{
svn_fs_root_t *root = make_root(fs, pool);
fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
+ frd->txn_id = *txn;
root->is_txn_root = TRUE;
- root->txn = apr_pstrdup(root->pool, txn);
+ root->txn = svn_fs_fs__id_txn_unparse(txn, root->pool);
root->txn_flags = flags;
root->rev = base_rev;
- frd->txn_id = txn;
-
/* Because this cache actually tries to invalidate elements, keep
the number of elements per page down.
@@ -4114,14 +4334,14 @@ make_txn_root(svn_fs_root_t **root_p,
APR_HASH_KEY_STRING,
32, 20, FALSE,
apr_pstrcat(pool, txn, ":TXN",
- (char *)NULL),
+ SVN_VA_NULL),
root->pool));
/* Initialize transaction-local caches in FS.
Note that we cannot put those caches in frd because that content
fs root object is not available where we would need it. */
- SVN_ERR(svn_fs_fs__initialize_txn_caches(fs, txn, pool));
+ SVN_ERR(svn_fs_fs__initialize_txn_caches(fs, root->txn, root->pool));
root->fsap_data = frd;
@@ -4132,7 +4352,7 @@ make_txn_root(svn_fs_root_t **root_p,
/* Verify. */
-static APR_INLINE const char *
+static const char *
stringify_node(dag_node_t *node,
apr_pool_t *pool)
{
@@ -4142,10 +4362,12 @@ stringify_node(dag_node_t *node,
/* Check metadata sanity on NODE, and on its children. Manually verify
information for DAG nodes in revision REV, and trust the metadata
- accuracy for nodes belonging to older revisions. */
+ accuracy for nodes belonging to older revisions. To detect cycles,
+ provide all parent dag_node_t * in PARENT_NODES. */
static svn_error_t *
verify_node(dag_node_t *node,
svn_revnum_t rev,
+ apr_array_header_t *parent_nodes,
apr_pool_t *pool)
{
svn_boolean_t has_mergeinfo;
@@ -4155,6 +4377,18 @@ verify_node(dag_node_t *node,
int pred_count;
svn_node_kind_t kind;
apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ /* Detect (non-)DAG cycles. */
+ for (i = 0; i < parent_nodes->nelts; ++i)
+ {
+ dag_node_t *parent = APR_ARRAY_IDX(parent_nodes, i, dag_node_t *);
+ if (svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(parent),
+ svn_fs_fs__dag_get_id(node)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Node is its own direct or indirect parent '%s'",
+ stringify_node(node, iterpool));
+ }
/* Fetch some data. */
SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, node));
@@ -4205,32 +4439,40 @@ verify_node(dag_node_t *node,
}
if (kind == svn_node_dir)
{
- apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *entries;
apr_int64_t children_mergeinfo = 0;
+ APR_ARRAY_PUSH(parent_nodes, dag_node_t*) = node;
SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
/* Compute CHILDREN_MERGEINFO. */
- for (hi = apr_hash_first(pool, entries);
- hi;
- hi = apr_hash_next(hi))
+ for (i = 0; i < entries->nelts; ++i)
{
- svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+ svn_fs_dirent_t *dirent
+ = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
dag_node_t *child;
- svn_revnum_t child_rev;
apr_int64_t child_mergeinfo;
svn_pool_clear(iterpool);
/* Compute CHILD_REV. */
- SVN_ERR(svn_fs_fs__dag_get_node(&child, fs, dirent->id, iterpool));
- SVN_ERR(svn_fs_fs__dag_get_revision(&child_rev, child, iterpool));
-
- if (child_rev == rev)
- SVN_ERR(verify_node(child, rev, iterpool));
+ if (svn_fs_fs__id_rev(dirent->id) == rev)
+ {
+ SVN_ERR(svn_fs_fs__dag_get_node(&child, fs, dirent->id,
+ iterpool));
+ SVN_ERR(verify_node(child, rev, parent_nodes, iterpool));
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&child_mergeinfo,
+ child));
+ }
+ else
+ {
+ /* access mergeinfo counter with minimal overhead */
+ node_revision_t *noderev;
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, dirent->id,
+ iterpool, iterpool));
+ child_mergeinfo = noderev->mergeinfo_count;
+ }
- SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&child_mergeinfo, child));
children_mergeinfo += child_mergeinfo;
}
@@ -4243,6 +4485,10 @@ verify_node(dag_node_t *node,
stringify_node(node, iterpool),
mergeinfo_count, has_mergeinfo,
children_mergeinfo);
+
+ /* If we don't make it here, there was an error / corruption.
+ * In that case, nobody will need PARENT_NODES anymore. */
+ apr_array_pop(parent_nodes);
}
svn_pool_destroy(iterpool);
@@ -4255,6 +4501,7 @@ svn_fs_fs__verify_root(svn_fs_root_t *root,
{
svn_fs_t *fs = root->fs;
dag_node_t *root_dir;
+ apr_array_header_t *parent_nodes;
/* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
(and elsewhere). This code makes more thorough checks than the
@@ -4270,16 +4517,16 @@ svn_fs_fs__verify_root(svn_fs_root_t *root,
if (root->is_txn_root)
{
fs_txn_root_data_t *frd = root->fsap_data;
- SVN_ERR(svn_fs_fs__dag_txn_root(&root_dir, fs, frd->txn_id, pool));
+ SVN_ERR(svn_fs_fs__dag_txn_root(&root_dir, fs, &frd->txn_id, pool));
}
else
{
- fs_rev_root_data_t *frd = root->fsap_data;
- root_dir = frd->root_dir;
+ root_dir = root->fsap_data;
}
/* Recursively verify ROOT_DIR. */
- SVN_ERR(verify_node(root_dir, root->rev, pool));
+ parent_nodes = apr_array_make(pool, 16, sizeof(dag_node_t *));
+ SVN_ERR(verify_node(root_dir, root->rev, parent_nodes, pool));
/* Verify explicitly the predecessor of the root. */
{
diff --git a/subversion/libsvn_fs_fs/tree.h b/subversion/libsvn_fs_fs/tree.h
index 34fa0a2..7ddfcd9 100644
--- a/subversion/libsvn_fs_fs/tree.h
+++ b/subversion/libsvn_fs_fs/tree.h
@@ -48,8 +48,7 @@ svn_error_t *svn_fs_fs__deltify(svn_fs_t *fs, svn_revnum_t rev,
/* Commit the transaction TXN as a new revision. Return the new
revision in *NEW_REV. If the transaction conflicts with other
changes return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a string
- that details the cause of the conflict. Perform temporary
- allocations in POOL. */
+ that details the cause of the conflict. */
svn_error_t *svn_fs_fs__commit_txn(const char **conflict_p,
svn_revnum_t *new_rev, svn_fs_txn_t *txn,
apr_pool_t *pool);
@@ -91,6 +90,20 @@ svn_error_t *
svn_fs_fs__verify_root(svn_fs_root_t *root,
apr_pool_t *pool);
+svn_error_t *
+svn_fs_fs__info_format(int *fs_format,
+ svn_version_t **supports_version,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_fs_fs__info_config_files(apr_array_header_t **files,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_fs_fs/util.c b/subversion/libsvn_fs_fs/util.c
new file mode 100644
index 0000000..faa1e3d
--- /dev/null
+++ b/subversion/libsvn_fs_fs/util.c
@@ -0,0 +1,694 @@
+/* util.c --- utility functions for FSFS repo access
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_ctype.h"
+#include "svn_dirent_uri.h"
+#include "private/svn_string_private.h"
+
+#include "fs_fs.h"
+#include "pack.h"
+#include "util.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+svn_boolean_t
+svn_fs_fs__is_packed_rev(svn_fs_t *fs,
+ svn_revnum_t rev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ return (rev < ffd->min_unpacked_rev);
+}
+
+svn_boolean_t
+svn_fs_fs__is_packed_revprop(svn_fs_t *fs,
+ svn_revnum_t rev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* rev 0 will not be packed */
+ return (rev < ffd->min_unpacked_rev)
+ && (rev != 0)
+ && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
+}
+
+svn_revnum_t
+svn_fs_fs__packed_base_rev(svn_fs_t *fs,
+ svn_revnum_t revision)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return (revision < ffd->min_unpacked_rev)
+ ? (revision - (revision % ffd->max_files_per_dir))
+ : revision;
+}
+
+const char *
+svn_fs_fs__path_txn_current(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_current_lock(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
+}
+
+const char *
+svn_fs_fs__path_lock(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
+}
+
+const char *
+svn_fs_fs__path_pack_lock(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, pool);
+}
+
+const char *
+svn_fs_fs__path_revprop_generation(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
+}
+
+const char *
+svn_fs_fs__path_rev_packed(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *kind,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ assert(svn_fs_fs__is_packed_rev(fs, rev));
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool,
+ "%ld" PATH_EXT_PACKED_SHARD,
+ rev / ffd->max_files_per_dir),
+ kind, SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool, "%ld",
+ rev / ffd->max_files_per_dir),
+ SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(! svn_fs_fs__is_packed_rev(fs, rev));
+
+ if (ffd->max_files_per_dir)
+ {
+ return svn_dirent_join(svn_fs_fs__path_rev_shard(fs, rev, pool),
+ apr_psprintf(pool, "%ld", rev),
+ pool);
+ }
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool, "%ld", rev), SVN_VA_NULL);
+}
+
+/* Set *PATH to the path of REV in FS with PACKED selecting whether the
+ (potential) pack file or single revision file name is returned.
+ Allocate *PATH in POOL.
+*/
+static const char *
+path_rev_absolute_internal(svn_fs_t *fs,
+ svn_revnum_t rev,
+ svn_boolean_t packed,
+ apr_pool_t *pool)
+{
+ return packed
+ ? svn_fs_fs__path_rev_packed(fs, rev, PATH_PACKED, pool)
+ : svn_fs_fs__path_rev(fs, rev, pool);
+}
+
+const char *
+svn_fs_fs__path_rev_absolute(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t is_packed = ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT
+ && svn_fs_fs__is_packed_rev(fs, rev);
+
+ return path_rev_absolute_internal(fs, rev, is_packed, pool);
+}
+
+const char *
+svn_fs_fs__path_revprops_shard(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld",
+ rev / ffd->max_files_per_dir),
+ SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_revprops_pack_shard(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / ffd->max_files_per_dir),
+ SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_revprops(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->max_files_per_dir)
+ {
+ return svn_dirent_join(svn_fs_fs__path_revprops_shard(fs, rev, pool),
+ apr_psprintf(pool, "%ld", rev),
+ pool);
+ }
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld", rev), SVN_VA_NULL);
+}
+
+/* Return TO_ADD appended to the C string representation of TXN_ID.
+ * Allocate the result in POOL.
+ */
+static const char *
+combine_txn_id_string(const svn_fs_fs__id_part_t *txn_id,
+ const char *to_add,
+ apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, svn_fs_fs__id_txn_unparse(txn_id, pool),
+ to_add, SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_txns_dir(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_dir(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
+ return svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool),
+ combine_txn_id_string(txn_id, PATH_EXT_TXN, pool),
+ pool);
+}
+
+const char*
+svn_fs_fs__path_l2p_proto_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_INDEX PATH_EXT_L2P_INDEX, pool);
+}
+
+const char*
+svn_fs_fs__path_p2l_proto_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_INDEX PATH_EXT_P2L_INDEX, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_item_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_TXN_ITEM_INDEX, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_proto_revs(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_proto_rev(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ return svn_dirent_join(svn_fs_fs__path_txn_proto_revs(fs, pool),
+ combine_txn_id_string(txn_id, PATH_EXT_REV, pool),
+ pool);
+ else
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_REV, pool);
+}
+
+
+const char *
+svn_fs_fs__path_txn_proto_rev_lock(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ return svn_dirent_join(svn_fs_fs__path_txn_proto_revs(fs, pool),
+ combine_txn_id_string(txn_id, PATH_EXT_REV_LOCK,
+ pool),
+ pool);
+ else
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
+ PATH_REV_LOCK, pool);
+}
+
+const char *
+svn_fs_fs__path_txn_node_rev(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ char *filename = (char *)svn_fs_fs__id_unparse(id, pool)->data;
+ *strrchr(filename, '.') = '\0';
+
+ return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, svn_fs_fs__id_txn_id(id),
+ pool),
+ apr_psprintf(pool, PATH_PREFIX_NODE "%s",
+ filename),
+ pool);
+}
+
+const char *
+svn_fs_fs__path_txn_node_props(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, svn_fs_fs__path_txn_node_rev(fs, id, pool),
+ PATH_EXT_PROPS, SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_txn_node_children(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, svn_fs_fs__path_txn_node_rev(fs, id, pool),
+ PATH_EXT_CHILDREN, SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_node_origin(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *node_id,
+ apr_pool_t *pool)
+{
+ char buffer[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__ui64tobase36(buffer, node_id->number);
+
+ if (len > 1)
+ buffer[len - 1] = '\0';
+
+ return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
+ buffer, SVN_VA_NULL);
+}
+
+const char *
+svn_fs_fs__path_min_unpacked_rev(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
+}
+
+svn_error_t *
+svn_fs_fs__check_file_buffer_numeric(const char *buf,
+ apr_off_t offset,
+ const char *path,
+ const char *title,
+ apr_pool_t *pool)
+{
+ const char *p;
+
+ for (p = buf + offset; *p; p++)
+ if (!svn_ctype_isdigit(*p))
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
+ title, svn_dirent_local_style(path, pool), *p, buf);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ char buf[80];
+ apr_file_t *file;
+ apr_size_t len;
+
+ SVN_ERR(svn_io_file_open(&file,
+ svn_fs_fs__path_min_unpacked_rev(fs, pool),
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT,
+ pool));
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__update_min_unpacked_rev(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
+
+ return svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs, pool);
+}
+
+svn_error_t *
+svn_fs_fs__write_min_unpacked_rev(svn_fs_t *fs,
+ svn_revnum_t revnum,
+ apr_pool_t *scratch_pool)
+{
+ const char *final_path;
+ char buf[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__i64toa(buf, revnum);
+ buf[len] = '\n';
+
+ final_path = svn_fs_fs__path_min_unpacked_rev(fs, scratch_pool);
+
+ SVN_ERR(svn_io_write_atomic(final_path, buf, len + 1,
+ final_path /* copy_perms */, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_current(svn_revnum_t *rev,
+ apr_uint64_t *next_node_id,
+ apr_uint64_t *next_copy_id,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stringbuf_t *content;
+
+ SVN_ERR(svn_fs_fs__read_content(&content,
+ svn_fs_fs__path_current(fs, pool),
+ pool));
+
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ /* When format 1 and 2 filesystems are upgraded, the 'current' file is
+ left intact. As a consequence, there is a window when a filesystem
+ has a new format, but this file still contains the IDs left from an
+ old format, i.e. looks like "359 j5 v\n". Do not be too strict here
+ and only expect a parseable revision number. */
+ SVN_ERR(svn_revnum_parse(rev, content->data, NULL));
+
+ *next_node_id = 0;
+ *next_copy_id = 0;
+ }
+ else
+ {
+ const char *str;
+
+ SVN_ERR(svn_revnum_parse(rev, content->data, &str));
+ if (*str != ' ')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+
+ *next_node_id = svn__base36toui64(&str, str + 1);
+ if (*str != ' ')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+
+ *next_copy_id = svn__base36toui64(&str, str + 1);
+ if (*str != '\n')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__write_current(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint64_t next_node_id,
+ apr_uint64_t next_copy_id,
+ apr_pool_t *pool)
+{
+ char *buf;
+ const char *name;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Now we can just write out this line. */
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ buf = apr_psprintf(pool, "%ld\n", rev);
+ }
+ else
+ {
+ char node_id_str[SVN_INT64_BUFFER_SIZE];
+ char copy_id_str[SVN_INT64_BUFFER_SIZE];
+ svn__ui64tobase36(node_id_str, next_node_id);
+ svn__ui64tobase36(copy_id_str, next_copy_id);
+
+ buf = apr_psprintf(pool, "%ld %s %s\n", rev, node_id_str, copy_id_str);
+ }
+
+ name = svn_fs_fs__path_current(fs, pool);
+ SVN_ERR(svn_io_write_atomic(name, buf, strlen(buf),
+ name /* copy_perms_path */, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__try_stringbuf_from_file(svn_stringbuf_t **content,
+ svn_boolean_t *missing,
+ const char *path,
+ svn_boolean_t last_attempt,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
+ if (missing)
+ *missing = FALSE;
+
+ if (err)
+ {
+ *content = NULL;
+
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ if (!last_attempt)
+ {
+ svn_error_clear(err);
+ if (missing)
+ *missing = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+#ifdef ESTALE
+ else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
+ || APR_TO_OS_ERROR(err->apr_err) == EIO)
+ {
+ if (!last_attempt)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ }
+#endif
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_fs__get_file_offset(apr_off_t *offset_p,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ apr_off_t offset;
+
+ /* Note that, for buffered files, one (possibly surprising) side-effect
+ of this call is to flush any unwritten data to disk. */
+ offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
+ *offset_p = offset;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_content(svn_stringbuf_t **content,
+ const char *fname,
+ apr_pool_t *pool)
+{
+ int i;
+ *content = NULL;
+
+ for (i = 0; !*content && (i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT); ++i)
+ SVN_ERR(svn_fs_fs__try_stringbuf_from_file(content, NULL,
+ fname, i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT,
+ pool));
+
+ if (!*content)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't read '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__read_number_from_stream(apr_int64_t *result,
+ svn_boolean_t *hit_eof,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *sb;
+ svn_boolean_t eof;
+ svn_error_t *err;
+
+ SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
+ if (hit_eof)
+ *hit_eof = eof;
+ else
+ if (eof)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
+
+ if (!eof)
+ {
+ err = svn_cstring_atoi64(result, sb->data);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ _("Number '%s' invalid or too large"),
+ sb->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__move_into_place(const char *old_filename,
+ const char *new_filename,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
+
+ /* Move the file into place. */
+ err = svn_io_file_rename(old_filename, new_filename, pool);
+ if (err && APR_STATUS_IS_EXDEV(err->apr_err))
+ {
+ apr_file_t *file;
+
+ /* Can't rename across devices; fall back to copying. */
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
+
+ /* Flush the target of the copy to disk. */
+ SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
+ APR_OS_DEFAULT, pool));
+ /* ### BH: Does this really guarantee a flush of the data written
+ ### via a completely different handle on all operating systems?
+ ###
+ ### Maybe we should perform the copy ourselves instead of making
+ ### apr do that and flush the real handle? */
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+ if (err)
+ return svn_error_trace(err);
+
+#ifdef __linux__
+ {
+ /* Linux has the unusual feature that fsync() on a file is not
+ enough to ensure that a file's directory entries have been
+ flushed to disk; you have to fsync the directory as well.
+ On other operating systems, we'd only be asking for trouble
+ by trying to open and fsync a directory. */
+ const char *dirname;
+ apr_file_t *file;
+
+ dirname = svn_dirent_dirname(new_filename, pool);
+ SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
+ pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_fs_fs__use_log_addressing(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return ffd->use_log_addressing;
+}
diff --git a/subversion/libsvn_fs_fs/util.h b/subversion/libsvn_fs_fs/util.h
new file mode 100644
index 0000000..328dfbc
--- /dev/null
+++ b/subversion/libsvn_fs_fs/util.h
@@ -0,0 +1,408 @@
+/* util.h --- utility functions for FSFS repo access
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__UTIL_H
+#define SVN_LIBSVN_FS__UTIL_H
+
+#include "svn_fs.h"
+#include "id.h"
+
+/* Functions for dealing with recoverable errors on mutable files
+ *
+ * Revprops, current, and txn-current files are mutable; that is, they
+ * change as part of normal fsfs operation, in constrat to revs files, or
+ * the format file, which are written once at create (or upgrade) time.
+ * When more than one host writes to the same repository, we will
+ * sometimes see these recoverable errors when accesssing these files.
+ *
+ * These errors all relate to NFS, and thus we only use this retry code if
+ * ESTALE is defined.
+ *
+ ** ESTALE
+ *
+ * In NFS v3 and under, the server doesn't track opened files. If you
+ * unlink(2) or rename(2) a file held open by another process *on the
+ * same host*, that host's kernel typically renames the file to
+ * .nfsXXXX and automatically deletes that when it's no longer open,
+ * but this behavior is not required.
+ *
+ * For obvious reasons, this does not work *across hosts*. No one
+ * knows about the opened file; not the server, and not the deleting
+ * client. So the file vanishes, and the reader gets stale NFS file
+ * handle.
+ *
+ ** EIO, ENOENT
+ *
+ * Some client implementations (at least the 2.6.18.5 kernel that ships
+ * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
+ * even EIO errors when trying to read these files that have been renamed
+ * over on some other host.
+ *
+ ** Solution
+ *
+ * Try open and read of such files in try_stringbuf_from_file(). Call
+ * this function within a loop of SVN_FS_FS__RECOVERABLE_RETRY_COUNT
+ * iterations (though, realistically, the second try will succeed).
+ */
+
+#define SVN_FS_FS__RECOVERABLE_RETRY_COUNT 10
+
+/* Return TRUE is REV is packed in FS, FALSE otherwise. */
+svn_boolean_t
+svn_fs_fs__is_packed_rev(svn_fs_t *fs,
+ svn_revnum_t rev);
+
+/* Return TRUE is REV's props have been packed in FS, FALSE otherwise. */
+svn_boolean_t
+svn_fs_fs__is_packed_revprop(svn_fs_t *fs,
+ svn_revnum_t rev);
+
+/* Return the first revision in the pack / rev file containing REVISION in
+ * filesystem FS. For non-packed revs, this will simply be REVISION. */
+svn_revnum_t
+svn_fs_fs__packed_base_rev(svn_fs_t *fs,
+ svn_revnum_t revision);
+
+/* Return the full path of the rev shard directory that will contain
+ * revision REV in FS. Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_rev_shard(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the full path of the non-packed rev file containing revision REV
+ * in FS. Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_rev(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the path of the pack-related file that for revision REV in FS.
+ * KIND specifies the file name base, e.g. "manifest" or "pack".
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_rev_packed(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *kind,
+ apr_pool_t *pool);
+
+/* Return the full path of the "txn-current" file in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_current(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the full path of the "txn-current-lock" file in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_current_lock(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the full path of the global write lock file in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_lock(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the full path of the pack operation lock file in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_pack_lock(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the full path of the revprop generation file in FS.
+ * Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_revprop_generation(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the full path of the revision properties pack shard directory
+ * that will contain the packed properties of revision REV in FS.
+ * Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_revprops_pack_shard(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set *PATH to the path of REV in FS, whether in a pack file or not.
+ Allocate *PATH in POOL.
+
+ Note: If the caller does not have the write lock on FS, then the path is
+ not guaranteed to be correct or to remain correct after the function
+ returns, because the revision might become packed before or after this
+ call. If a file exists at that path, then it is correct; if not, then
+ the caller should call update_min_unpacked_rev() and re-try once. */
+const char *
+svn_fs_fs__path_rev_absolute(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the full path of the revision properties shard directory that
+ * will contain the properties of revision REV in FS.
+ * Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_revprops_shard(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the full path of the non-packed revision properties file that
+ * contains the props for revision REV in FS. Allocate the result in POOL.
+ */
+const char *
+svn_fs_fs__path_revprops(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the path of the file storing the oldest non-packed revision in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_min_unpacked_rev(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the path of the 'transactions' directory in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txns_dir(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the path of the directory containing the transaction TXN_ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_dir(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the 'txn-protorevs' directory in FS, even if that
+ * folder may not exist in FS. The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_proto_revs(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return the path of the proto-revision file for transaction TXN_ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_proto_rev(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the proto-revision lock file for transaction TXN_ID
+ * in FS. The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_proto_rev_lock(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the in-transaction node revision
+ * identified by ID in FS. The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_node_rev(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the in-transaction properties of
+ * the node identified by ID in FS. The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_node_props(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the directory entries of the
+ * in-transaction directory node identified by ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_node_children(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the log-to-phys index for
+ * the transaction identified by TXN_ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char*
+svn_fs_fs__path_l2p_proto_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the phys-to-log index for
+ * the transaction identified by TXN_ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char*
+svn_fs_fs__path_p2l_proto_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing item_index counter for
+ * the transaction identified by TXN_ID in FS.
+ * The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_txn_item_index(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *txn_id,
+ apr_pool_t *pool);
+
+/* Return the path of the file containing the node origins cachs for
+ * the given NODE_ID in FS. The result will be allocated in POOL.
+ */
+const char *
+svn_fs_fs__path_node_origin(svn_fs_t *fs,
+ const svn_fs_fs__id_part_t *node_id,
+ apr_pool_t *pool);
+
+/* Set *MIN_UNPACKED_REV to the integer value read from the file returned
+ * by #svn_fs_fs__path_min_unpacked_rev() for FS.
+ * Use POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Check that BUF, a nul-terminated buffer of text from file PATH,
+ contains only digits at OFFSET and beyond, raising an error if not.
+ TITLE contains a user-visible description of the file, usually the
+ short file name.
+
+ Uses POOL for temporary allocation. */
+svn_error_t *
+svn_fs_fs__check_file_buffer_numeric(const char *buf,
+ apr_off_t offset,
+ const char *path,
+ const char *title,
+ apr_pool_t *pool);
+
+/* Re-read the MIN_UNPACKED_REV member of FS from disk.
+ * Use POOL for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__update_min_unpacked_rev(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Atomically update the 'min-unpacked-rev' file in FS to hold the specifed
+ * REVNUM. Perform temporary allocations in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_fs_fs__write_min_unpacked_rev(svn_fs_t *fs,
+ svn_revnum_t revnum,
+ apr_pool_t *scratch_pool);
+
+/* Set *REV, *NEXT_NODE_ID and *NEXT_COPY_ID to the values read from the
+ * 'current' file. For new FS formats, which only store the youngest
+ * revision, set the *NEXT_NODE_ID and *NEXT_COPY_ID to 0. Perform
+ * temporary allocations in POOL.
+ */
+svn_error_t *
+svn_fs_fs__read_current(svn_revnum_t *rev,
+ apr_uint64_t *next_node_id,
+ apr_uint64_t *next_copy_id,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Atomically update the 'current' file to hold the specifed REV,
+ NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
+ ignored and may be 0 if the FS format does not use them.)
+ Perform temporary allocations in POOL. */
+svn_error_t *
+svn_fs_fs__write_current(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint64_t next_node_id,
+ apr_uint64_t next_copy_id,
+ apr_pool_t *pool);
+
+/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
+ * not be modified unless the whole file was read successfully.
+ *
+ * ESTALE, EIO and ENOENT will not cause this function to return an error
+ * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate
+ * missing files (ENOENT) there.
+ *
+ * Use POOL for allocations.
+ */
+svn_error_t *
+svn_fs_fs__try_stringbuf_from_file(svn_stringbuf_t **content,
+ svn_boolean_t *missing,
+ const char *path,
+ svn_boolean_t last_attempt,
+ apr_pool_t *pool);
+
+/* Fetch the current offset of FILE into *OFFSET_P. */
+svn_error_t *
+svn_fs_fs__get_file_offset(apr_off_t *offset_p,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+/* Read the file FNAME and store the contents in *BUF.
+ Allocations are performed in POOL. */
+svn_error_t *
+svn_fs_fs__read_content(svn_stringbuf_t **content,
+ const char *fname,
+ apr_pool_t *pool);
+
+/* Reads a line from STREAM and converts it to a 64 bit integer to be
+ * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
+ * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
+ * error return.
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+svn_error_t *
+svn_fs_fs__read_number_from_stream(apr_int64_t *result,
+ svn_boolean_t *hit_eof,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool);
+
+/* Move a file into place from OLD_FILENAME in the transactions
+ directory to its final location NEW_FILENAME in the repository. On
+ Unix, match the permissions of the new file to the permissions of
+ PERMS_REFERENCE. Temporary allocations are from POOL.
+
+ This function almost duplicates svn_io_file_move(), but it tries to
+ guarantee a flush. */
+svn_error_t *
+svn_fs_fs__move_into_place(const char *old_filename,
+ const char *new_filename,
+ const char *perms_reference,
+ apr_pool_t *pool);
+
+/* Return TRUE, iff FS uses logical addressing. */
+svn_boolean_t
+svn_fs_fs__use_log_addressing(svn_fs_t *fs);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/verify.c b/subversion/libsvn_fs_fs/verify.c
new file mode 100644
index 0000000..0fa314b
--- /dev/null
+++ b/subversion/libsvn_fs_fs/verify.c
@@ -0,0 +1,883 @@
+/* verify.c --- verification of FSFS filesystems
+ *
+ * ====================================================================
+ * 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_sorts.h"
+#include "svn_checksum.h"
+#include "svn_time.h"
+#include "private/svn_subr_private.h"
+
+#include "verify.h"
+#include "fs_fs.h"
+
+#include "cached_data.h"
+#include "rep-cache.h"
+#include "util.h"
+#include "index.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+
+/** Verifying. **/
+
+/* Baton type expected by verify_walker(). The purpose is to reuse open
+ * rev / pack file handles between calls. Its contents need to be cleaned
+ * periodically to limit resource usage.
+ */
+typedef struct verify_walker_baton_t
+{
+ /* number of calls to verify_walker() since the last clean */
+ int iteration_count;
+
+ /* number of files opened since the last clean */
+ int file_count;
+
+ /* progress notification callback to invoke periodically (may be NULL) */
+ svn_fs_progress_notify_func_t notify_func;
+
+ /* baton to use with NOTIFY_FUNC */
+ void *notify_baton;
+
+ /* remember the last revision for which we called notify_func */
+ svn_revnum_t last_notified_revision;
+
+ /* cached hint for successive calls to svn_fs_fs__check_rep() */
+ void *hint;
+
+ /* pool to use for the file handles etc. */
+ apr_pool_t *pool;
+} verify_walker_baton_t;
+
+/* Used by svn_fs_fs__verify().
+ Implements svn_fs_fs__walk_rep_reference().walker. */
+static svn_error_t *
+verify_walker(representation_t *rep,
+ void *baton,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ verify_walker_baton_t *walker_baton = baton;
+ void *previous_hint;
+
+ /* notify and free resources periodically */
+ if ( walker_baton->iteration_count > 1000
+ || walker_baton->file_count > 16)
+ {
+ if ( walker_baton->notify_func
+ && rep->revision != walker_baton->last_notified_revision)
+ {
+ walker_baton->notify_func(rep->revision,
+ walker_baton->notify_baton,
+ scratch_pool);
+ walker_baton->last_notified_revision = rep->revision;
+ }
+
+ svn_pool_clear(walker_baton->pool);
+
+ walker_baton->iteration_count = 0;
+ walker_baton->file_count = 0;
+ walker_baton->hint = NULL;
+ }
+
+ /* access the repo data */
+ previous_hint = walker_baton->hint;
+ SVN_ERR(svn_fs_fs__check_rep(rep, fs, &walker_baton->hint,
+ walker_baton->pool));
+
+ /* update resource usage counters */
+ walker_baton->iteration_count++;
+ if (previous_hint != walker_baton->hint)
+ walker_baton->file_count++;
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify the rep cache DB's consistency with our rev / pack data.
+ * The function signature is similar to svn_fs_fs__verify.
+ * The values of START and END have already been auto-selected and
+ * verified.
+ */
+static svn_error_t *
+verify_rep_cache(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t exists;
+
+ /* rep-cache verification. */
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
+ if (exists)
+ {
+ /* provide a baton to allow the reuse of open file handles between
+ iterations (saves 2/3 of OS level file operations). */
+ verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->pool = svn_pool_create(pool);
+ baton->last_notified_revision = SVN_INVALID_REVNUM;
+ baton->notify_func = notify_func;
+ baton->notify_baton = notify_baton;
+
+ /* tell the user that we are now ready to do *something* */
+ if (notify_func)
+ notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
+
+ /* Do not attempt to walk the rep-cache database if its file does
+ not exist, since doing so would create it --- which may confuse
+ the administrator. Don't take any lock. */
+ SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
+ verify_walker, baton,
+ cancel_func, cancel_baton,
+ pool));
+
+ /* walker resource cleanup */
+ svn_pool_destroy(baton->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the MD5 checksum of the data between offsets START and END
+ * in FILE matches the EXPECTED checksum. If there is a mismatch use the
+ * indedx NAME in the error message. Supports cancellation with CANCEL_FUNC
+ * and CANCEL_BATON. SCRATCH_POOL is for temporary allocations. */
+static svn_error_t *
+verify_index_checksum(apr_file_t *file,
+ const char *name,
+ apr_off_t start,
+ apr_off_t end,
+ svn_checksum_t *expected,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ unsigned char buffer[SVN__STREAM_CHUNK_SIZE];
+ apr_off_t size = end - start;
+ svn_checksum_t *actual;
+ svn_checksum_ctx_t *context
+ = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
+
+ /* Calculate the index checksum. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &start, scratch_pool));
+ while (size > 0)
+ {
+ apr_size_t to_read = size > sizeof(buffer)
+ ? sizeof(buffer)
+ : (apr_size_t)size;
+ SVN_ERR(svn_io_file_read_full2(file, buffer, to_read, NULL, NULL,
+ scratch_pool));
+ SVN_ERR(svn_checksum_update(context, buffer, to_read));
+ size -= to_read;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ SVN_ERR(svn_checksum_final(&actual, context, scratch_pool));
+
+ /* Verify that it matches the expected checksum. */
+ if (!svn_checksum_match(expected, actual))
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, scratch_pool));
+ SVN_ERR(svn_checksum_mismatch_err(expected, actual, scratch_pool,
+ _("%s checksum mismatch in file %s"),
+ name, file_name));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify the MD5 checksums of the index data in the rev / pack file
+ * containing revision START in FS. If given, invoke CANCEL_FUNC with
+ * CANCEL_BATON at regular intervals. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static svn_error_t *
+verify_index_checksums(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_fs__revision_file_t *rev_file;
+
+ /* Open the rev / pack file and read the footer */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+
+ /* Verify the index contents against the checksum from the footer. */
+ SVN_ERR(verify_index_checksum(rev_file->file, "L2P index",
+ rev_file->l2p_offset, rev_file->p2l_offset,
+ rev_file->l2p_checksum,
+ cancel_func, cancel_baton, scratch_pool));
+ SVN_ERR(verify_index_checksum(rev_file->file, "P2L index",
+ rev_file->p2l_offset, rev_file->footer_offset,
+ rev_file->p2l_checksum,
+ cancel_func, cancel_baton, scratch_pool));
+
+ /* Done. */
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all log-to-phys index entries for revisions START to
+ * START + COUNT-1 in FS there is a consistent entry in the phys-to-log
+ * index. If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
+ * intervals. Use POOL for allocations.
+ */
+static svn_error_t *
+compare_l2p_to_p2l_index(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_revnum_t i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_array_header_t *max_ids;
+
+ /* common file access structure */
+ svn_fs_fs__revision_file_t *rev_file;
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start, pool,
+ iterpool));
+
+ /* determine the range of items to check for each revision */
+ SVN_ERR(svn_fs_fs__l2p_get_max_ids(&max_ids, fs, start, count, pool,
+ iterpool));
+
+ /* check all items in all revisions if the given range */
+ for (i = 0; i < max_ids->nelts; ++i)
+ {
+ apr_uint64_t k;
+ apr_uint64_t max_id = APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
+ svn_revnum_t revision = start + i;
+
+ for (k = 0; k < max_id; ++k)
+ {
+ apr_off_t offset;
+ svn_fs_fs__p2l_entry_t *p2l_entry;
+ svn_pool_clear(iterpool);
+
+ /* get L2P entry. Ignore unused entries. */
+ SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, revision,
+ NULL, k, iterpool));
+ if (offset == -1)
+ continue;
+
+ /* find the corresponding P2L entry */
+ SVN_ERR(svn_fs_fs__p2l_entry_lookup(&p2l_entry, fs, rev_file,
+ revision, offset, iterpool,
+ iterpool));
+
+ if (p2l_entry == NULL)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index entry not found for "
+ "PHYS %s returned by "
+ "l2p index for LOG r%ld:i%ld"),
+ apr_off_t_toa(pool, offset),
+ revision, (long)k);
+
+ if ( p2l_entry->item.number != k
+ || p2l_entry->item.revision != revision)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index info LOG r%ld:i%ld"
+ " does not match "
+ "l2p index for LOG r%ld:i%ld"),
+ p2l_entry->item.revision,
+ (long)p2l_entry->item.number,
+ revision, (long)k);
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all phys-to-log index entries for revisions START to
+ * START + COUNT-1 in FS there is a consistent entry in the log-to-phys
+ * index. If given, invoke CANCEL_FUNC with CANCEL_BATON at regular
+ * intervals. Use POOL for allocations.
+ *
+ * Please note that we can only check on pack / rev file granularity and
+ * must only be called for a single rev / pack file.
+ */
+static svn_error_t *
+compare_p2l_to_l2p_index(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_off_t max_offset;
+ apr_off_t offset = 0;
+
+ /* common file access structure */
+ svn_fs_fs__revision_file_t *rev_file;
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start, pool,
+ iterpool));
+
+ /* get the size of the rev / pack file as covered by the P2L index */
+ SVN_ERR(svn_fs_fs__p2l_get_max_offset(&max_offset, fs, rev_file, start,
+ pool));
+
+ /* for all offsets in the file, get the P2L index entries and check
+ them against the L2P index */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+ svn_fs_fs__p2l_entry_t *last_entry;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* get all entries for the current block */
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, fs, rev_file, start,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+ if (entries->nelts == 0)
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION,
+ NULL,
+ _("p2l does not cover offset %s"
+ " for revision %ld"),
+ apr_off_t_toa(pool, offset), start);
+
+ /* process all entries (and later continue with the next block) */
+ last_entry
+ = &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_fs__p2l_entry_t);
+ offset = last_entry->offset + last_entry->size;
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+
+ /* check all sub-items for consist entries in the L2P index */
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ {
+ /* There is no L2P entry for unused rev file sections.
+ * And its P2L index data is hardly ever used. But we
+ * should still check whether someone tempered with it. */
+ if ( entry->item.revision != SVN_INVALID_REVNUM
+ && ( entry->item.revision < start
+ || entry->item.revision >= start + count))
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("Empty P2L entry for PHYS %s "
+ "refers to revision %ld outside "
+ "the rev / pack file (%ld-%ld)"),
+ apr_off_t_toa(pool, entry->offset),
+ entry->item.revision,
+ start, start + count - 1);
+ }
+ else
+ {
+ apr_off_t l2p_offset;
+ SVN_ERR(svn_fs_fs__item_offset(&l2p_offset, fs, rev_file,
+ entry->item.revision, NULL,
+ entry->item.number, iterpool));
+
+ if (l2p_offset != entry->offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("l2p index entry PHYS %s"
+ "does not match p2l index value "
+ "LOG r%ld:i%ld for PHYS %s"),
+ apr_off_t_toa(pool, l2p_offset),
+ entry->item.revision,
+ (long)entry->item.number,
+ apr_off_t_toa(pool, entry->offset));
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Items smaller than this can be read at once into a buffer and directly
+ * be checksummed. Larger items require stream processing.
+ * Must be a multiple of 8. */
+#define STREAM_THRESHOLD 4096
+
+/* Verify that the next SIZE bytes read from FILE are NUL.
+ * SIZE must not exceed STREAM_THRESHOLD. Use POOL for allocations.
+ */
+static svn_error_t *
+expect_buffer_nul(apr_file_t *file,
+ apr_off_t size,
+ apr_pool_t *pool)
+{
+ union
+ {
+ unsigned char buffer[STREAM_THRESHOLD];
+ apr_uint64_t chunks[STREAM_THRESHOLD / sizeof(apr_uint64_t)];
+ } data;
+
+ apr_size_t i;
+ SVN_ERR_ASSERT(size <= STREAM_THRESHOLD);
+
+ /* read the whole data block; error out on failure */
+ data.chunks[(size - 1)/ sizeof(apr_uint64_t)] = 0;
+ SVN_ERR(svn_io_file_read_full2(file, data.buffer, size, NULL, NULL, pool));
+
+ /* chunky check */
+ for (i = 0; i < size / sizeof(apr_uint64_t); ++i)
+ if (data.chunks[i] != 0)
+ break;
+
+ /* byte-wise check upon mismatch or at the end of the block */
+ for (i *= sizeof(apr_uint64_t); i < size; ++i)
+ if (data.buffer[i] != 0)
+ {
+ const char *file_name;
+ apr_off_t offset;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, pool));
+ SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+ offset -= size - i;
+
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Empty section in file %s contains "
+ "non-NUL data at offset %s"),
+ file_name, apr_off_t_toa(pool, offset));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the next SIZE bytes read from FILE are NUL.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+read_all_nul(apr_file_t *file,
+ apr_off_t size,
+ apr_pool_t *pool)
+{
+ for (; size >= STREAM_THRESHOLD; size -= STREAM_THRESHOLD)
+ SVN_ERR(expect_buffer_nul(file, STREAM_THRESHOLD, pool));
+
+ if (size)
+ SVN_ERR(expect_buffer_nul(file, size, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Compare the ACTUAL checksum with the one expected by ENTRY.
+ * Return an error in case of mismatch. Use the name of FILE
+ * in error message. Allocate data in POOL.
+ */
+static svn_error_t *
+expected_checksum(apr_file_t *file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_uint32_t actual,
+ apr_pool_t *pool)
+{
+ if (actual != entry->fnv1_checksum)
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_file_name_get(&file_name, file, pool));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Checksum mismatch in item at offset %s of "
+ "length %s bytes in file %s"),
+ apr_off_t_toa(pool, entry->offset),
+ apr_off_t_toa(pool, entry->size), file_name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the FNV checksum over the next ENTRY->SIZE bytes read
+ * from FILE will match ENTRY's expected checksum. SIZE must not
+ * exceed STREAM_THRESHOLD. Use POOL for allocations.
+ */
+static svn_error_t *
+expected_buffered_checksum(apr_file_t *file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ unsigned char buffer[STREAM_THRESHOLD];
+ SVN_ERR_ASSERT(entry->size <= STREAM_THRESHOLD);
+
+ SVN_ERR(svn_io_file_read_full2(file, buffer, (apr_size_t)entry->size,
+ NULL, NULL, pool));
+ SVN_ERR(expected_checksum(file, entry,
+ svn__fnv1a_32x4(buffer, (apr_size_t)entry->size),
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the FNV checksum over the next ENTRY->SIZE bytes read from
+ * FILE will match ENTRY's expected checksum. Use POOL for allocations.
+ */
+static svn_error_t *
+expected_streamed_checksum(apr_file_t *file,
+ svn_fs_fs__p2l_entry_t *entry,
+ apr_pool_t *pool)
+{
+ unsigned char buffer[STREAM_THRESHOLD];
+ svn_checksum_t *checksum;
+ svn_checksum_ctx_t *context
+ = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
+ apr_off_t size = entry->size;
+
+ while (size > 0)
+ {
+ apr_size_t to_read = size > sizeof(buffer)
+ ? sizeof(buffer)
+ : (apr_size_t)size;
+ SVN_ERR(svn_io_file_read_full2(file, buffer, to_read, NULL, NULL,
+ pool));
+ SVN_ERR(svn_checksum_update(context, buffer, to_read));
+ size -= to_read;
+ }
+
+ SVN_ERR(svn_checksum_final(&checksum, context, pool));
+ SVN_ERR(expected_checksum(file, entry,
+ ntohl(*(const apr_uint32_t *)checksum->digest),
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that for all phys-to-log index entries for revisions START to
+ * START + COUNT-1 in FS match the actual pack / rev file contents.
+ * If given, invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
+ * Use POOL for allocations.
+ *
+ * Please note that we can only check on pack / rev file granularity and
+ * must only be called for a single rev / pack file.
+ */
+static svn_error_t *
+compare_p2l_to_rev(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t count,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_off_t max_offset;
+ apr_off_t offset = 0;
+ svn_fs_fs__revision_file_t *rev_file;
+
+ /* open the pack / rev file that is covered by the p2l index */
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start, pool,
+ iterpool));
+
+ /* check file size vs. range covered by index */
+ SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
+ SVN_ERR(svn_fs_fs__p2l_get_max_offset(&max_offset, fs, rev_file, start,
+ pool));
+
+ if (rev_file->l2p_offset != max_offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT, NULL,
+ _("File size of %s for revision r%ld does "
+ "not match p2l index size of %s"),
+ apr_off_t_toa(pool, rev_file->l2p_offset), start,
+ apr_off_t_toa(pool, max_offset));
+
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, ffd->block_size, NULL, 0,
+ pool));
+
+ /* for all offsets in the file, get the P2L index entries and check
+ them against the L2P index */
+ for (offset = 0; offset < max_offset; )
+ {
+ apr_array_header_t *entries;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* get all entries for the current block */
+ SVN_ERR(svn_fs_fs__p2l_index_lookup(&entries, fs, rev_file, start,
+ offset, ffd->p2l_page_size,
+ iterpool, iterpool));
+
+ /* The above might have moved the file pointer.
+ * Ensure we actually start reading at OFFSET. */
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file, ffd->block_size,
+ NULL, offset, iterpool));
+
+ /* process all entries (and later continue with the next block) */
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ svn_fs_fs__p2l_entry_t *entry
+ = &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
+
+ /* skip bits we previously checked */
+ if (i == 0 && entry->offset < offset)
+ continue;
+
+ /* skip zero-sized entries */
+ if (entry->size == 0)
+ continue;
+
+ /* p2l index must cover all rev / pack file offsets exactly once */
+ if (entry->offset != offset)
+ return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
+ NULL,
+ _("p2l index entry for revision r%ld"
+ " is non-contiguous between offsets "
+ " %s and %s"),
+ start,
+ apr_off_t_toa(pool, offset),
+ apr_off_t_toa(pool, entry->offset));
+
+ /* empty sections must contain NUL bytes only */
+ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
+ {
+ /* skip filler entry at the end of the p2l index */
+ if (entry->offset != max_offset)
+ SVN_ERR(read_all_nul(rev_file->file, entry->size, pool));
+ }
+ else
+ {
+ if (entry->size < STREAM_THRESHOLD)
+ SVN_ERR(expected_buffered_checksum(rev_file->file, entry,
+ pool));
+ else
+ SVN_ERR(expected_streamed_checksum(rev_file->file, entry,
+ pool));
+ }
+
+ /* advance offset */
+ offset += entry->size;
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the revprops of the revisions START to END in FS can be
+ * accessed. Invoke CANCEL_FUNC with CANCEL_BATON at regular intervals.
+ *
+ * The values of START and END have already been auto-selected and
+ * verified.
+ */
+static svn_error_t *
+verify_revprops(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_revnum_t revision;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (revision = start; revision < end; ++revision)
+ {
+ svn_string_t *date;
+ apr_time_t timetemp;
+
+ svn_pool_clear(iterpool);
+
+ /* Access the svn:date revprop.
+ * This implies parsing all revprops for that revision. */
+ SVN_ERR(svn_fs_fs__revision_prop(&date, fs, revision,
+ SVN_PROP_REVISION_DATE, iterpool));
+
+ /* The time stamp is the only revprop that, if given, needs to
+ * have a valid content. */
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&timetemp, date->data, iterpool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_revnum_t
+pack_size(svn_fs_t *fs, svn_revnum_t rev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ return rev < ffd->min_unpacked_rev ? ffd->max_files_per_dir : 1;
+}
+
+/* Verify that on-disk representation has not been tempered with (in a way
+ * that leaves the repository in a corrupted state). This compares log-to-
+ * phys with phys-to-log indexes, verifies the low-level checksums and
+ * checks that all revprops are available. The function signature is
+ * similar to svn_fs_fs__verify.
+ *
+ * The values of START and END have already been auto-selected and
+ * verified. You may call this for format7 or higher repos.
+ */
+static svn_error_t *
+verify_f7_metadata_consistency(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_revnum_t revision, next_revision;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (revision = start; revision <= end; revision = next_revision)
+ {
+ svn_error_t *err = SVN_NO_ERROR;
+
+ svn_revnum_t count = pack_size(fs, revision);
+ svn_revnum_t pack_start = svn_fs_fs__packed_base_rev(fs, revision);
+ svn_revnum_t pack_end = pack_start + count;
+
+ svn_pool_clear(iterpool);
+
+ if (notify_func && (pack_start % ffd->max_files_per_dir == 0))
+ notify_func(pack_start, notify_baton, iterpool);
+
+ /* Check for external corruption to the indexes. */
+ err = verify_index_checksums(fs, pack_start, cancel_func,
+ cancel_baton, iterpool);
+
+ /* two-way index check */
+ if (!err)
+ err = compare_l2p_to_p2l_index(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+ if (!err)
+ err = compare_p2l_to_l2p_index(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+
+ /* verify in-index checksums and types vs. actual rev / pack files */
+ if (!err)
+ err = compare_p2l_to_rev(fs, pack_start, pack_end - pack_start,
+ cancel_func, cancel_baton, iterpool);
+
+ /* ensure that revprops are available and accessible */
+ if (!err)
+ err = verify_revprops(fs, pack_start, pack_end,
+ cancel_func, cancel_baton, iterpool);
+
+ /* concurrent packing is one of the reasons why verification may fail.
+ Make sure, we operate on up-to-date information. */
+ if (err)
+ {
+ svn_error_t *err2
+ = svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev,
+ fs, pool);
+
+ /* Be careful to not leak ERR. */
+ if (err2)
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ }
+
+ /* retry the whole shard if it got packed in the meantime */
+ if (err && count != pack_size(fs, revision))
+ {
+ svn_error_clear(err);
+
+ /* We could simply assign revision here but the code below is
+ more intuitive to maintainers. */
+ next_revision = svn_fs_fs__packed_base_rev(fs, revision);
+ }
+ else
+ {
+ SVN_ERR(err);
+ next_revision = pack_end;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__verify(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
+
+ /* Input validation. */
+ if (! SVN_IS_VALID_REVNUM(start))
+ start = 0;
+ if (! SVN_IS_VALID_REVNUM(end))
+ end = youngest;
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(start, fs, pool));
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(end, fs, pool));
+
+ /* log/phys index consistency. We need to check them first to make
+ sure we can access the rev / pack files in format7. */
+ if (svn_fs_fs__use_log_addressing(fs))
+ SVN_ERR(verify_f7_metadata_consistency(fs, start, end,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool));
+
+ /* rep cache consistency */
+ if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ SVN_ERR(verify_rep_cache(fs, start, end, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/verify.h b/subversion/libsvn_fs_fs/verify.h
new file mode 100644
index 0000000..650a35b
--- /dev/null
+++ b/subversion/libsvn_fs_fs/verify.h
@@ -0,0 +1,42 @@
+/* verify.h : verification interface of the native filesystem layer
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__VERIFY_H
+#define SVN_LIBSVN_FS__VERIFY_H
+
+#include "fs.h"
+
+/* Verify metadata in fsfs filesystem FS. Limit the checks to revisions
+ * START to END where possible. Indicate progress via the optional
+ * NOTIFY_FUNC callback using NOTIFY_BATON. The optional CANCEL_FUNC
+ * will periodically be called with CANCEL_BATON to allow for preemption.
+ * Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__verify(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+#endif