diff options
Diffstat (limited to 'subversion/libsvn_fs_base/revs-txns.c')
-rw-r--r-- | subversion/libsvn_fs_base/revs-txns.c | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/revs-txns.c b/subversion/libsvn_fs_base/revs-txns.c new file mode 100644 index 0000000000000..d21884311a834 --- /dev/null +++ b/subversion/libsvn_fs_base/revs-txns.c @@ -0,0 +1,1067 @@ +/* revs-txns.c : operations on revision and transactions + * + * ==================================================================== + * 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 <string.h> + +#include <apr_tables.h> +#include <apr_pools.h> + +#include "svn_pools.h" +#include "svn_time.h" +#include "svn_fs.h" +#include "svn_props.h" +#include "svn_hash.h" +#include "svn_io.h" + +#include "fs.h" +#include "dag.h" +#include "err.h" +#include "trail.h" +#include "tree.h" +#include "revs-txns.h" +#include "key-gen.h" +#include "id.h" +#include "bdb/rev-table.h" +#include "bdb/txn-table.h" +#include "bdb/copies-table.h" +#include "bdb/changes-table.h" +#include "../libsvn_fs/fs-loader.h" + +#include "svn_private_config.h" +#include "private/svn_fs_util.h" + + +/*** Helpers ***/ + +/* Set *txn_p to a transaction object allocated in POOL for the + transaction in FS whose id is TXN_ID. If EXPECT_DEAD is set, this + transaction must be a dead one, else an error is returned. If + EXPECT_DEAD is not set, the transaction must *not* be a dead one, + else an error is returned. */ +static svn_error_t * +get_txn(transaction_t **txn_p, + svn_fs_t *fs, + const char *txn_id, + svn_boolean_t expect_dead, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_id, trail, pool)); + if (expect_dead && (txn->kind != transaction_kind_dead)) + return svn_error_createf(SVN_ERR_FS_TRANSACTION_NOT_DEAD, 0, + _("Transaction is not dead: '%s'"), txn_id); + if ((! expect_dead) && (txn->kind == transaction_kind_dead)) + return svn_error_createf(SVN_ERR_FS_TRANSACTION_DEAD, 0, + _("Transaction is dead: '%s'"), txn_id); + *txn_p = txn; + return SVN_NO_ERROR; +} + + +/* This is only for symmetry with the get_txn() helper. */ +#define put_txn svn_fs_bdb__put_txn + + + +/*** Revisions ***/ + +/* Return the committed transaction record *TXN_P and its ID *TXN_ID + (as long as those parameters aren't NULL) for the revision REV in + FS as part of TRAIL. */ +static svn_error_t * +get_rev_txn(transaction_t **txn_p, + const char **txn_id, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool) +{ + revision_t *revision; + transaction_t *txn; + + SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool)); + if (revision->txn_id == NULL) + return svn_fs_base__err_corrupt_fs_revision(fs, rev); + + SVN_ERR(get_txn(&txn, fs, revision->txn_id, FALSE, trail, pool)); + if (txn->revision != rev) + return svn_fs_base__err_corrupt_txn(fs, revision->txn_id); + + if (txn_p) + *txn_p = txn; + if (txn_id) + *txn_id = revision->txn_id; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR(get_rev_txn(&txn, NULL, fs, rev, trail, pool)); + if (txn->root_id == NULL) + return svn_fs_base__err_corrupt_fs_revision(fs, rev); + + *root_id_p = txn->root_id; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__rev_get_txn_id(const char **txn_id_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool) +{ + revision_t *revision; + + SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool)); + if (revision->txn_id == NULL) + return svn_fs_base__err_corrupt_fs_revision(fs, rev); + + *txn_id_p = revision->txn_id; + return SVN_NO_ERROR; +} + + +static svn_error_t * +txn_body_youngest_rev(void *baton, trail_t *trail) +{ + return svn_fs_bdb__youngest_rev(baton, trail->fs, trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__youngest_rev(svn_revnum_t *youngest_p, + svn_fs_t *fs, + apr_pool_t *pool) +{ + svn_revnum_t youngest; + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_youngest_rev, &youngest, + TRUE, pool)); + *youngest_p = youngest; + return SVN_NO_ERROR; +} + + +struct revision_proplist_args { + apr_hash_t **table_p; + svn_revnum_t rev; +}; + + +static svn_error_t * +txn_body_revision_proplist(void *baton, trail_t *trail) +{ + struct revision_proplist_args *args = baton; + transaction_t *txn; + + SVN_ERR(get_rev_txn(&txn, NULL, trail->fs, args->rev, trail, trail->pool)); + *(args->table_p) = txn->proplist; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__revision_proplist(apr_hash_t **table_p, + svn_fs_t *fs, + svn_revnum_t rev, + apr_pool_t *pool) +{ + struct revision_proplist_args args; + apr_hash_t *table; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.table_p = &table; + args.rev = rev; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args, + FALSE, pool)); + + *table_p = table ? table : apr_hash_make(pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__revision_prop(svn_string_t **value_p, + svn_fs_t *fs, + svn_revnum_t rev, + const char *propname, + apr_pool_t *pool) +{ + struct revision_proplist_args args; + apr_hash_t *table; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + /* Get the proplist. */ + args.table_p = &table; + args.rev = rev; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args, + FALSE, pool)); + + /* And then the prop from that list (if there was a list). */ + *value_p = svn_hash_gets(table, propname); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__set_rev_prop(svn_fs_t *fs, + svn_revnum_t rev, + const char *name, + const svn_string_t *const *old_value_p, + const svn_string_t *value, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + const char *txn_id; + + SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool)); + + /* If there's no proplist, but we're just deleting a property, exit now. */ + if ((! txn->proplist) && (! value)) + return SVN_NO_ERROR; + + /* Now, if there's no proplist, we know we need to make one. */ + if (! txn->proplist) + txn->proplist = apr_hash_make(pool); + + /* Set the property. */ + if (old_value_p) + { + const svn_string_t *wanted_value = *old_value_p; + const svn_string_t *present_value = svn_hash_gets(txn->proplist, name); + if ((!wanted_value != !present_value) + || (wanted_value && present_value + && !svn_string_compare(wanted_value, present_value))) + { + /* What we expected isn't what we found. */ + return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, + _("revprop '%s' has unexpected value in " + "filesystem"), + name); + } + /* Fall through. */ + } + svn_hash_sets(txn->proplist, name, value); + + /* Overwrite the revision. */ + return put_txn(fs, txn, txn_id, trail, pool); +} + + +struct change_rev_prop_args { + svn_revnum_t rev; + const char *name; + const svn_string_t *const *old_value_p; + const svn_string_t *value; +}; + + +static svn_error_t * +txn_body_change_rev_prop(void *baton, trail_t *trail) +{ + struct change_rev_prop_args *args = baton; + + return svn_fs_base__set_rev_prop(trail->fs, args->rev, + args->name, args->old_value_p, args->value, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__change_rev_prop(svn_fs_t *fs, + svn_revnum_t rev, + const char *name, + const svn_string_t *const *old_value_p, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct change_rev_prop_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.rev = rev; + args.name = name; + args.old_value_p = old_value_p; + args.value = value; + return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args, + TRUE, pool); +} + + + +/*** Transactions ***/ + +svn_error_t * +svn_fs_base__txn_make_committed(svn_fs_t *fs, + const char *txn_name, + svn_revnum_t revision, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + + /* Make sure the TXN is not committed already. */ + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + /* Convert TXN to a committed transaction. */ + txn->base_id = NULL; + txn->revision = revision; + txn->kind = transaction_kind_committed; + return put_txn(fs, txn, txn_name, trail, pool); +} + + +svn_error_t * +svn_fs_base__txn_get_revision(svn_revnum_t *revision, + svn_fs_t *fs, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + *revision = txn->revision; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__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, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + *root_id_p = txn->root_id; + *base_root_id_p = txn->base_id; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__set_txn_root(svn_fs_t *fs, + const char *txn_name, + const svn_fs_id_t *new_id, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + if (! svn_fs_base__id_eq(txn->root_id, new_id)) + { + txn->root_id = new_id; + SVN_ERR(put_txn(fs, txn, txn_name, trail, pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__set_txn_base(svn_fs_t *fs, + const char *txn_name, + const svn_fs_id_t *new_id, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + if (! svn_fs_base__id_eq(txn->base_id, new_id)) + { + txn->base_id = new_id; + SVN_ERR(put_txn(fs, txn, txn_name, trail, pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__add_txn_copy(svn_fs_t *fs, + const char *txn_name, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + /* Get the transaction and ensure its mutability. */ + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + /* Allocate a new array if this transaction has no copies. */ + if (! txn->copies) + txn->copies = apr_array_make(pool, 1, sizeof(copy_id)); + + /* Add COPY_ID to the array. */ + APR_ARRAY_PUSH(txn->copies, const char *) = copy_id; + + /* Finally, write out the transaction. */ + return put_txn(fs, txn, txn_name, trail, pool); +} + + + +/* Generic transaction operations. */ + +struct txn_proplist_args { + apr_hash_t **table_p; + const char *id; +}; + + +static svn_error_t * +txn_body_txn_proplist(void *baton, trail_t *trail) +{ + transaction_t *txn; + struct txn_proplist_args *args = baton; + + SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(trail->fs, args->id); + + *(args->table_p) = txn->proplist; + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p, + const char *txn_id, + trail_t *trail) +{ + struct txn_proplist_args args; + apr_hash_t *table; + + args.table_p = &table; + args.id = txn_id; + SVN_ERR(txn_body_txn_proplist(&args, trail)); + + *table_p = table ? table : apr_hash_make(trail->pool); + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_fs_base__txn_proplist(apr_hash_t **table_p, + svn_fs_txn_t *txn, + apr_pool_t *pool) +{ + struct txn_proplist_args args; + apr_hash_t *table; + svn_fs_t *fs = txn->fs; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.table_p = &table; + args.id = txn->id; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args, + FALSE, pool)); + + *table_p = table ? table : apr_hash_make(pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__txn_prop(svn_string_t **value_p, + svn_fs_txn_t *txn, + const char *propname, + apr_pool_t *pool) +{ + struct txn_proplist_args args; + apr_hash_t *table; + svn_fs_t *fs = txn->fs; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + /* Get the proplist. */ + args.table_p = &table; + args.id = txn->id; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args, + FALSE, pool)); + + /* And then the prop from that list (if there was a list). */ + *value_p = svn_hash_gets(table, propname); + + return SVN_NO_ERROR; +} + + + +struct change_txn_prop_args { + svn_fs_t *fs; + const char *id; + const char *name; + const svn_string_t *value; +}; + + +svn_error_t * +svn_fs_base__set_txn_prop(svn_fs_t *fs, + const char *txn_name, + const char *name, + const svn_string_t *value, + trail_t *trail, + apr_pool_t *pool) +{ + transaction_t *txn; + + SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool)); + if (txn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + /* If there's no proplist, but we're just deleting a property, exit now. */ + if ((! txn->proplist) && (! value)) + return SVN_NO_ERROR; + + /* Now, if there's no proplist, we know we need to make one. */ + if (! txn->proplist) + txn->proplist = apr_hash_make(pool); + + /* Set the property. */ + svn_hash_sets(txn->proplist, name, value); + + /* Now overwrite the transaction. */ + return put_txn(fs, txn, txn_name, trail, pool); +} + + +static svn_error_t * +txn_body_change_txn_prop(void *baton, trail_t *trail) +{ + struct change_txn_prop_args *args = baton; + return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name, + args->value, trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__change_txn_prop(svn_fs_txn_t *txn, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct change_txn_prop_args args; + svn_fs_t *fs = txn->fs; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.id = txn->id; + args.name = name; + args.value = value; + return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args, + TRUE, pool); +} + + +svn_error_t * +svn_fs_base__change_txn_props(svn_fs_txn_t *txn, + const apr_array_header_t *props, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + int i; + + for (i = 0; i < props->nelts; i++) + { + svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name, + prop->value, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Creating a transaction */ + +static txn_vtable_t txn_vtable = { + svn_fs_base__commit_txn, + svn_fs_base__abort_txn, + svn_fs_base__txn_prop, + svn_fs_base__txn_proplist, + svn_fs_base__change_txn_prop, + svn_fs_base__txn_root, + svn_fs_base__change_txn_props +}; + + +/* Allocate and return a new transaction object in POOL for FS whose + transaction ID is ID. ID is not copied. */ +static svn_fs_txn_t * +make_txn(svn_fs_t *fs, + const char *id, + svn_revnum_t base_rev, + apr_pool_t *pool) +{ + svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn)); + + txn->fs = fs; + txn->id = id; + txn->base_rev = base_rev; + txn->vtable = &txn_vtable; + txn->fsap_data = NULL; + + return txn; +} + + +struct begin_txn_args +{ + svn_fs_txn_t **txn_p; + svn_revnum_t base_rev; + apr_uint32_t flags; +}; + + +static svn_error_t * +txn_body_begin_txn(void *baton, trail_t *trail) +{ + struct begin_txn_args *args = baton; + const svn_fs_id_t *root_id; + const char *txn_id; + + SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev, + trail, trail->pool)); + SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id, + trail, trail->pool)); + + if (args->flags & SVN_FS_TXN_CHECK_OOD) + { + struct change_txn_prop_args cpargs; + cpargs.fs = trail->fs; + cpargs.id = txn_id; + cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD; + cpargs.value = svn_string_create("true", trail->pool); + + SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); + } + + if (args->flags & SVN_FS_TXN_CHECK_LOCKS) + { + struct change_txn_prop_args cpargs; + cpargs.fs = trail->fs; + cpargs.id = txn_id; + cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS; + cpargs.value = svn_string_create("true", trail->pool); + + SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); + } + + *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool); + return SVN_NO_ERROR; +} + +/* Note: it is acceptable for this function to call back into + public FS API interfaces because it does not itself use trails. */ +svn_error_t * +svn_fs_base__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_fs_txn_t *txn; + struct begin_txn_args args; + svn_string_t date; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.txn_p = &txn; + args.base_rev = rev; + args.flags = flags; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool)); + + *txn_p = txn; + + /* 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); + return svn_fs_base__change_txn_prop(txn, SVN_PROP_REVISION_DATE, + &date, pool); +} + + +struct open_txn_args +{ + svn_fs_txn_t **txn_p; + const char *name; +}; + + +static svn_error_t * +txn_body_open_txn(void *baton, trail_t *trail) +{ + struct open_txn_args *args = baton; + transaction_t *fstxn; + svn_revnum_t base_rev = SVN_INVALID_REVNUM; + const char *txn_id; + + SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool)); + if (fstxn->kind != transaction_kind_committed) + { + txn_id = svn_fs_base__id_txn_id(fstxn->base_id); + SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id, + trail, trail->pool)); + } + + *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__open_txn(svn_fs_txn_t **txn_p, + svn_fs_t *fs, + const char *name, + apr_pool_t *pool) +{ + svn_fs_txn_t *txn; + struct open_txn_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.txn_p = &txn; + args.name = name; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool)); + + *txn_p = txn; + return SVN_NO_ERROR; +} + + +struct cleanup_txn_args +{ + transaction_t **txn_p; + const char *name; +}; + + +static svn_error_t * +txn_body_cleanup_txn(void *baton, trail_t *trail) +{ + struct cleanup_txn_args *args = baton; + return get_txn(args->txn_p, trail->fs, args->name, TRUE, + trail, trail->pool); +} + + +static svn_error_t * +txn_body_cleanup_txn_copy(void *baton, trail_t *trail) +{ + const char *copy_id = *(const char **)baton; + svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail, + trail->pool); + + /* Copy doesn't exist? No sweat. */ + if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + return svn_error_trace(err); +} + + +static svn_error_t * +txn_body_cleanup_txn_changes(void *baton, trail_t *trail) +{ + const char *key = *(const char **)baton; + + return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool); +} + + +struct get_dirents_args +{ + apr_hash_t **dirents; + const svn_fs_id_t *id; + const char *txn_id; +}; + + +static svn_error_t * +txn_body_get_dirents(void *baton, trail_t *trail) +{ + struct get_dirents_args *args = baton; + dag_node_t *node; + + /* Get the node. */ + SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id, + trail, trail->pool)); + + /* If immutable, do nothing and return. */ + if (! svn_fs_base__dag_check_mutable(node, args->txn_id)) + return SVN_NO_ERROR; + + /* If a directory, do nothing and return. */ + *(args->dirents) = NULL; + if (svn_fs_base__dag_node_kind(node) != svn_node_dir) + return SVN_NO_ERROR; + + /* Else it's mutable. Get its dirents. */ + return svn_fs_base__dag_dir_entries(args->dirents, node, + trail, trail->pool); +} + + +struct remove_node_args +{ + const svn_fs_id_t *id; + const char *txn_id; +}; + + +static svn_error_t * +txn_body_remove_node(void *baton, trail_t *trail) +{ + struct remove_node_args *args = baton; + return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id, + trail, trail->pool); +} + + +static svn_error_t * +delete_txn_tree(svn_fs_t *fs, + const svn_fs_id_t *id, + const char *txn_id, + apr_pool_t *pool) +{ + struct get_dirents_args dirent_args; + struct remove_node_args rm_args; + apr_hash_t *dirents = NULL; + apr_hash_index_t *hi; + svn_error_t *err; + + /* If this sucker isn't mutable, there's nothing to do. */ + if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(id), txn_id) != 0) + return SVN_NO_ERROR; + + /* See if the thing has dirents that need to be recursed upon. If + you can't find the thing itself, don't sweat it. We probably + already cleaned it up. */ + dirent_args.dirents = &dirents; + dirent_args.id = id; + dirent_args.txn_id = txn_id; + err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args, + FALSE, pool); + if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + /* If there are dirents upon which to recurse ... recurse. */ + if (dirents) + { + apr_pool_t *subpool = svn_pool_create(pool); + + /* Loop over hash entries */ + for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) + { + void *val; + svn_fs_dirent_t *dirent; + + svn_pool_clear(subpool); + apr_hash_this(hi, NULL, NULL, &val); + dirent = val; + SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool)); + } + svn_pool_destroy(subpool); + } + + /* Remove the node. */ + rm_args.id = id; + rm_args.txn_id = txn_id; + return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args, + TRUE, pool); +} + + +static svn_error_t * +txn_body_delete_txn(void *baton, trail_t *trail) +{ + const char *txn_id = *(const char **)baton; + + return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__purge_txn(svn_fs_t *fs, + const char *txn_id, + apr_pool_t *pool) +{ + struct cleanup_txn_args args; + transaction_t *txn; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + /* Open the transaction, expecting it to be dead. */ + args.txn_p = &txn; + args.name = txn_id; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args, + FALSE, pool)); + + /* Delete the mutable portion of the tree hanging from the + transaction (which should gracefully recover if we've already + done this). */ + SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool)); + + /* Kill the transaction's changes (which should gracefully recover + if...). */ + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes, + &txn_id, TRUE, pool)); + + /* Kill the transaction's copies (which should gracefully...). */ + if (txn->copies) + { + int i; + + for (i = 0; i < txn->copies->nelts; i++) + { + SVN_ERR(svn_fs_base__retry_txn + (fs, txn_body_cleanup_txn_copy, + &APR_ARRAY_IDX(txn->copies, i, const char *), + TRUE, pool)); + } + } + + /* Kill the transaction itself (which ... just kidding -- this has + no graceful failure mode). */ + return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id, + TRUE, pool); +} + + +static svn_error_t * +txn_body_abort_txn(void *baton, trail_t *trail) +{ + svn_fs_txn_t *txn = baton; + transaction_t *fstxn; + + /* Get the transaction by its id, set it to "dead", and store the + transaction. */ + SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool)); + if (fstxn->kind != transaction_kind_normal) + return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id); + + fstxn->kind = transaction_kind_dead; + return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__abort_txn(svn_fs_txn_t *txn, + apr_pool_t *pool) +{ + SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); + + /* Set the transaction to "dead". */ + SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn, + TRUE, pool)); + + /* Now, purge it. */ + SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool), + _("Transaction aborted, but cleanup failed")); + + return SVN_NO_ERROR; +} + + +struct list_transactions_args +{ + apr_array_header_t **names_p; + apr_pool_t *pool; +}; + +static svn_error_t * +txn_body_list_transactions(void* baton, trail_t *trail) +{ + struct list_transactions_args *args = baton; + return svn_fs_bdb__get_txn_list(args->names_p, trail->fs, + trail, args->pool); +} + +svn_error_t * +svn_fs_base__list_transactions(apr_array_header_t **names_p, + svn_fs_t *fs, + apr_pool_t *pool) +{ + apr_array_header_t *names; + struct list_transactions_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.names_p = &names; + args.pool = pool; + SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args, + FALSE, pool)); + + *names_p = names; + return SVN_NO_ERROR; +} |