You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/07/17 14:39:49 UTC
svn commit: r1504100 [2/5] - in
/subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs: dag.c fs.c
fs_fs.c fs_fs.h pack.c transaction.c transaction.h tree.c
Modified: subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/fs_fs.c?rev=1504100&r1=1504099&r2=1504100&view=diff
==============================================================================
--- subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/fsfs-improvements/subversion/libsvn_fs_fs/fs_fs.c Wed Jul 17 12:39:48 2013
@@ -64,6 +64,7 @@
#include "rep-cache.h"
#include "revprops.h"
#include "temp_serializer.h"
+#include "transaction.h"
#include "util.h"
#include "private/svn_string_private.h"
@@ -110,17 +111,6 @@ 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 *
@@ -152,53 +142,6 @@ path_lock(svn_fs_t *fs, apr_pool_t *pool
return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
}
-/* 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(svn_fs_fs__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(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 char *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_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
- PATH_NEXT_IDS, pool);
-}
-
-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(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
- PATH_REV_LOCK, pool);
-}
-
static APR_INLINE const char *
path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
{
@@ -209,127 +152,8 @@ path_node_origin(svn_fs_t *fs, const cha
node_id_minus_last_char, NULL);
}
-static APR_INLINE const char *
-path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
-{
- const char *path;
- apr_off_t offset = 0;
-
- 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);
-}
-
-
-/* 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)
-{
- 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;
-}
-
-/* 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 char *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 (strcmp(txn->txn_id, txn_id) == 0)
- 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;
-}
-
-
/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
static svn_error_t *
get_lock_on_filesystem(const char *lock_filename,
@@ -447,233 +271,6 @@ svn_fs_fs__with_txn_current_lock(svn_fs_
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;
-}
-
-/* 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)
-{
- 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);
-}
-
-/* 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)
-{
- struct unlock_proto_rev_baton b;
-
- b.txn_id = txn_id;
- b.lockcookie = lockcookie;
- return unlock_proto_rev_body(fs, &b, 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)
-{
- 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,
- svn_fs_fs__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.
-
- 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 char *txn_id,
- apr_pool_t *pool)
-{
- struct get_writable_proto_rev_baton b;
-
- b.file = file;
- b.lockcookie = lockcookie;
- b.txn_id = txn_id;
-
- return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, 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);
-}
@@ -1335,128 +932,6 @@ svn_fs_fs__revision_exists(svn_revnum_t
}
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,
- 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 POOL for temporary allocations.
- */
-static svn_error_t *
-store_sha1_rep_mapping(svn_fs_t *fs,
- 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)
- {
- 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);
- svn_stringbuf_t *rep_string
- = svn_fs_fs__unparse_representation(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->data,
- rep_string->len, NULL, pool));
-
- SVN_ERR(svn_io_file_close(rep_file, 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) ? SVN_FS_FS__KIND_FILE
- : SVN_FS_FS__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;
-}
-
-
-svn_error_t *
svn_fs_fs__file_length(svn_filesize_t *length,
node_revision_t *noderev,
apr_pool_t *pool)
@@ -1541,2567 +1016,71 @@ svn_fs_fs__rep_copy(representation_t *re
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)))
- {
- /* 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;
+/* Write out the zeroth revision for filesystem FS. */
+static svn_error_t *
+write_revision_zero(svn_fs_t *fs)
+{
+ const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, fs->pool);
+ apr_hash_t *proplist;
+ svn_string_t date;
- 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;
+ /* 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));
- 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;
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
+ date.len = strlen(date.data);
+ proplist = apr_hash_make(fs->pool);
+ svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
+ return svn_fs_fs__set_revision_proplist(fs, 0, proplist, fs->pool);
+}
- 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;
- }
+svn_error_t *
+svn_fs_fs__create(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ int format = SVN_FS_FS__FORMAT_NUMBER;
+ fs_fs_data_t *ffd = fs->fsap_data;
- /* Point our new_change to our (possibly modified) old_change. */
- new_change = old_change;
- }
- else
+ fs->path = apr_pstrdup(pool, path);
+ /* See if compatibility with older versions was explicitly requested. */
+ if (fs->config)
{
- /* 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 (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;
}
+ ffd->format = format;
- if (new_change)
- new_change->node_kind = change->node_kind;
+ /* Override the default linear layout if this is a new-enough format. */
+ if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+ ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
- /* 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);
- }
-
- 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;
-}
-
-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(svn_fs_fs__read_changes(&changes,
- svn_stream_from_aprfile2(file, TRUE,
- scratch_pool),
- 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;
-}
-
-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(svn_fs_fs__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
- = svn_fs_fs__path_txn_current(cb->fs, pool);
- char next_txn_id[MAX_KEY_SIZE+3];
- apr_size_t len;
-
- svn_stringbuf_t *buf;
- SVN_ERR(svn_fs_fs__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_atomic(txn_current_filename, next_txn_id, len,
- 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. 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(svn_fs_fs__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)
- {
- /* 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);
- 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(svn_fs_fs__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)
-{
- 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_atomic(path_txn_props(txn->fs, txn->id, pool),
- buf->data, buf->len,
- NULL /* copy_perms_path */, pool));
- return SVN_NO_ERROR;
-}
-
-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(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(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
- = 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 || !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;
- 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_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 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;
- apr_hash_t *changes = apr_hash_make(pool);
-
- 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_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);
-}
-
-/* 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)
-{
- /* 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;
- svn_boolean_t maybe_shared_rep = FALSE;
-
- /* If we have no predecessors, 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);
-
- /* 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)
- {
- *rep = NULL;
- 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).
- *
- * Message-ID: <CA...@mail.gmail.com>
- */
- 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, its parent chain may be different
- * from the node-rev parent chain. */
- if (*rep && maybe_shared_rep)
- {
- /* Check whether the length of the deltification chain is acceptable.
- * Otherwise, shared reps may form a non-skipping delta chain in
- * extreme cases. */
- int chain_length = 0;
- SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, *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;
- }
-
- 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;
- 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->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(svn_fs_fs__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(svn_fs_fs__get_contents(&source, fs, base_rep, b->pool));
-
- /* Write out the rep header. */
- if (base_rep)
- {
- header.base_revision = base_rep->revision;
- header.base_offset = base_rep->offset;
- 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->pool));
-
- /* Now determine the offset of the actual svndiff data. */
- SVN_ERR(svn_fs_fs__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 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.
- 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(svn_fs_fs__check_rep(*old_rep, fs, NULL, NULL, 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(svn_fs_fs__parse_representation(old_rep, rep_string, 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;
-
- 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(svn_fs_fs__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);
- 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;
- }
-
- /* 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);
-
- 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
- = 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 || !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(svn_fs_fs__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(svn_fs_fs__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 = 0;
- }
-
- 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;
- svn_fs_fs__rep_header_t header = { 0 };
-
- 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(svn_fs_fs__get_contents(&source, fs, base_rep, pool));
-
- SVN_ERR(svn_fs_fs__get_file_offset(&rep->offset, file, pool));
-
- /* Write out the rep header. */
- if (base_rep)
- {
- header.base_revision = base_rep->revision;
- header.base_offset = base_rep->offset;
- header.base_length = base_rep->size;
- header.type = svn_fs_fs__rep_delta;
- }
- else
- {
- header.type = svn_fs_fs__rep_self_delta;
- }
-
- file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
- SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, 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(svn_fs_fs__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)
-{
- 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,
[... 919 lines stripped ...]