diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_fs_base | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_fs_base')
63 files changed, 24534 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/bdb/bdb-err.c b/subversion/libsvn_fs_base/bdb/bdb-err.c new file mode 100644 index 0000000..3d51711 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/bdb-err.c @@ -0,0 +1,106 @@ +/* + * err.c : implementation of fs-private error functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <stdarg.h> + +#include <apr_strings.h> + +#include "svn_fs.h" +#include "../fs.h" +#include "../err.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" + +#define SVN_WANT_BDB +#include "svn_private_config.h" + + +/* Return a distinguished error for any db error code we want to detect + * programatically; otherwise return a generic error. + */ +static int +bdb_err_to_apr_err(int db_err) +{ + if (db_err == DB_LOCK_DEADLOCK) + return SVN_ERR_FS_BERKELEY_DB_DEADLOCK; + else + return SVN_ERR_FS_BERKELEY_DB; +} + + +svn_error_t * +svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err) +{ + svn_error_t *child_errors; + + child_errors = bdb_baton->error_info->pending_errors; + bdb_baton->error_info->pending_errors = NULL; + + return svn_error_create(bdb_err_to_apr_err(db_err), child_errors, + db_strerror(db_err)); +} + + +svn_error_t * +svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton, + int db_err, const char *fmt, ...) +{ + va_list ap; + char *msg; + svn_error_t *err; + svn_error_t *child_errors; + + child_errors = bdb_baton->error_info->pending_errors; + bdb_baton->error_info->pending_errors = NULL; + + err = svn_error_create(bdb_err_to_apr_err(db_err), child_errors, NULL); + + va_start(ap, fmt); + msg = apr_pvsprintf(err->pool, fmt, ap); + va_end(ap); + err->message = apr_psprintf(err->pool, "%s%s", msg, db_strerror(db_err)); + return svn_error_trace(err); +} + + +svn_error_t * +svn_fs_bdb__wrap_db(svn_fs_t *fs, const char *operation, int db_err) +{ + base_fs_data_t *bfd = fs->fsap_data; + + if (! db_err) + { + svn_error_clear(bfd->bdb->error_info->pending_errors); + bfd->bdb->error_info->pending_errors = NULL; + return SVN_NO_ERROR; + } + + bfd = fs->fsap_data; + return svn_fs_bdb__dberrf + (bfd->bdb, db_err, + _("Berkeley DB error for filesystem '%s' while %s:\n"), + fs->path ? fs->path : "(none)", _(operation)); +} diff --git a/subversion/libsvn_fs_base/bdb/bdb-err.h b/subversion/libsvn_fs_base/bdb/bdb-err.h new file mode 100644 index 0000000..200afe9 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/bdb-err.h @@ -0,0 +1,115 @@ +/* + * err.h : interface to routines for returning Berkeley DB errors + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#ifndef SVN_LIBSVN_FS_BDB_ERR_H +#define SVN_LIBSVN_FS_BDB_ERR_H + +#include <apr_pools.h> + +#include "svn_error.h" +#include "svn_fs.h" + +#include "env.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Return an svn_error_t object that reports a Berkeley DB error. + DB_ERR is the error value returned by the Berkeley DB routine. + Wrap and consume pending errors in BDB. */ +svn_error_t *svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err); + + +/* Allocate an error object for a Berkeley DB error, with a formatted message. + Wrap and consume pending errors in BDB. + + DB_ERR is the Berkeley DB error code. + FMT is a printf-style format string, describing how to format any + subsequent arguments. + + The svn_error_t object returned has a message consisting of: + - the text specified by FMT and the subsequent arguments, and + - the Berkeley DB error message for the error code DB_ERR. + + There is no separator between the two messages; if you want one, + you should include it in FMT. */ +svn_error_t *svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton, int db_err, + const char *fmt, ...) + __attribute__((format(printf, 3, 4))); + + +/* Clear errors associated with BDB. */ +void svn_fs_bdb__clear_err(bdb_env_t *bdb); + + +/* Check the return status from the Berkeley DB operation. If the + operation succeeded, return zero. Otherwise, construct an + appropriate Subversion error object describing what went wrong. + - FS is the Subversion filesystem we're operating on. + - OPERATION is a gerund clause describing what we were trying to do. + - BDB_ERR is the return status from the Berkeley DB function. */ +svn_error_t *svn_fs_bdb__wrap_db(svn_fs_t *fs, + const char *operation, + int db_err); + + +/* A terse wrapper for svn_fs_bdb__wrap_db. */ +#define BDB_WRAP(fs, op, err) (svn_fs_bdb__wrap_db((fs), (op), (err))) + +/* If EXPR returns a non-zero value, pass that value to + svn_fs_bdb__dberr and return that function's value. This is like + SVN_ERR, but is used by functions that return a Subversion error + and call other functions that return a Berkeley DB error code. */ +#define SVN_BDB_ERR(bdb, expr) \ + do { \ + int db_err__temp = (expr); \ + if (db_err__temp) \ + return svn_fs_bdb__dberr((bdb), db_err__temp); \ + svn_error_clear((bdb)->error_info->pending_errors); \ + (bdb)->error_info->pending_errors = NULL; \ + } while (0) + + +/* If EXPR returns a non-zero value, return it. This is like SVN_ERR, + but for functions that return a Berkeley DB error code. */ +#define BDB_ERR(expr) \ + do { \ + int db_err__temp = (expr); \ + if (db_err__temp) \ + return db_err__temp; \ + } while (0) + + +/* Verify that FS refers to an open database; return an appropriate + error if this is not the case. */ +svn_error_t *svn_fs_bdb__check_fs(svn_fs_t *fs); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_BDB_ERR_H */ diff --git a/subversion/libsvn_fs_base/bdb/bdb_compat.c b/subversion/libsvn_fs_base/bdb/bdb_compat.c new file mode 100644 index 0000000..5596eee --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/bdb_compat.c @@ -0,0 +1,34 @@ +/* bdb_compat.c --- Compatibility wrapper for different BDB versions. + * + * ==================================================================== + * 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 "bdb_compat.h" + +int +svn_fs_bdb__check_version(void) +{ + int major, minor; + + db_version(&major, &minor, NULL); + if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR) + return DB_OLD_VERSION; + return 0; +} diff --git a/subversion/libsvn_fs_base/bdb/bdb_compat.h b/subversion/libsvn_fs_base/bdb/bdb_compat.h new file mode 100644 index 0000000..bea62de --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/bdb_compat.h @@ -0,0 +1,135 @@ +/* svn_bdb_compat.h --- Compatibility wrapper for different BDB versions. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_BDB_COMPAT_H +#define SVN_LIBSVN_FS_BDB_COMPAT_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Symbols and constants */ + +/* BDB 4.1 introduced the DB_AUTO_COMMIT flag. Older versions can just + use 0 instead. */ +#ifdef DB_AUTO_COMMIT +#define SVN_BDB_AUTO_COMMIT (DB_AUTO_COMMIT) +#else +#define SVN_BDB_AUTO_COMMIT (0) +#endif + +/* DB_INCOMPLETE is obsolete in BDB 4.1. */ +#ifdef DB_INCOMPLETE +#define SVN_BDB_HAS_DB_INCOMPLETE 1 +#else +#define SVN_BDB_HAS_DB_INCOMPLETE 0 +#endif + +/* In BDB 4.3, "buffer too small" errors come back with + DB_BUFFER_SMALL (instead of ENOMEM, which is now fatal). */ +#ifdef DB_BUFFER_SMALL +#define SVN_BDB_DB_BUFFER_SMALL DB_BUFFER_SMALL +#else +#define SVN_BDB_DB_BUFFER_SMALL ENOMEM +#endif + +/* BDB 4.4 introdiced the DB_REGISTER flag for DBEnv::open that allows + for automatic recovery of the databases after a program crash. */ +#ifdef DB_REGISTER +#define SVN_BDB_AUTO_RECOVER (DB_REGISTER | DB_RECOVER) +#else +#define SVN_BDB_AUTO_RECOVER (0) +#endif + + +/* Explicit BDB version check. */ +#define SVN_BDB_VERSION_AT_LEAST(major,minor) \ + (DB_VERSION_MAJOR > (major) \ + || (DB_VERSION_MAJOR == (major) && DB_VERSION_MINOR >= (minor))) + + +/* Parameter lists */ + +/* In BDB 4.1, DB->open takes a transaction parameter. We'll ignore it + when building with 4.0. */ +#if SVN_BDB_VERSION_AT_LEAST(4,1) +#define SVN_BDB_OPEN_PARAMS(env,txn) (env), (txn) +#else +#define SVN_BDB_OPEN_PARAMS(env,txn) (env) +#endif + +/* In BDB 4.3, the error gatherer function grew a new DBENV parameter, + and the MSG parameter's type changed. */ +#if SVN_BDB_VERSION_AT_LEAST(4,3) +/* Prevents most compilers from whining about unused parameters. */ +#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)(varname)) +#else +#define bdb_error_gatherer(param1, param2, param3) \ + bdb_error_gatherer(param2, char *msg) +#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)0) +#endif + +/* In BDB 4.3 and later, the file names in DB_ENV->open and DB->open + are assumed to be encoded in UTF-8 on Windows. */ +#if defined(WIN32) && SVN_BDB_VERSION_AT_LEAST(4,3) +#define SVN_BDB_PATH_UTF8 (1) +#else +#define SVN_BDB_PATH_UTF8 (0) +#endif + +/* In BDB 4.6, the cursor routines were renamed, and the old names + deprecated. */ +#if SVN_BDB_VERSION_AT_LEAST(4,6) +#define svn_bdb_dbc_close(c) ((c)->close(c)) +#define svn_bdb_dbc_count(c,r,f) ((c)->count(c,r,f)) +#define svn_bdb_dbc_del(c,f) ((c)->del(c,f)) +#define svn_bdb_dbc_dup(c,p,f) ((c)->dup(c,p,f)) +#define svn_bdb_dbc_get(c,k,d,f) ((c)->get(c,k,d,f)) +#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->pget(c,k,p,d,f)) +#define svn_bdb_dbc_put(c,k,d,f) ((c)->put(c,k,d,f)) +#else +#define svn_bdb_dbc_close(c) ((c)->c_close(c)) +#define svn_bdb_dbc_count(c,r,f) ((c)->c_count(c,r,f)) +#define svn_bdb_dbc_del(c,f) ((c)->c_del(c,f)) +#define svn_bdb_dbc_dup(c,p,f) ((c)->c_dup(c,p,f)) +#define svn_bdb_dbc_get(c,k,d,f) ((c)->c_get(c,k,d,f)) +#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->c_pget(c,k,p,d,f)) +#define svn_bdb_dbc_put(c,k,d,f) ((c)->c_put(c,k,d,f)) +#endif + +/* Before calling db_create, we must check that the version of the BDB + libraries we're linking with is the same as the one we compiled + against, because the DB->open call is not binary compatible between + BDB 4.0 and 4.1. This function returns DB_OLD_VERSION if the + compile-time and run-time versions of BDB don't match. */ +int svn_fs_bdb__check_version(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_BDB_COMPAT_H */ diff --git a/subversion/libsvn_fs_base/bdb/changes-table.c b/subversion/libsvn_fs_base/bdb/changes-table.c new file mode 100644 index 0000000..80ff468 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/changes-table.c @@ -0,0 +1,457 @@ +/* changes-table.c : operations on the `changes' table + * + * ==================================================================== + * 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 "bdb_compat.h" + +#include <apr_hash.h> +#include <apr_tables.h> + +#include "svn_hash.h" +#include "svn_fs.h" +#include "svn_pools.h" +#include "svn_path.h" +#include "../fs.h" +#include "../err.h" +#include "../trail.h" +#include "../id.h" +#include "../util/fs_skels.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "dbt.h" +#include "changes-table.h" + +#include "private/svn_fs_util.h" +#include "private/svn_fspath.h" +#include "svn_private_config.h" + + +/*** Creating and opening the changes table. ***/ + +int +svn_fs_bdb__open_changes_table(DB **changes_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *changes; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&changes, env, 0)); + + /* Enable duplicate keys. This allows us to store the changes + one-per-row. Note: this must occur before ->open(). */ + BDB_ERR(changes->set_flags(changes, DB_DUP)); + + BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL), + "changes", 0, DB_BTREE, + open_flags, 0666)); + + *changes_p = changes; + return 0; +} + + + +/*** Storing and retrieving changes. ***/ + +svn_error_t * +svn_fs_bdb__changes_add(svn_fs_t *fs, + const char *key, + change_t *change, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, value; + svn_skel_t *skel; + + /* Convert native type to skel. */ + SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool)); + + /* Store a new record into the database. */ + svn_fs_base__str_to_dbt(&query, key); + svn_fs_base__skel_to_dbt(&value, skel, pool); + svn_fs_base__trail_debug(trail, "changes", "put"); + return BDB_WRAP(fs, N_("creating change"), + bfd->changes->put(bfd->changes, trail->db_txn, + &query, &value, 0)); +} + + +svn_error_t * +svn_fs_bdb__changes_delete(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + int db_err; + DBT query; + base_fs_data_t *bfd = fs->fsap_data; + + svn_fs_base__trail_debug(trail, "changes", "del"); + db_err = bfd->changes->del(bfd->changes, trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), 0); + + /* If there're no changes for KEY, that is acceptable. Any other + error should be propagated to the caller, though. */ + if ((db_err) && (db_err != DB_NOTFOUND)) + { + SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err)); + } + + return SVN_NO_ERROR; +} + + +/* Merge the internal-use-only CHANGE into a hash of public-FS + svn_fs_path_change2_t CHANGES, collapsing multiple changes into a + single succinct change per path. */ +static svn_error_t * +fold_change(apr_hash_t *changes, + const change_t *change) +{ + apr_pool_t *pool = apr_hash_pool_get(changes); + svn_fs_path_change2_t *old_change, *new_change; + const char *path; + + if ((old_change = svn_hash_gets(changes, change->path))) + { + /* This path already exists in the hash, so we have to merge + this change into the already existing one. */ + + /* Since the path already exists in the hash, we don't have to + dup the allocation for the path itself. */ + path = change->path; + + /* 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_base__id_eq(old_change->node_rev_id, + change->noderev_id)) + && (old_change->change_kind != svn_fs_path_change_delete)) + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Invalid change ordering: new node revision ID without delete")); + + /* Sanity check: an add, replacement, or reset must be the first + thing to follow a deletion. */ + if ((old_change->change_kind == svn_fs_path_change_delete) + && (! ((change->kind == svn_fs_path_change_replace) + || (change->kind == svn_fs_path_change_reset) + || (change->kind == svn_fs_path_change_add)))) + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Invalid change ordering: non-add change on deleted path")); + + /* Sanity check: an add can't follow anything except + a delete or reset. */ + if ((change->kind == svn_fs_path_change_add) + && (old_change->change_kind != svn_fs_path_change_delete) + && (old_change->change_kind != svn_fs_path_change_reset)) + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Invalid change ordering: add change on preexisting path")); + + /* Now, merge that change in. */ + switch (change->kind) + { + case svn_fs_path_change_reset: + /* A reset here will simply remove the path change from the + hash. */ + old_change = NULL; + break; + + case svn_fs_path_change_delete: + if (old_change->change_kind == svn_fs_path_change_add) + { + /* If the path was introduced in this transaction via an + add, and we are deleting it, just remove the path + altogether. */ + old_change = NULL; + } + else + { + /* A deletion overrules all previous changes. */ + old_change->change_kind = svn_fs_path_change_delete; + old_change->text_mod = change->text_mod; + old_change->prop_mod = change->prop_mod; + } + break; + + case svn_fs_path_change_add: + case svn_fs_path_change_replace: + /* An add at this point must be following a previous delete, + so treat it just like a replace. */ + old_change->change_kind = svn_fs_path_change_replace; + old_change->node_rev_id = svn_fs_base__id_copy(change->noderev_id, + pool); + old_change->text_mod = change->text_mod; + old_change->prop_mod = change->prop_mod; + break; + + case svn_fs_path_change_modify: + default: + if (change->text_mod) + old_change->text_mod = TRUE; + if (change->prop_mod) + old_change->prop_mod = TRUE; + break; + } + + /* Point our new_change to our (possibly modified) old_change. */ + new_change = old_change; + } + else + { + /* This change is new to the hash, so make a new public change + structure from the internal one (in the hash's pool), and dup + the path into the hash's pool, too. */ + new_change = svn_fs__path_change_create_internal( + svn_fs_base__id_copy(change->noderev_id, pool), + change->kind, + pool); + new_change->text_mod = change->text_mod; + new_change->prop_mod = change->prop_mod; + new_change->node_kind = svn_node_unknown; + new_change->copyfrom_known = FALSE; + path = apr_pstrdup(pool, change->path); + } + + /* Add (or update) this path. */ + svn_hash_sets(changes, path, new_change); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__changes_fetch(apr_hash_t **changes_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBC *cursor; + DBT query, result; + int db_err = 0, db_c_err = 0; + svn_error_t *err = SVN_NO_ERROR; + apr_hash_t *changes = apr_hash_make(pool); + apr_pool_t *subpool = svn_pool_create(pool); + + /* Get a cursor on the first record matching KEY, and then loop over + the records, adding them to the return array. */ + svn_fs_base__trail_debug(trail, "changes", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"), + bfd->changes->cursor(bfd->changes, trail->db_txn, + &cursor, 0))); + + /* Advance the cursor to the key that we're looking for. */ + svn_fs_base__str_to_dbt(&query, key); + svn_fs_base__result_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); + if (! db_err) + svn_fs_base__track_dbt(&result, pool); + + while (! db_err) + { + change_t *change; + svn_skel_t *result_skel; + + /* Clear the per-iteration subpool. */ + svn_pool_clear(subpool); + + /* RESULT now contains a change record associated with KEY. We + need to parse that skel into an change_t structure ... */ + result_skel = svn_skel__parse(result.data, result.size, subpool); + if (! result_skel) + { + err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Error reading changes for key '%s'"), + key); + goto cleanup; + } + err = svn_fs_base__parse_change_skel(&change, result_skel, subpool); + if (err) + goto cleanup; + + /* ... and merge it with our return hash. */ + err = fold_change(changes, change); + if (err) + goto cleanup; + + /* 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)) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(subpool, changes); + hi; + hi = apr_hash_next(hi)) + { + /* KEY is the path. */ + const void *hashkey; + apr_ssize_t klen; + const char *child_relpath; + + apr_hash_this(hi, &hashkey, &klen, NULL); + + /* If we come across our own path, ignore it. + If we come across a child of our path, remove it. */ + child_relpath = svn_fspath__skip_ancestor(change->path, hashkey); + if (child_relpath && *child_relpath) + apr_hash_set(changes, hashkey, klen, NULL); + } + } + + /* Advance the cursor to the next record with this same KEY, and + fetch that record. */ + svn_fs_base__result_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); + if (! db_err) + svn_fs_base__track_dbt(&result, pool); + } + + /* Destroy the per-iteration subpool. */ + svn_pool_destroy(subpool); + + /* If there are no (more) change records for this KEY, we're + finished. Just return the (possibly empty) array. Any other + error, however, needs to get handled appropriately. */ + if (db_err && (db_err != DB_NOTFOUND)) + err = BDB_WRAP(fs, N_("fetching changes"), db_err); + + cleanup: + /* Close the cursor. */ + db_c_err = svn_bdb_dbc_close(cursor); + + /* If we had an error prior to closing the cursor, return the error. */ + if (err) + return svn_error_trace(err); + + /* If our only error thus far was when we closed the cursor, return + that error. */ + if (db_c_err) + SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err)); + + /* Finally, set our return variable and get outta here. */ + *changes_p = changes; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBC *cursor; + DBT query, result; + int db_err = 0, db_c_err = 0; + svn_error_t *err = SVN_NO_ERROR; + change_t *change; + apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change)); + + /* Get a cursor on the first record matching KEY, and then loop over + the records, adding them to the return array. */ + svn_fs_base__trail_debug(trail, "changes", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"), + bfd->changes->cursor(bfd->changes, trail->db_txn, + &cursor, 0))); + + /* Advance the cursor to the key that we're looking for. */ + svn_fs_base__str_to_dbt(&query, key); + svn_fs_base__result_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); + if (! db_err) + svn_fs_base__track_dbt(&result, pool); + + while (! db_err) + { + svn_skel_t *result_skel; + + /* RESULT now contains a change record associated with KEY. We + need to parse that skel into an change_t structure ... */ + result_skel = svn_skel__parse(result.data, result.size, pool); + if (! result_skel) + { + err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Error reading changes for key '%s'"), + key); + goto cleanup; + } + err = svn_fs_base__parse_change_skel(&change, result_skel, pool); + if (err) + goto cleanup; + + /* ... and add it to our return array. */ + APR_ARRAY_PUSH(changes, change_t *) = change; + + /* Advance the cursor to the next record with this same KEY, and + fetch that record. */ + svn_fs_base__result_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); + if (! db_err) + svn_fs_base__track_dbt(&result, pool); + } + + /* If there are no (more) change records for this KEY, we're + finished. Just return the (possibly empty) array. Any other + error, however, needs to get handled appropriately. */ + if (db_err && (db_err != DB_NOTFOUND)) + err = BDB_WRAP(fs, N_("fetching changes"), db_err); + + cleanup: + /* Close the cursor. */ + db_c_err = svn_bdb_dbc_close(cursor); + + /* If we had an error prior to closing the cursor, return the error. */ + if (err) + return svn_error_trace(err); + + /* If our only error thus far was when we closed the cursor, return + that error. */ + if (db_c_err) + SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err)); + + /* Finally, set our return variable and get outta here. */ + *changes_p = changes; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/changes-table.h b/subversion/libsvn_fs_base/bdb/changes-table.h new file mode 100644 index 0000000..c9df636 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/changes-table.h @@ -0,0 +1,94 @@ +/* changes-table.h : internal interface to `changes' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_CHANGES_TABLE_H +#define SVN_LIBSVN_FS_CHANGES_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_io.h" +#include "svn_fs.h" +#include "../fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `changes' table in ENV. If CREATE is non-zero, create one + if it doesn't exist. Set *CHANGES_P to the new table. Return a + Berkeley DB error code. */ +int svn_fs_bdb__open_changes_table(DB **changes_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Add CHANGE as a record to the `changes' table in FS as part of + TRAIL, keyed on KEY. + + CHANGE->path is expected to be a canonicalized filesystem path (see + svn_fs__canonicalize_abspath). + + Note that because the `changes' table uses duplicate keys, this + function will not overwrite prior additions that have the KEY + key, but simply adds this new record alongside previous ones. */ +svn_error_t *svn_fs_bdb__changes_add(svn_fs_t *fs, + const char *key, + change_t *change, + trail_t *trail, + apr_pool_t *pool); + + +/* Remove all changes associated with KEY from the `changes' table in + FS, as part of TRAIL. */ +svn_error_t *svn_fs_bdb__changes_delete(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + +/* Return a hash *CHANGES_P, keyed on const char * paths, and + containing svn_fs_path_change2_t * values representing summarized + changed records associated with KEY in FS, as part of TRAIL. + Allocate the array and its items in POOL. */ +svn_error_t *svn_fs_bdb__changes_fetch(apr_hash_t **changes_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + +/* Return an array *CHANGES_P of change_t * items representing + all the change records associated with KEY in FS, as part of TRAIL. + Allocate the array and its items in POOL. */ +svn_error_t *svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_CHANGES_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/checksum-reps-table.c b/subversion/libsvn_fs_base/bdb/checksum-reps-table.c new file mode 100644 index 0000000..f4a34c3 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/checksum-reps-table.c @@ -0,0 +1,208 @@ +/* checksum-reps-table.c : operations on the `checksum-reps' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_strings.h> + +#include "bdb_compat.h" +#include "../fs.h" +#include "../err.h" +#include "../key-gen.h" +#include "dbt.h" +#include "../trail.h" +#include "bdb-err.h" +#include "../../libsvn_fs/fs-loader.h" +#include "checksum-reps-table.h" + +#include "svn_private_config.h" + + +int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *checksum_reps; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&checksum_reps, env, 0)); + error = (checksum_reps->open)(SVN_BDB_OPEN_PARAMS(checksum_reps, NULL), + "checksum-reps", 0, DB_BTREE, + open_flags, 0666); + + /* Create the checksum-reps table if it doesn't exist. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(checksum_reps->close(checksum_reps, 0)); + return svn_fs_bdb__open_checksum_reps_table(checksum_reps_p, env, TRUE); + } + + /* Create the initial `next-key' table entry. */ + if (create) + { + DBT key, value; + BDB_ERR(checksum_reps->put(checksum_reps, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + BDB_ERR(error); + + *checksum_reps_p = checksum_reps; + return 0; +} + +svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key, + svn_fs_t *fs, + svn_checksum_t *checksum, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + + /* We only allow SHA1 checksums in this table. */ + if (checksum->kind != svn_checksum_sha1) + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, + _("Only SHA1 checksums can be used as keys in the " + "checksum-reps table.\n")); + + svn_fs_base__trail_debug(trail, "checksum-reps", "get"); + db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn, + svn_fs_base__checksum_to_dbt(&key, checksum), + svn_fs_base__result_dbt(&value), 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_no_such_checksum_rep(fs, checksum); + + *rep_key = apr_pstrmemdup(pool, value.data, value.size); + return SVN_NO_ERROR; +} + +svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs, + svn_checksum_t *checksum, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + + /* We only allow SHA1 checksums in this table. */ + if (checksum->kind != svn_checksum_sha1) + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, + _("Only SHA1 checksums can be used as keys in the " + "checksum-reps table.\n")); + + /* Create a key from our CHECKSUM. */ + svn_fs_base__checksum_to_dbt(&key, checksum); + + /* Check to see if we already have a mapping for CHECKSUM. If so, + and the value is the same one we were about to write, that's + cool -- just do nothing. If, however, the value is *different*, + that's a red flag! */ + svn_fs_base__trail_debug(trail, "checksum-reps", "get"); + db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn, + &key, svn_fs_base__result_dbt(&value), 0); + svn_fs_base__track_dbt(&value, pool); + if (db_err != DB_NOTFOUND) + { + const char *sum_str = svn_checksum_to_cstring_display(checksum, pool); + return svn_error_createf + (SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Representation key for checksum '%s' exists in filesystem '%s'."), + sum_str, fs->path); + } + + /* Create a value from our REP_KEY, and add this record to the table. */ + svn_fs_base__str_to_dbt(&value, rep_key); + svn_fs_base__trail_debug(trail, "checksum-reps", "put"); + SVN_ERR(BDB_WRAP(fs, N_("storing checksum-reps record"), + bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn, + &key, &value, 0))); + return SVN_NO_ERROR; +} + +svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs, + svn_checksum_t *checksum, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + + /* We only allow SHA1 checksums in this table. */ + if (checksum->kind != svn_checksum_sha1) + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, + _("Only SHA1 checksums can be used as keys in the " + "checksum-reps table.\n")); + + svn_fs_base__checksum_to_dbt(&key, checksum); + svn_fs_base__trail_debug(trail, "checksum-reps", "del"); + SVN_ERR(BDB_WRAP(fs, N_("deleting entry from 'checksum-reps' table"), + bfd->checksum_reps->del(bfd->checksum_reps, + trail->db_txn, &key, 0))); + return SVN_NO_ERROR; +} + +svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **id_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + apr_size_t len; + char next_key[MAX_KEY_SIZE]; + int db_err; + + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + + /* Get the current value associated with the `next-key' key in the + `checksum-reps' table. */ + svn_fs_base__trail_debug(trail, "checksum-reps", "get"); + SVN_ERR(BDB_WRAP(fs, N_("allocating new representation reuse ID " + "(getting 'next-key')"), + bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn, + &query, + svn_fs_base__result_dbt(&result), + 0))); + svn_fs_base__track_dbt(&result, pool); + + /* Set our return value. */ + *id_p = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + len = result.size; + svn_fs_base__next_key(result.data, &len, next_key); + svn_fs_base__trail_debug(trail, "checksum_reps", "put"); + db_err = bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn, + svn_fs_base__str_to_dbt(&query, + NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&result, next_key), + 0); + + return BDB_WRAP(fs, N_("bumping next representation reuse ID"), db_err); +} diff --git a/subversion/libsvn_fs_base/bdb/checksum-reps-table.h b/subversion/libsvn_fs_base/bdb/checksum-reps-table.h new file mode 100644 index 0000000..ccdcd48 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/checksum-reps-table.h @@ -0,0 +1,89 @@ +/* checksum-reps-table.h : internal interface to ops on `checksum-reps' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H +#define SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "svn_checksum.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `checksum-reps' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *CHECKSUM_REPS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p, + DB_ENV *env, + svn_boolean_t create); + +/* Set *REP_KEY to the representation key stored as the value of key + CHECKSUM in the `checksum-reps' table. Do this as part of TRAIL. + Use POOL for allocations. + + If no such node revision ID is stored for CHECKSUM, return + SVN_ERR_FS_NO_SUCH_CHECKSUM_REP. */ +svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key, + svn_fs_t *fs, + svn_checksum_t *checksum, + trail_t *trail, + apr_pool_t *pool); + +/* Store in the `checksum-reps' table a mapping of CHECKSUM to + representation key REP_KEY in FS. Do this as part of TRAIL. Use + POOL for temporary allocations. + + WARNING: NEVER store a record that maps a checksum to a mutable + representation. Ever. Under pain of dismemberment and death. */ +svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs, + svn_checksum_t *checksum, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool); + +/* Delete from the `checksum-reps' table the mapping of CHECKSUM to a + representation key in FS. Do this as part of TRAIL. Use POOL for + temporary allocations. */ +svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs, + svn_checksum_t *checksum, + trail_t *trail, + apr_pool_t *pool); + +/* Reserve a unique reuse ID in the `checksum-reps' table in FS for a + new instance of a re-used representation as part of TRAIL. Return + the slot's id in *REUSE_ID_P, allocated in POOL. */ +svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **reuse_id_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/copies-table.c b/subversion/libsvn_fs_base/bdb/copies-table.c new file mode 100644 index 0000000..7bf6ca8 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/copies-table.c @@ -0,0 +1,210 @@ +/* copies-table.c : operations on the `copies' table + * + * ==================================================================== + * 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 "bdb_compat.h" + +#include "private/svn_skel.h" + +#include "../fs.h" +#include "../err.h" +#include "../key-gen.h" +#include "dbt.h" +#include "../util/fs_skels.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "copies-table.h" +#include "rev-table.h" + +#include "svn_private_config.h" + + +int +svn_fs_bdb__open_copies_table(DB **copies_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *copies; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&copies, env, 0)); + BDB_ERR((copies->open)(SVN_BDB_OPEN_PARAMS(copies, NULL), + "copies", 0, DB_BTREE, + open_flags, 0666)); + + /* Create the initial `next-key' table entry. */ + if (create) + { + DBT key, value; + BDB_ERR(copies->put(copies, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *copies_p = copies; + return 0; +} + + +/* Store COPY as a copy named COPY_ID in FS as part of TRAIL. */ +/* ### only has one caller; might not need to be abstracted */ +static svn_error_t * +put_copy(svn_fs_t *fs, + const copy_t *copy, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + svn_skel_t *copy_skel; + DBT key, value; + + /* Convert native type to skel. */ + SVN_ERR(svn_fs_base__unparse_copy_skel(©_skel, copy, pool)); + + /* Only in the context of this function do we know that the DB call + will not attempt to modify COPY_ID, so the cast belongs here. */ + svn_fs_base__str_to_dbt(&key, copy_id); + svn_fs_base__skel_to_dbt(&value, copy_skel, pool); + svn_fs_base__trail_debug(trail, "copies", "put"); + return BDB_WRAP(fs, N_("storing copy record"), + bfd->copies->put(bfd->copies, trail->db_txn, + &key, &value, 0)); +} + + +svn_error_t * +svn_fs_bdb__reserve_copy_id(const char **id_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + apr_size_t len; + char next_key[MAX_KEY_SIZE]; + int db_err; + + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + + /* Get the current value associated with the `next-key' key in the + copies table. */ + svn_fs_base__trail_debug(trail, "copies", "get"); + SVN_ERR(BDB_WRAP(fs, N_("allocating new copy ID (getting 'next-key')"), + bfd->copies->get(bfd->copies, trail->db_txn, &query, + svn_fs_base__result_dbt(&result), + 0))); + svn_fs_base__track_dbt(&result, pool); + + /* Set our return value. */ + *id_p = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + len = result.size; + svn_fs_base__next_key(result.data, &len, next_key); + svn_fs_base__trail_debug(trail, "copies", "put"); + db_err = bfd->copies->put(bfd->copies, trail->db_txn, + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&result, next_key), + 0); + + return BDB_WRAP(fs, N_("bumping next copy key"), db_err); +} + + +svn_error_t * +svn_fs_bdb__create_copy(svn_fs_t *fs, + const char *copy_id, + const char *src_path, + const char *src_txn_id, + const svn_fs_id_t *dst_noderev_id, + copy_kind_t kind, + trail_t *trail, + apr_pool_t *pool) +{ + copy_t copy; + copy.kind = kind; + copy.src_path = src_path; + copy.src_txn_id = src_txn_id; + copy.dst_noderev_id = dst_noderev_id; + return put_copy(fs, ©, copy_id, trail, pool); +} + + +svn_error_t * +svn_fs_bdb__delete_copy(svn_fs_t *fs, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + int db_err; + + svn_fs_base__str_to_dbt(&key, copy_id); + svn_fs_base__trail_debug(trail, "copies", "del"); + db_err = bfd->copies->del(bfd->copies, trail->db_txn, &key, 0); + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_no_such_copy(fs, copy_id); + return BDB_WRAP(fs, N_("deleting entry from 'copies' table"), db_err); +} + + +svn_error_t * +svn_fs_bdb__get_copy(copy_t **copy_p, + svn_fs_t *fs, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + svn_skel_t *skel; + copy_t *copy; + + /* Only in the context of this function do we know that the DB call + will not attempt to modify copy_id, so the cast belongs here. */ + svn_fs_base__trail_debug(trail, "copies", "get"); + db_err = bfd->copies->get(bfd->copies, trail->db_txn, + svn_fs_base__str_to_dbt(&key, copy_id), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_no_such_copy(fs, copy_id); + SVN_ERR(BDB_WRAP(fs, N_("reading copy"), db_err)); + + /* Unparse COPY skel */ + skel = svn_skel__parse(value.data, value.size, pool); + if (! skel) + return svn_fs_base__err_corrupt_copy(fs, copy_id); + + /* Convert skel to native type. */ + SVN_ERR(svn_fs_base__parse_copy_skel(©, skel, pool)); + *copy_p = copy; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/copies-table.h b/subversion/libsvn_fs_base/bdb/copies-table.h new file mode 100644 index 0000000..08ab139 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/copies-table.h @@ -0,0 +1,93 @@ +/* copies-table.h : internal interface to ops on `copies' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_COPIES_TABLE_H +#define SVN_LIBSVN_FS_COPIES_TABLE_H + +#include "svn_fs.h" +#include "../fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `copies' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *COPIES_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_copies_table(DB **copies_p, + DB_ENV *env, + svn_boolean_t create); + +/* Reserve a slot in the `copies' table in FS for a new copy operation + as part of TRAIL. Return the slot's id in *COPY_ID_P, allocated in + POOL. */ +svn_error_t *svn_fs_bdb__reserve_copy_id(const char **copy_id_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool); + +/* Create a new copy with id COPY_ID in FS as part of TRAIL. + SRC_PATH/SRC_TXN_ID are the path/transaction ID (respectively) of + the copy source, and DST_NODEREV_ID is the node revision id of the + copy destination. KIND describes the type of copy operation. + + SRC_PATH is expected to be a canonicalized filesystem path (see + svn_fs__canonicalize_abspath). + + COPY_ID should generally come from a call to + svn_fs_bdb__reserve_copy_id(). */ +svn_error_t *svn_fs_bdb__create_copy(svn_fs_t *fs, + const char *copy_id, + const char *src_path, + const char *src_txn_id, + const svn_fs_id_t *dst_noderev_id, + copy_kind_t kind, + trail_t *trail, + apr_pool_t *pool); + +/* Remove the copy whose name is COPY_ID from the `copies' table of + FS, as part of TRAIL. If there is no such copy, + SVN_ERR_FS_NO_SUCH_COPY is the error returned. */ +svn_error_t *svn_fs_bdb__delete_copy(svn_fs_t *fs, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool); + +/* Retrieve the copy *COPY_P named COPY_ID from the `copies' table of + FS, as part of TRAIL. Perform all allocations in POOL. If + there is no such copy, SVN_ERR_FS_NO_SUCH_COPY is the error + returned. */ +svn_error_t *svn_fs_bdb__get_copy(copy_t **copy_p, + svn_fs_t *fs, + const char *copy_id, + trail_t *trail, + apr_pool_t *pool); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_COPIES_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/dbt.c b/subversion/libsvn_fs_base/bdb/dbt.c new file mode 100644 index 0000000..a18ba47 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/dbt.c @@ -0,0 +1,170 @@ +/* dbt.c --- DBT-frobbing functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <stdlib.h> +#include <string.h> +#include <apr_pools.h> +#include <apr_md5.h> +#include <apr_sha1.h> + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "../id.h" +#include "dbt.h" + + +DBT * +svn_fs_base__clear_dbt(DBT *dbt) +{ + memset(dbt, 0, sizeof(*dbt)); + + return dbt; +} + + +DBT *svn_fs_base__nodata_dbt(DBT *dbt) +{ + svn_fs_base__clear_dbt(dbt); + + /* A `nodata' dbt is one which retrieves zero bytes from offset zero, + and stores them in a zero-byte buffer in user-allocated memory. */ + dbt->flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); + dbt->doff = dbt->dlen = 0; + + return dbt; +} + + +DBT * +svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size) +{ + svn_fs_base__clear_dbt(dbt); + + dbt->data = (void *) data; + dbt->size = (u_int32_t) size; + + return dbt; +} + + +DBT * +svn_fs_base__result_dbt(DBT *dbt) +{ + svn_fs_base__clear_dbt(dbt); + dbt->flags |= DB_DBT_MALLOC; + + return dbt; +} + + +/* An APR pool cleanup function that simply applies `free' to its + argument. */ +static apr_status_t +apr_free_cleanup(void *arg) +{ + free(arg); + + return 0; +} + + +DBT * +svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool) +{ + if (dbt->data) + apr_pool_cleanup_register(pool, dbt->data, apr_free_cleanup, + apr_pool_cleanup_null); + + return dbt; +} + + +DBT * +svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno) +{ + svn_fs_base__set_dbt(dbt, recno, sizeof(*recno)); + dbt->ulen = dbt->size; + dbt->flags |= DB_DBT_USERMEM; + + return dbt; +} + + +int +svn_fs_base__compare_dbt(const DBT *a, const DBT *b) +{ + int common_size = a->size > b->size ? b->size : a->size; + int cmp = memcmp(a->data, b->data, common_size); + + if (cmp) + return cmp; + else + return a->size - b->size; +} + + + +/* Building DBT's from interesting things. */ + + +/* Set DBT to the unparsed form of ID; allocate memory from POOL. + Return DBT. */ +DBT * +svn_fs_base__id_to_dbt(DBT *dbt, + const svn_fs_id_t *id, + apr_pool_t *pool) +{ + svn_string_t *unparsed_id = svn_fs_base__id_unparse(id, pool); + svn_fs_base__set_dbt(dbt, unparsed_id->data, unparsed_id->len); + return dbt; +} + + +/* Set DBT to the unparsed form of SKEL; allocate memory from POOL. */ +DBT * +svn_fs_base__skel_to_dbt(DBT *dbt, + svn_skel_t *skel, + apr_pool_t *pool) +{ + svn_stringbuf_t *unparsed_skel = svn_skel__unparse(skel, pool); + svn_fs_base__set_dbt(dbt, unparsed_skel->data, unparsed_skel->len); + return dbt; +} + + +/* Set DBT to the text of the null-terminated string STR. DBT will + refer to STR's storage. Return DBT. */ +DBT * +svn_fs_base__str_to_dbt(DBT *dbt, const char *str) +{ + svn_fs_base__set_dbt(dbt, str, strlen(str)); + return dbt; +} + +DBT * +svn_fs_base__checksum_to_dbt(DBT *dbt, svn_checksum_t *checksum) +{ + svn_fs_base__set_dbt(dbt, checksum->digest, svn_checksum_size(checksum)); + + return dbt; +} diff --git a/subversion/libsvn_fs_base/bdb/dbt.h b/subversion/libsvn_fs_base/bdb/dbt.h new file mode 100644 index 0000000..db93d77 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/dbt.h @@ -0,0 +1,120 @@ +/* dbt.h --- interface to DBT-frobbing functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_DBT_H +#define SVN_LIBSVN_FS_DBT_H + +#include <apr_pools.h> + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" +#include "private/svn_skel.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Set all fields of DBT to zero. Return DBT. */ +DBT *svn_fs_base__clear_dbt(DBT *dbt); + + +/* Set DBT to retrieve no data. This is useful when you're just + probing the table to see if an entry exists, or to find a key, but + don't care what the value is. Return DBT. */ +DBT *svn_fs_base__nodata_dbt(DBT *dbt); + + +/* Set DBT to refer to the SIZE bytes at DATA. Return DBT. */ +DBT *svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size); + + +/* Prepare DBT to hold data returned from Berkeley DB. Return DBT. + + Clear all its fields to zero, but set the DB_DBT_MALLOC flag, + requesting that Berkeley DB place the returned data in a freshly + malloc'd block. If the database operation succeeds, the caller + then owns the data block, and is responsible for making sure it + gets freed. + + You can use this with svn_fs_base__track_dbt: + + svn_fs_base__result_dbt (&foo); + ... some Berkeley DB operation that puts data in foo ... + svn_fs_base__track_dbt (&foo, pool); + + This arrangement is: + - thread-safe --- the returned data is allocated via malloc, and + won't be overwritten if some other thread performs an operation + on the same table. See the explanation of ``Retrieved key/data + permanence'' in the section of the Berkeley DB manual on the DBT + type. + - pool-friendly --- the data returned by Berkeley DB is now guaranteed + to be freed when POOL is cleared. */ +DBT *svn_fs_base__result_dbt(DBT *dbt); + +/* Arrange for POOL to `track' DBT's data: when POOL is cleared, + DBT->data will be freed, using `free'. If DBT->data is zero, + do nothing. + + This is meant for use with svn_fs_base__result_dbt; see the explanation + there. */ +DBT *svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool); + + +/* Prepare DBT for use as a key into a RECNO table. This call makes + DBT refer to the db_recno_t pointed to by RECNO as its buffer; the + record number you assign to *RECNO will be the table key. */ +DBT *svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno); + + +/* Compare two DBT values in byte-by-byte lexicographic order. */ +int svn_fs_base__compare_dbt(const DBT *a, const DBT *b); + + +/* Set DBT to the unparsed form of ID; allocate memory from POOL. + Return DBT. */ +DBT *svn_fs_base__id_to_dbt(DBT *dbt, const svn_fs_id_t *id, + apr_pool_t *pool); + + +/* Set DBT to the unparsed form of SKEL; allocate memory from POOL. + Return DBT. */ +DBT *svn_fs_base__skel_to_dbt(DBT *dbt, svn_skel_t *skel, apr_pool_t *pool); + + +/* Set DBT to the text of the null-terminated string STR. DBT will + refer to STR's storage. Return DBT. */ +DBT *svn_fs_base__str_to_dbt(DBT *dbt, const char *str); + + +/* Set DBT to the bytes contained by CHECKSUM. DBT will refer to CHECKSUM's + storage. Return DBT.*/ +DBT *svn_fs_base__checksum_to_dbt(DBT* dbt, svn_checksum_t *checksum); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_DBT_H */ diff --git a/subversion/libsvn_fs_base/bdb/env.c b/subversion/libsvn_fs_base/bdb/env.c new file mode 100644 index 0000000..557c9dc --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/env.c @@ -0,0 +1,719 @@ +/* env.h : managing the BDB environment + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <assert.h> + +#include <apr.h> +#if APR_HAS_THREADS +#include <apr_thread_proc.h> +#include <apr_time.h> +#endif + +#include <apr_strings.h> +#include <apr_hash.h> + +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_utf.h" +#include "private/svn_atomic.h" +#include "private/svn_mutex.h" + +#include "bdb-err.h" +#include "bdb_compat.h" + +#include "env.h" + +/* A note about the BDB environment descriptor cache. + + With the advent of DB_REGISTER in BDB-4.4, a process may only open + an environment handle once. This means that we must maintain a + cache of open environment handles, with reference counts. We + allocate each environment descriptor (a bdb_env_t) from its own + pool. The cache itself (and the cache pool) are shared between + threads, so all direct or indirect access to the pool is serialized + with a global mutex. + + Because several threads can now use the same DB_ENV handle, we must + use the DB_THREAD flag when opening the environments, otherwise the + env handles (and all of libsvn_fs_base) won't be thread-safe. + + If we use DB_THREAD, however, all of the code that reads data from + the database without a cursor must use either DB_DBT_MALLOC, + DB_DBT_REALLOC, or DB_DBT_USERMEM, as described in the BDB + documentation. + + (Oh, yes -- using DB_THREAD might not work on some systems. But + then, it's quite probable that threading is seriously broken on + those systems anyway, so we'll rely on APR_HAS_THREADS.) +*/ + + +/* The cache key for a Berkeley DB environment descriptor. This is a + combination of the device ID and INODE number of the Berkeley DB + config file. + + XXX FIXME: Although the dev+inode combination is supposed do be + unique, apparently that's not always the case with some remote + filesystems. We /should/ be safe using this as a unique hash key, + because the database must be on a local filesystem. We can hope, + anyway. */ +typedef struct bdb_env_key_t +{ + apr_dev_t device; + apr_ino_t inode; +} bdb_env_key_t; + +/* The cached Berkeley DB environment descriptor. */ +struct bdb_env_t +{ + /**************************************************************************/ + /* Error Reporting */ + + /* A (char *) casted pointer to this structure is passed to BDB's + set_errpfx(), which treats it as a NUL-terminated character + string to prefix all BDB error messages. However, svn also + registers bdb_error_gatherer() as an error handler with + set_errcall() which turns off BDB's default printing of errors to + stderr and anytime thereafter when BDB reports an error and + before the BDB function returns, it calls bdb_error_gatherer() + and passes the same error prefix (char *) pointer given to + set_errpfx(). The bdb_error_gatherer() callback casts the + (char *) it back to a (bdb_env_t *). + + To avoid problems should BDB ever try to interpret our baton as a + string, the first field in the structure is a char + errpfx_string[]. Initializers of this structure must strcpy the + value of BDB_ERRPFX_STRING into this array. */ + char errpfx_string[sizeof(BDB_ERRPFX_STRING)]; + + /* Extended error information. */ +#if APR_HAS_THREADS + apr_threadkey_t *error_info; /* Points to a bdb_error_info_t. */ +#else + bdb_error_info_t error_info; +#endif + + /**************************************************************************/ + /* BDB Environment Cache */ + + /* The Berkeley DB environment. */ + DB_ENV *env; + + /* The flags with which this environment was opened. Reopening the + environment with a different set of flags is not allowed. Trying + to change the state of the DB_PRIVATE flag is an especially bad + idea, so svn_fs_bdb__open() forbids any flag changes. */ + u_int32_t flags; + + /* The home path of this environment; a canonical SVN path encoded in + UTF-8 and allocated from this decriptor's pool. */ + const char *path; + + /* The home path of this environment, in the form expected by BDB. */ + const char *path_bdb; + + /* The reference count for this environment handle; this is + essentially the difference between the number of calls to + svn_fs_bdb__open and svn_fs_bdb__close. */ + unsigned refcount; + + /* If this flag is TRUE, someone has detected that the environment + descriptor is in a panicked state and should be removed from the + cache. + + Note 1: Once this flag is set, it must not be cleared again. + + Note 2: Unlike other fields in this structure, this field is not + protected by the cache mutex on threaded platforms, and + should only be accesses via the svn_atomic functions. */ + volatile svn_atomic_t panic; + + /* The key for the environment descriptor cache. */ + bdb_env_key_t key; + + /* The handle of the open DB_CONFIG file. + + We keep the DB_CONFIG file open in this process as long as the + environment handle itself is open. On Windows, this guarantees + that the cache key remains unique; here's what the Windows SDK + docs have to say about the file index (interpreted as the INODE + number by APR): + + "This value is useful only while the file is open by at least + one process. If no processes have it open, the index may + change the next time the file is opened." + + Now, we certainly don't want a unique key to change while it's + being used, do we... */ + apr_file_t *dbconfig_file; + + /* The pool associated with this environment descriptor. + + Because the descriptor has a life of its own, the structure and + any data associated with it are allocated from their own global + pool. */ + apr_pool_t *pool; + +}; + + +#if APR_HAS_THREADS +/* Get the thread-specific error info from a bdb_env_t. */ +static bdb_error_info_t * +get_error_info(const bdb_env_t *bdb) +{ + void *priv; + apr_threadkey_private_get(&priv, bdb->error_info); + if (!priv) + { + priv = calloc(1, sizeof(bdb_error_info_t)); + apr_threadkey_private_set(priv, bdb->error_info); + } + return priv; +} +#else +#define get_error_info(bdb) (&(bdb)->error_info) +#endif /* APR_HAS_THREADS */ + + +/* Convert a BDB error to a Subversion error. */ +static svn_error_t * +convert_bdb_error(bdb_env_t *bdb, int db_err) +{ + if (db_err) + { + bdb_env_baton_t bdb_baton; + bdb_baton.env = bdb->env; + bdb_baton.bdb = bdb; + bdb_baton.error_info = get_error_info(bdb); + SVN_BDB_ERR(&bdb_baton, db_err); + } + return SVN_NO_ERROR; +} + + +/* Allocating an appropriate Berkeley DB environment object. */ + +/* BDB error callback. See bdb_error_info_t in env.h for more info. + Note: bdb_error_gatherer is a macro with BDB < 4.3, so be careful how + you use it! */ +static void +bdb_error_gatherer(const DB_ENV *dbenv, const char *baton, const char *msg) +{ + /* See the documentation at bdb_env_t's definition why the + (bdb_env_t *) cast is safe and why it is done. */ + bdb_error_info_t *error_info = get_error_info((const bdb_env_t *) baton); + svn_error_t *new_err; + + SVN_BDB_ERROR_GATHERER_IGNORE(dbenv); + + new_err = svn_error_createf(APR_SUCCESS, NULL, "bdb: %s", msg); + if (error_info->pending_errors) + svn_error_compose(error_info->pending_errors, new_err); + else + error_info->pending_errors = new_err; + + if (error_info->user_callback) + error_info->user_callback(NULL, (char *)msg); /* ### I hate this cast... */ +} + + +/* Pool cleanup for the cached environment descriptor. */ +static apr_status_t +cleanup_env(void *data) +{ + bdb_env_t *bdb = data; + bdb->pool = NULL; + bdb->dbconfig_file = NULL; /* will be closed during pool destruction */ +#if APR_HAS_THREADS + apr_threadkey_private_delete(bdb->error_info); +#endif /* APR_HAS_THREADS */ + + /* If there are no references to this descriptor, free its memory here, + so that we don't leak it if create_env returns an error. + See bdb_close, which takes care of freeing this memory if the + environment is still open when the cache is destroyed. */ + if (!bdb->refcount) + free(data); + + return APR_SUCCESS; +} + +#if APR_HAS_THREADS +/* This cleanup is the fall back plan. If the thread exits and the + environment hasn't been closed it's responsible for cleanup of the + thread local error info variable, which would otherwise be leaked. + Normally it will not be called, because svn_fs_bdb__close will + set the thread's error info to NULL after cleaning it up. */ +static void +cleanup_error_info(void *baton) +{ + bdb_error_info_t *error_info = baton; + + if (error_info) + svn_error_clear(error_info->pending_errors); + + free(error_info); +} +#endif /* APR_HAS_THREADS */ + +/* Create a Berkeley DB environment. */ +static svn_error_t * +create_env(bdb_env_t **bdbp, const char *path, apr_pool_t *pool) +{ + int db_err; + bdb_env_t *bdb; + const char *path_bdb; + char *tmp_path, *tmp_path_bdb; + apr_size_t path_size, path_bdb_size; + +#if SVN_BDB_PATH_UTF8 + path_bdb = svn_dirent_local_style(path, pool); +#else + SVN_ERR(svn_utf_cstring_from_utf8(&path_bdb, + svn_dirent_local_style(path, pool), + pool)); +#endif + + /* Allocate the whole structure, including strings, from the heap, + because it must survive the cache pool cleanup. */ + path_size = strlen(path) + 1; + path_bdb_size = strlen(path_bdb) + 1; + /* Using calloc() to ensure the padding bytes in bdb->key (which is used as + * a hash key) are zeroed. */ + bdb = calloc(1, sizeof(*bdb) + path_size + path_bdb_size); + + /* We must initialize this now, as our callers may assume their bdb + pointer is valid when checking for errors. */ + apr_pool_cleanup_register(pool, bdb, cleanup_env, apr_pool_cleanup_null); + apr_cpystrn(bdb->errpfx_string, BDB_ERRPFX_STRING, + sizeof(bdb->errpfx_string)); + bdb->path = tmp_path = (char*)(bdb + 1); + bdb->path_bdb = tmp_path_bdb = tmp_path + path_size; + apr_cpystrn(tmp_path, path, path_size); + apr_cpystrn(tmp_path_bdb, path_bdb, path_bdb_size); + bdb->pool = pool; + *bdbp = bdb; + +#if APR_HAS_THREADS + { + apr_status_t apr_err = apr_threadkey_private_create(&bdb->error_info, + cleanup_error_info, + pool); + if (apr_err) + return svn_error_create(apr_err, NULL, + "Can't allocate thread-specific storage" + " for the Berkeley DB environment descriptor"); + } +#endif /* APR_HAS_THREADS */ + + db_err = db_env_create(&(bdb->env), 0); + if (!db_err) + { + /* See the documentation at bdb_env_t's definition why the + (char *) cast is safe and why it is done. */ + bdb->env->set_errpfx(bdb->env, (char *) bdb); + + /* bdb_error_gatherer is in parens to stop macro expansion. */ + bdb->env->set_errcall(bdb->env, (bdb_error_gatherer)); + + /* Needed on Windows in case Subversion and Berkeley DB are using + different C runtime libraries */ + db_err = bdb->env->set_alloc(bdb->env, malloc, realloc, free); + + /* If we detect a deadlock, select a transaction to abort at + random from those participating in the deadlock. */ + if (!db_err) + db_err = bdb->env->set_lk_detect(bdb->env, DB_LOCK_RANDOM); + } + return convert_bdb_error(bdb, db_err); +} + + + +/* The environment descriptor cache. */ + +/* The global pool used for this cache. */ +static apr_pool_t *bdb_cache_pool = NULL; + +/* The cache. The items are bdb_env_t structures. */ +static apr_hash_t *bdb_cache = NULL; + +/* The mutex that protects bdb_cache. */ +static svn_mutex__t *bdb_cache_lock = NULL; + +/* Cleanup callback to NULL out the cache, so we don't try to use it after + the pool has been cleared during global shutdown. */ +static apr_status_t +clear_cache(void *data) +{ + bdb_cache = NULL; + bdb_cache_lock = NULL; + return APR_SUCCESS; +} + +static volatile svn_atomic_t bdb_cache_state = 0; + +static svn_error_t * +bdb_init_cb(void *baton, apr_pool_t *pool) +{ + bdb_cache_pool = svn_pool_create(pool); + bdb_cache = apr_hash_make(bdb_cache_pool); + + SVN_ERR(svn_mutex__init(&bdb_cache_lock, TRUE, bdb_cache_pool)); + apr_pool_cleanup_register(bdb_cache_pool, NULL, clear_cache, + apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_bdb__init(apr_pool_t* pool) +{ + return svn_atomic__init_once(&bdb_cache_state, bdb_init_cb, NULL, pool); +} + +/* Construct a cache key for the BDB environment at PATH in *KEYP. + if DBCONFIG_FILE is not NULL, return the opened file handle. + Allocate from POOL. */ +static svn_error_t * +bdb_cache_key(bdb_env_key_t *keyp, apr_file_t **dbconfig_file, + const char *path, apr_pool_t *pool) +{ + const char *dbcfg_file_name = svn_dirent_join(path, BDB_CONFIG_FILE, pool); + apr_file_t *dbcfg_file; + apr_status_t apr_err; + apr_finfo_t finfo; + + SVN_ERR(svn_io_file_open(&dbcfg_file, dbcfg_file_name, + APR_READ, APR_OS_DEFAULT, pool)); + + apr_err = apr_file_info_get(&finfo, APR_FINFO_DEV | APR_FINFO_INODE, + dbcfg_file); + if (apr_err) + return svn_error_wrap_apr + (apr_err, "Can't create BDB environment cache key"); + + /* Make sure that any padding in the key is always cleared, so that + the key's hash deterministic. */ + memset(keyp, 0, sizeof *keyp); + keyp->device = finfo.device; + keyp->inode = finfo.inode; + + if (dbconfig_file) + *dbconfig_file = dbcfg_file; + else + apr_file_close(dbcfg_file); + + return SVN_NO_ERROR; +} + + +/* Find a BDB environment in the cache. + Return the environment's panic state in *PANICP. + + Note: You MUST acquire the cache mutex before calling this function. +*/ +static bdb_env_t * +bdb_cache_get(const bdb_env_key_t *keyp, svn_boolean_t *panicp) +{ + bdb_env_t *bdb = apr_hash_get(bdb_cache, keyp, sizeof *keyp); + if (bdb && bdb->env) + { + *panicp = !!svn_atomic_read(&bdb->panic); +#if SVN_BDB_VERSION_AT_LEAST(4,2) + if (!*panicp) + { + u_int32_t flags; + if (bdb->env->get_flags(bdb->env, &flags) + || (flags & DB_PANIC_ENVIRONMENT)) + { + /* Something is wrong with the environment. */ + svn_atomic_set(&bdb->panic, TRUE); + *panicp = TRUE; + bdb = NULL; + } + } +#endif /* at least bdb-4.2 */ + } + else + { + *panicp = FALSE; + } + return bdb; +} + + + +/* Close and destroy a BDB environment descriptor. */ +static svn_error_t * +bdb_close(bdb_env_t *bdb) +{ + svn_error_t *err = SVN_NO_ERROR; + + /* This bit is delcate; we must propagate the error from + DB_ENV->close to the caller, and always destroy the pool. */ + int db_err = bdb->env->close(bdb->env, 0); + + /* If automatic database recovery is enabled, ignore DB_RUNRECOVERY + errors, since they're dealt with eventually by BDB itself. */ + if (db_err && (!SVN_BDB_AUTO_RECOVER || db_err != DB_RUNRECOVERY)) + err = convert_bdb_error(bdb, db_err); + + /* Free the environment descriptor. The pool cleanup will do this unless + the cache has already been destroyed. */ + if (bdb->pool) + svn_pool_destroy(bdb->pool); + else + free(bdb); + return svn_error_trace(err); +} + + +static svn_error_t * +svn_fs_bdb__close_internal(bdb_env_t *bdb) +{ + svn_error_t *err = SVN_NO_ERROR; + + if (--bdb->refcount != 0) + { + /* If the environment is panicked and automatic recovery is not + enabled, return an appropriate error. */ +#if !SVN_BDB_AUTO_RECOVER + if (svn_atomic_read(&bdb->panic)) + err = svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL, + db_strerror(DB_RUNRECOVERY)); +#endif + } + else + { + /* If the bdb cache has been set to NULL that means we are + shutting down, and the pool that holds the bdb cache has + already been destroyed, so accessing it here would be a Bad + Thing (tm) */ + if (bdb_cache) + apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, NULL); + err = bdb_close(bdb); + } + return svn_error_trace(err); +} + +svn_error_t * +svn_fs_bdb__close(bdb_env_baton_t *bdb_baton) +{ + bdb_env_t *bdb = bdb_baton->bdb; + + SVN_ERR_ASSERT(bdb_baton->env == bdb_baton->bdb->env); + SVN_ERR_ASSERT(bdb_baton->error_info->refcount > 0); + + /* Neutralize bdb_baton's pool cleanup to prevent double-close. See + cleanup_env_baton(). */ + bdb_baton->bdb = NULL; + + /* Note that we only bother with this cleanup if the pool is non-NULL, to + guard against potential races between this and the cleanup_env cleanup + callback. It's not clear if that can actually happen, but better safe + than sorry. */ + if (0 == --bdb_baton->error_info->refcount && bdb->pool) + { + svn_error_clear(bdb_baton->error_info->pending_errors); +#if APR_HAS_THREADS + free(bdb_baton->error_info); + apr_threadkey_private_set(NULL, bdb->error_info); +#endif + } + + /* This may run during final pool cleanup when the lock is NULL. */ + SVN_MUTEX__WITH_LOCK(bdb_cache_lock, svn_fs_bdb__close_internal(bdb)); + + return SVN_NO_ERROR; +} + + + +/* Open and initialize a BDB environment. */ +static svn_error_t * +bdb_open(bdb_env_t *bdb, u_int32_t flags, int mode) +{ +#if APR_HAS_THREADS + flags |= DB_THREAD; +#endif + SVN_ERR(convert_bdb_error + (bdb, (bdb->env->open)(bdb->env, bdb->path_bdb, flags, mode))); + +#if SVN_BDB_AUTO_COMMIT + /* Assert the BDB_AUTO_COMMIT flag on the opened environment. This + will force all operations on the environment (and handles that + are opened within the environment) to be transactional. */ + + SVN_ERR(convert_bdb_error + (bdb, bdb->env->set_flags(bdb->env, SVN_BDB_AUTO_COMMIT, 1))); +#endif + + return bdb_cache_key(&bdb->key, &bdb->dbconfig_file, + bdb->path, bdb->pool); +} + + +/* Pool cleanup for the environment baton. */ +static apr_status_t +cleanup_env_baton(void *data) +{ + bdb_env_baton_t *bdb_baton = data; + + if (bdb_baton->bdb) + svn_error_clear(svn_fs_bdb__close(bdb_baton)); + + return APR_SUCCESS; +} + + +static svn_error_t * +svn_fs_bdb__open_internal(bdb_env_baton_t **bdb_batonp, + const char *path, + u_int32_t flags, int mode, + apr_pool_t *pool) +{ + bdb_env_key_t key; + bdb_env_t *bdb; + svn_boolean_t panic; + + /* We can safely discard the open DB_CONFIG file handle. If the + environment descriptor is in the cache, the key's immutability is + guaranteed. If it's not, we don't care if the key changes, + between here and the actual insertion of the newly-created + environment into the cache, because no other thread can touch the + cache in the meantime. */ + SVN_ERR(bdb_cache_key(&key, NULL, path, pool)); + + bdb = bdb_cache_get(&key, &panic); + if (panic) + return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL, + db_strerror(DB_RUNRECOVERY)); + + /* Make sure that the environment's open flags haven't changed. */ + if (bdb && bdb->flags != flags) + { + /* Handle changes to the DB_PRIVATE flag specially */ + if ((flags ^ bdb->flags) & DB_PRIVATE) + { + if (flags & DB_PRIVATE) + return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL, + "Reopening a public Berkeley DB" + " environment with private attributes"); + else + return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL, + "Reopening a private Berkeley DB" + " environment with public attributes"); + } + + /* Otherwise return a generic "flags-mismatch" error. */ + return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL, + "Reopening a Berkeley DB environment" + " with different attributes"); + } + + if (!bdb) + { + svn_error_t *err; + + SVN_ERR(create_env(&bdb, path, svn_pool_create(bdb_cache_pool))); + err = bdb_open(bdb, flags, mode); + if (err) + { + /* Clean up, and we can't do anything about returned errors. */ + svn_error_clear(bdb_close(bdb)); + return svn_error_trace(err); + } + + apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, bdb); + bdb->flags = flags; + bdb->refcount = 1; + } + else + { + ++bdb->refcount; + } + + *bdb_batonp = apr_palloc(pool, sizeof **bdb_batonp); + (*bdb_batonp)->env = bdb->env; + (*bdb_batonp)->bdb = bdb; + (*bdb_batonp)->error_info = get_error_info(bdb); + ++(*bdb_batonp)->error_info->refcount; + apr_pool_cleanup_register(pool, *bdb_batonp, cleanup_env_baton, + apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp, const char *path, + u_int32_t flags, int mode, + apr_pool_t *pool) +{ + SVN_MUTEX__WITH_LOCK(bdb_cache_lock, + svn_fs_bdb__open_internal(bdb_batonp, + path, + flags, + mode, + pool)); + + return SVN_NO_ERROR; +} + + +svn_boolean_t +svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton) +{ + /* An invalid baton is equivalent to a panicked environment; in both + cases, database cleanups should be skipped. */ + if (!bdb_baton->bdb) + return TRUE; + + assert(bdb_baton->env == bdb_baton->bdb->env); + return !!svn_atomic_read(&bdb_baton->bdb->panic); +} + +void +svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton) +{ + if (!bdb_baton->bdb) + return; + + assert(bdb_baton->env == bdb_baton->bdb->env); + svn_atomic_set(&bdb_baton->bdb->panic, TRUE); +} + + +/* This function doesn't actually open the environment, so it doesn't + have to look in the cache. Callers are supposed to own an + exclusive lock on the filesystem anyway. */ +svn_error_t * +svn_fs_bdb__remove(const char *path, apr_pool_t *pool) +{ + bdb_env_t *bdb; + + SVN_ERR(create_env(&bdb, path, pool)); + return convert_bdb_error + (bdb, bdb->env->remove(bdb->env, bdb->path_bdb, DB_FORCE)); +} diff --git a/subversion/libsvn_fs_base/bdb/env.h b/subversion/libsvn_fs_base/bdb/env.h new file mode 100644 index 0000000..a8cce4e --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/env.h @@ -0,0 +1,159 @@ +/* env.h : managing the BDB environment + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_BDB_ENV_H +#define SVN_LIBSVN_FS_BDB_ENV_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include <apr_pools.h> +#include <apr_file_io.h> + +#include "bdb_compat.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* The name of the Berkeley DB config file. */ +#define BDB_CONFIG_FILE "DB_CONFIG" + +/* Prefix string for BDB errors. */ +#define BDB_ERRPFX_STRING "svn (bdb): " + + +/* Opaque descriptor of an open BDB environment. */ +typedef struct bdb_env_t bdb_env_t; + + +/* Thread-specific error info related to the bdb_env_t. */ +typedef struct bdb_error_info_t +{ + /* We hold the extended info here until the Berkeley DB function returns. + It usually returns an error code, triggering the collection and + wrapping of the additional errors stored here. + + Note: In some circumstances BDB will call the error function and not + go on to return an error code, so the caller must always check whether + pending_errors is non-NULL to avoid leaking errors. This behaviour + has been seen when running recovery on a repository upgraded to 4.3 + that still has old 4.2 log files present, a typical error string is + "Skipping log file db/log.0000000002: historic log version 8" */ + svn_error_t *pending_errors; + + /* We permitted clients of our library to install a Berkeley BDB errcall. + Since we now use the errcall ourselves, we must store and invoke a user + errcall, to maintain our API guarantees. */ + void (*user_callback)(const char *errpfx, char *msg); + + /* The reference count. It counts the number of bdb_env_baton_t + instances that refer to this object. */ + unsigned refcount; + +} bdb_error_info_t; + + +/* The Berkeley DB environment baton. */ +typedef struct bdb_env_baton_t +{ + /* The Berkeley DB environment. This pointer must be identical to + the one in the bdb_env_t. */ + DB_ENV *env; + + /* The (opaque) cached environment descriptor. */ + bdb_env_t *bdb; + + /* The error info related to this baton. */ + bdb_error_info_t *error_info; +} bdb_env_baton_t; + + + +/* Flag combination for opening a shared BDB environment. */ +#define SVN_BDB_STANDARD_ENV_FLAGS (DB_CREATE \ + | DB_INIT_LOCK \ + | DB_INIT_LOG \ + | DB_INIT_MPOOL \ + | DB_INIT_TXN \ + | SVN_BDB_AUTO_RECOVER) + +/* Flag combination for opening a private BDB environment. */ +#define SVN_BDB_PRIVATE_ENV_FLAGS (DB_CREATE \ + | DB_INIT_LOG \ + | DB_INIT_MPOOL \ + | DB_INIT_TXN \ + | DB_PRIVATE) + + +/* Iniitalize the BDB back-end's private stuff. */ +svn_error_t *svn_fs_bdb__init(apr_pool_t* pool); + + +/* Allocate the Berkeley DB descriptor BDB and open the environment. + * + * Allocate *BDBP from POOL and open (*BDBP)->env in PATH, using FLAGS + * and MODE. If applicable, set the BDB_AUTO_COMMIT flag for this + * environment. + * + * Use POOL for temporary allocation. + * + * Note: This function may return a bdb_env_baton_t object that refers + * to a previously opened environment. If FLAGS contains + * DB_PRIVATE and the environment is already open, the function + * will fail (this isn't a problem in practice, because a caller + * should obtain an exclusive lock on the repository before + * opening the environment). + */ + +svn_error_t *svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp, + const char *path, + u_int32_t flags, int mode, + apr_pool_t *pool); + +/* Close the Berkeley DB descriptor BDB. + * + * Note: This function might not actually close the environment if it + * has been opened more than once. + */ +svn_error_t *svn_fs_bdb__close(bdb_env_baton_t *bdb_baton); + + +/* Get the panic state of the open BDB environment. */ +svn_boolean_t svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton); + +/* Set the panic flag on the open BDB environment. */ +void svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton); + + +/* Remove the Berkeley DB environment at PATH. + * + * Use POOL for temporary allocation. + */ +svn_error_t *svn_fs_bdb__remove(const char *path, apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_BDB_ENV_H */ diff --git a/subversion/libsvn_fs_base/bdb/lock-tokens-table.c b/subversion/libsvn_fs_base/bdb/lock-tokens-table.c new file mode 100644 index 0000000..e70ef17 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/lock-tokens-table.c @@ -0,0 +1,157 @@ +/* lock-tokens-table.c : operations on the `lock-tokens' table + * + * ==================================================================== + * 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 <assert.h> + +#include "bdb_compat.h" + +#include "svn_pools.h" +#include "private/svn_skel.h" + +#include "dbt.h" +#include "../err.h" +#include "../fs.h" +#include "../util/fs_skels.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "lock-tokens-table.h" +#include "locks-table.h" + +#include "private/svn_fs_util.h" + + +int +svn_fs_bdb__open_lock_tokens_table(DB **lock_tokens_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *lock_tokens; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&lock_tokens, env, 0)); + error = (lock_tokens->open)(SVN_BDB_OPEN_PARAMS(lock_tokens, NULL), + "lock-tokens", 0, DB_BTREE, + open_flags, 0666); + + /* Create the table if it doesn't yet exist. This is a form of + automagical repository upgrading. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(lock_tokens->close(lock_tokens, 0)); + return svn_fs_bdb__open_lock_tokens_table(lock_tokens_p, env, TRUE); + } + BDB_ERR(error); + + *lock_tokens_p = lock_tokens; + return 0; +} + + +svn_error_t * +svn_fs_bdb__lock_token_add(svn_fs_t *fs, + const char *path, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool) +{ + + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + + svn_fs_base__str_to_dbt(&key, path); + svn_fs_base__str_to_dbt(&value, lock_token); + svn_fs_base__trail_debug(trail, "lock-tokens", "add"); + return BDB_WRAP(fs, N_("storing lock token record"), + bfd->lock_tokens->put(bfd->lock_tokens, trail->db_txn, + &key, &value, 0)); +} + + +svn_error_t * +svn_fs_bdb__lock_token_delete(svn_fs_t *fs, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + int db_err; + + svn_fs_base__str_to_dbt(&key, path); + svn_fs_base__trail_debug(trail, "lock-tokens", "del"); + db_err = bfd->lock_tokens->del(bfd->lock_tokens, trail->db_txn, &key, 0); + if (db_err == DB_NOTFOUND) + return SVN_FS__ERR_NO_SUCH_LOCK(fs, path); + return BDB_WRAP(fs, N_("deleting entry from 'lock-tokens' table"), db_err); +} + + +svn_error_t * +svn_fs_bdb__lock_token_get(const char **lock_token_p, + svn_fs_t *fs, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + svn_error_t *err; + svn_lock_t *lock; + const char *lock_token; + int db_err; + + svn_fs_base__trail_debug(trail, "lock-tokens", "get"); + db_err = bfd->lock_tokens->get(bfd->lock_tokens, trail->db_txn, + svn_fs_base__str_to_dbt(&key, path), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return SVN_FS__ERR_NO_SUCH_LOCK(fs, path); + SVN_ERR(BDB_WRAP(fs, N_("reading lock token"), db_err)); + + lock_token = apr_pstrmemdup(pool, value.data, value.size); + + /* Make sure the token still points to an existing, non-expired + lock, by doing a lookup in the `locks' table. */ + err = svn_fs_bdb__lock_get(&lock, fs, lock_token, trail, pool); + if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) + { + /* If `locks' doesn't have the lock, then we should lose it too. */ + svn_error_t *delete_err; + delete_err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool); + if (delete_err) + svn_error_compose(err, delete_err); + return svn_error_trace(err); + } + else if (err) + return svn_error_trace(err); + + *lock_token_p = lock_token; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/lock-tokens-table.h b/subversion/libsvn_fs_base/bdb/lock-tokens-table.h new file mode 100644 index 0000000..de958ce --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/lock-tokens-table.h @@ -0,0 +1,96 @@ +/* lock-tokens-table.h : internal interface to ops on `lock-tokens' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H +#define SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `lock-tokens' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *LOCK_TOKENS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_lock_tokens_table(DB **locks_tokens_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Add a lock-token to the `lock-tokens' table in FS, as part of TRAIL. + Use PATH as the key and LOCK_TOKEN as the value. + + Warning: if PATH already exists as a key, then its value will be + overwritten. */ +svn_error_t * +svn_fs_bdb__lock_token_add(svn_fs_t *fs, + const char *path, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool); + + +/* Remove the lock-token whose key is PATH from the `lock-tokens' + table of FS, as part of TRAIL. + + If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK. +*/ +svn_error_t * +svn_fs_bdb__lock_token_delete(svn_fs_t *fs, + const char *path, + trail_t *trail, + apr_pool_t *pool); + + +/* Retrieve the lock-token *LOCK_TOKEN_P pointed to by PATH from the + `lock-tokens' table of FS, as part of TRAIL. Perform all + allocations in POOL. + + If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK. + + If PATH points to a token which points to an expired lock, return + SVN_ERR_FS_LOCK_EXPIRED. (After this, both the token and lock are + gone from their respective tables.) + + If PATH points to a token which points to a non-existent lock, + return SVN_ERR_FS_BAD_LOCK_TOKEN. (After this, the token is also + removed from the `lock-tokens' table.) + */ +svn_error_t * +svn_fs_bdb__lock_token_get(const char **lock_token_p, + svn_fs_t *fs, + const char *path, + trail_t *trail, + apr_pool_t *pool); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/locks-table.c b/subversion/libsvn_fs_base/bdb/locks-table.c new file mode 100644 index 0000000..a22663f --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/locks-table.c @@ -0,0 +1,328 @@ +/* locks-table.c : operations on the `locks' table + * + * ==================================================================== + * 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 <assert.h> + +#include "bdb_compat.h" + +#include "svn_pools.h" +#include "svn_path.h" +#include "private/svn_skel.h" + +#include "dbt.h" +#include "../err.h" +#include "../fs.h" +#include "../util/fs_skels.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "locks-table.h" +#include "lock-tokens-table.h" + +#include "private/svn_fs_util.h" +#include "private/svn_fspath.h" + + +int +svn_fs_bdb__open_locks_table(DB **locks_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *locks; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&locks, env, 0)); + error = (locks->open)(SVN_BDB_OPEN_PARAMS(locks, NULL), + "locks", 0, DB_BTREE, + open_flags, 0666); + + /* Create the table if it doesn't yet exist. This is a form of + automagical repository upgrading. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(locks->close(locks, 0)); + return svn_fs_bdb__open_locks_table(locks_p, env, TRUE); + } + BDB_ERR(error); + + *locks_p = locks; + return 0; +} + + + +svn_error_t * +svn_fs_bdb__lock_add(svn_fs_t *fs, + const char *lock_token, + svn_lock_t *lock, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + svn_skel_t *lock_skel; + DBT key, value; + + /* Convert native type to skel. */ + SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool)); + + svn_fs_base__str_to_dbt(&key, lock_token); + svn_fs_base__skel_to_dbt(&value, lock_skel, pool); + svn_fs_base__trail_debug(trail, "lock", "add"); + return BDB_WRAP(fs, N_("storing lock record"), + bfd->locks->put(bfd->locks, trail->db_txn, + &key, &value, 0)); +} + + + +svn_error_t * +svn_fs_bdb__lock_delete(svn_fs_t *fs, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + int db_err; + + svn_fs_base__str_to_dbt(&key, lock_token); + svn_fs_base__trail_debug(trail, "locks", "del"); + db_err = bfd->locks->del(bfd->locks, trail->db_txn, &key, 0); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_bad_lock_token(fs, lock_token); + return BDB_WRAP(fs, N_("deleting lock from 'locks' table"), db_err); +} + + + +svn_error_t * +svn_fs_bdb__lock_get(svn_lock_t **lock_p, + svn_fs_t *fs, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + svn_skel_t *skel; + svn_lock_t *lock; + + svn_fs_base__trail_debug(trail, "lock", "get"); + db_err = bfd->locks->get(bfd->locks, trail->db_txn, + svn_fs_base__str_to_dbt(&key, lock_token), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_bad_lock_token(fs, lock_token); + SVN_ERR(BDB_WRAP(fs, N_("reading lock"), db_err)); + + /* Parse TRANSACTION skel */ + skel = svn_skel__parse(value.data, value.size, pool); + if (! skel) + return svn_fs_base__err_corrupt_lock(fs, lock_token); + + /* Convert skel to native type. */ + SVN_ERR(svn_fs_base__parse_lock_skel(&lock, skel, pool)); + + /* Possibly auto-expire the lock. */ + if (lock->expiration_date && (apr_time_now() > lock->expiration_date)) + { + SVN_ERR(svn_fs_bdb__lock_delete(fs, lock_token, trail, pool)); + return SVN_FS__ERR_LOCK_EXPIRED(fs, lock_token); + } + + *lock_p = lock; + return SVN_NO_ERROR; +} + + +static svn_error_t * +get_lock(svn_lock_t **lock_p, + svn_fs_t *fs, + const char *path, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + *lock_p = NULL; + + /* Make sure the token points to an existing, non-expired lock, by + doing a lookup in the `locks' table. Use 'pool'. */ + err = svn_fs_bdb__lock_get(lock_p, fs, lock_token, trail, pool); + if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) + { + svn_error_clear(err); + + /* If `locks' doesn't have the lock, then we should lose it + from `lock-tokens' table as well, then skip to the next + matching path-key. */ + err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool); + } + return svn_error_trace(err); +} + + +svn_error_t * +svn_fs_bdb__locks_get(svn_fs_t *fs, + const char *path, + svn_depth_t depth, + svn_fs_get_locks_callback_t get_locks_func, + void *get_locks_baton, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBC *cursor; + DBT key, value; + int db_err, db_c_err; + apr_pool_t *subpool = svn_pool_create(pool); + const char *lock_token; + svn_lock_t *lock; + svn_error_t *err; + const char *lookup_path = path; + apr_size_t lookup_len; + + /* First, try to lookup PATH itself. */ + err = svn_fs_bdb__lock_token_get(&lock_token, fs, path, trail, pool); + if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN) + || (err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK))) + { + svn_error_clear(err); + } + else if (err) + { + return svn_error_trace(err); + } + else + { + SVN_ERR(get_lock(&lock, fs, path, lock_token, trail, pool)); + if (lock && get_locks_func) + { + SVN_ERR(get_locks_func(get_locks_baton, lock, pool)); + + /* Found a lock so PATH is a file and we can ignore depth */ + return SVN_NO_ERROR; + } + } + + /* If we're only looking at PATH itself (depth = empty), stop here. */ + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Now go hunt for possible children of PATH. */ + + svn_fs_base__trail_debug(trail, "lock-tokens", "cursor"); + db_err = bfd->lock_tokens->cursor(bfd->lock_tokens, trail->db_txn, + &cursor, 0); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading lock tokens"), + db_err)); + + /* Since the key is going to be returned as well as the value make + sure BDB malloc's the returned key. */ + svn_fs_base__str_to_dbt(&key, lookup_path); + key.flags |= DB_DBT_MALLOC; + + /* Get the first matching key that is either equal or greater than + the one passed in, by passing in the DB_RANGE_SET flag. */ + db_err = svn_bdb_dbc_get(cursor, &key, svn_fs_base__result_dbt(&value), + DB_SET_RANGE); + + if (!svn_fspath__is_root(path, strlen(path))) + lookup_path = apr_pstrcat(pool, path, "/", (char *)NULL); + lookup_len = strlen(lookup_path); + + /* As long as the prefix of the returned KEY matches LOOKUP_PATH we + know it is either LOOKUP_PATH or a decendant thereof. */ + while ((! db_err) + && lookup_len < key.size + && strncmp(lookup_path, key.data, lookup_len) == 0) + { + const char *child_path; + + svn_pool_clear(subpool); + + svn_fs_base__track_dbt(&key, subpool); + svn_fs_base__track_dbt(&value, subpool); + + /* Create a usable path and token in temporary memory. */ + child_path = apr_pstrmemdup(subpool, key.data, key.size); + lock_token = apr_pstrmemdup(subpool, value.data, value.size); + + if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) + { + /* On the assumption that we only store locks for files, + depth=files and depth=immediates should boil down to the + same set of results. So just see if CHILD_PATH is an + immediate child of PATH. If not, we don't care about + this item. */ + const char *rel_path = svn_fspath__skip_ancestor(path, child_path); + if (!rel_path || (svn_path_component_count(rel_path) != 1)) + goto loop_it; + } + + /* Get the lock for CHILD_PATH. */ + err = get_lock(&lock, fs, child_path, lock_token, trail, subpool); + if (err) + { + svn_bdb_dbc_close(cursor); + return svn_error_trace(err); + } + + /* Lock is verified, hand it off to our callback. */ + if (lock && get_locks_func) + { + err = get_locks_func(get_locks_baton, lock, subpool); + if (err) + { + svn_bdb_dbc_close(cursor); + return svn_error_trace(err); + } + } + + loop_it: + svn_fs_base__result_dbt(&key); + svn_fs_base__result_dbt(&value); + db_err = svn_bdb_dbc_get(cursor, &key, &value, DB_NEXT); + } + + svn_pool_destroy(subpool); + db_c_err = svn_bdb_dbc_close(cursor); + + if (db_err && (db_err != DB_NOTFOUND)) + SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens"), db_err)); + if (db_c_err) + SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens (closing cursor)"), + db_c_err)); + + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_fs_base/bdb/locks-table.h b/subversion/libsvn_fs_base/bdb/locks-table.h new file mode 100644 index 0000000..e0d087c --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/locks-table.h @@ -0,0 +1,110 @@ +/* locks-table.h : internal interface to ops on `locks' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_LOCKS_TABLE_H +#define SVN_LIBSVN_FS_LOCKS_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `locks' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *LOCKS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_locks_table(DB **locks_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Add a lock to the `locks' table in FS, as part of TRAIL. + + Use LOCK_TOKEN as the key, presumably a string form of an apr_uuid_t. + Convert LOCK into a skel and store it as the value. + + Warning: if LOCK_TOKEN already exists as a key, then its value + will be overwritten. */ +svn_error_t *svn_fs_bdb__lock_add(svn_fs_t *fs, + const char *lock_token, + svn_lock_t *lock, + trail_t *trail, + apr_pool_t *pool); + + +/* Remove the lock whose key is LOCK_TOKEN from the `locks' table of + FS, as part of TRAIL. + + Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a + table key. */ +svn_error_t *svn_fs_bdb__lock_delete(svn_fs_t *fs, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool); + + +/* Retrieve the lock *LOCK_P pointed to by LOCK_TOKEN from the `locks' + table of FS, as part of TRAIL. Perform all allocations in POOL. + + Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a + table key. + + Before returning LOCK_P, check its expiration date. If expired, + remove the row from the `locks' table and return SVN_ERR_FS_LOCK_EXPIRED. + */ +svn_error_t *svn_fs_bdb__lock_get(svn_lock_t **lock_p, + svn_fs_t *fs, + const char *lock_token, + trail_t *trail, + apr_pool_t *pool); + + +/* Retrieve locks representing all locks that exist at or below PATH + in FS. Pass each lock to GET_LOCKS_FUNC callback along with + GET_LOCKS_BATON. + + Use DEPTH to filter the reported locks to only those within the + requested depth of PATH. + + This function promises to auto-expire any locks encountered while + building the hash. That means that the caller can trust that each + returned lock hasn't yet expired. +*/ +svn_error_t *svn_fs_bdb__locks_get(svn_fs_t *fs, + const char *path, + svn_depth_t depth, + svn_fs_get_locks_callback_t get_locks_func, + void *get_locks_baton, + trail_t *trail, + apr_pool_t *pool); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_LOCKS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/miscellaneous-table.c b/subversion/libsvn_fs_base/bdb/miscellaneous-table.c new file mode 100644 index 0000000..21a05ca --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/miscellaneous-table.c @@ -0,0 +1,135 @@ +/* miscellaneous-table.c : operations on the `miscellaneous' table + * + * ==================================================================== + * 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 <assert.h> +#include "bdb_compat.h" + +#include "svn_pools.h" +#include "dbt.h" +#include "../err.h" +#include "../fs.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "miscellaneous-table.h" + +#include "private/svn_fs_util.h" + + +int +svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *miscellaneous; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&miscellaneous, env, 0)); + error = (miscellaneous->open)(SVN_BDB_OPEN_PARAMS(miscellaneous, NULL), + "miscellaneous", 0, DB_BTREE, + open_flags, 0666); + + /* Create the table if it doesn't yet exist. This is a form of + automagical repository upgrading. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(miscellaneous->close(miscellaneous, 0)); + return svn_fs_bdb__open_miscellaneous_table(miscellaneous_p, env, TRUE); + } + BDB_ERR(error); + + /* If we're creating the table from scratch (not upgrading), record the + upgrade rev as 0. */ + if (create) + { + DBT key, value; + + BDB_ERR(miscellaneous->put + (miscellaneous, 0, + svn_fs_base__str_to_dbt + (&key, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *miscellaneous_p = miscellaneous; + return 0; +} + + +svn_error_t * +svn_fs_bdb__miscellaneous_set(svn_fs_t *fs, + const char *key_str, + const char *val, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + + svn_fs_base__str_to_dbt(&key, key_str); + if (val == NULL) + { + svn_fs_base__trail_debug(trail, "miscellaneous", "del"); + return BDB_WRAP(fs, N_("deleting record from 'miscellaneous' table"), + bfd->miscellaneous->del(bfd->miscellaneous, + trail->db_txn, &key, 0)); + } + else + { + svn_fs_base__str_to_dbt(&value, val); + svn_fs_base__trail_debug(trail, "miscellaneous", "add"); + return BDB_WRAP(fs, N_("storing miscellaneous record"), + bfd->miscellaneous->put(bfd->miscellaneous, + trail->db_txn, + &key, &value, 0)); + } +} + + +svn_error_t * +svn_fs_bdb__miscellaneous_get(const char **val, + svn_fs_t *fs, + const char *key_str, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + + *val = NULL; + svn_fs_base__trail_debug(trail, "miscellaneous", "get"); + db_err = bfd->miscellaneous->get(bfd->miscellaneous, trail->db_txn, + svn_fs_base__str_to_dbt(&key, key_str), + svn_fs_base__result_dbt(&value), 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err != DB_NOTFOUND) + { + SVN_ERR(BDB_WRAP(fs, N_("fetching miscellaneous record"), db_err)); + *val = apr_pstrmemdup(pool, value.data, value.size); + } + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/miscellaneous-table.h b/subversion/libsvn_fs_base/bdb/miscellaneous-table.h new file mode 100644 index 0000000..f972d02 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/miscellaneous-table.h @@ -0,0 +1,71 @@ +/* miscellaneous-table.h : internal interface to ops on `miscellaneous' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H +#define SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `miscellaneous' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *MISCELLANEOUS_P to the new table. + Return a Berkeley DB error code. */ +int +svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Add data to the `miscellaneous' table in FS, as part of TRAIL. + + KEY and VAL should be NULL-terminated strings. If VAL is NULL, + the key is removed from the table. */ +svn_error_t * +svn_fs_bdb__miscellaneous_set(svn_fs_t *fs, + const char *key, + const char *val, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *VAL to the value of data cooresponding to KEY in the + `miscellaneous' table of FS, or to NULL if that key isn't found. */ +svn_error_t * +svn_fs_bdb__miscellaneous_get(const char **val, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/node-origins-table.c b/subversion/libsvn_fs_base/bdb/node-origins-table.c new file mode 100644 index 0000000..48dc43b --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/node-origins-table.c @@ -0,0 +1,145 @@ +/* node-origins-table.c : operations on the `node-origins' table + * + * ==================================================================== + * 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 "bdb_compat.h" +#include "../fs.h" +#include "../err.h" +#include "../id.h" +#include "dbt.h" +#include "../trail.h" +#include "bdb-err.h" +#include "../../libsvn_fs/fs-loader.h" +#include "node-origins-table.h" + +#include "svn_private_config.h" + + +int svn_fs_bdb__open_node_origins_table(DB **node_origins_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *node_origins; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&node_origins, env, 0)); + error = (node_origins->open)(SVN_BDB_OPEN_PARAMS(node_origins, NULL), + "node-origins", 0, DB_BTREE, + open_flags, 0666); + + /* Create the node-origins table if it doesn't exist. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(node_origins->close(node_origins, 0)); + return svn_fs_bdb__open_node_origins_table(node_origins_p, env, TRUE); + } + + BDB_ERR(error); + + *node_origins_p = node_origins; + return 0; +} + +svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id, + svn_fs_t *fs, + const char *node_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + + svn_fs_base__trail_debug(trail, "node-origins", "get"); + db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn, + svn_fs_base__str_to_dbt(&key, node_id), + svn_fs_base__result_dbt(&value), 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_no_such_node_origin(fs, node_id); + + *origin_id = svn_fs_base__id_parse(value.data, value.size, pool); + return SVN_NO_ERROR; +} + +svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs, + const char *node_id, + const svn_fs_id_t *origin_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + + /* Create a key from our NODE_ID. */ + svn_fs_base__str_to_dbt(&key, node_id); + + /* Check to see if we already have a mapping for NODE_ID. If so, + and the value is the same one we were about to write. That's + cool -- just do nothing. If, however, the value is *different*, + that's a red flag! */ + svn_fs_base__trail_debug(trail, "node-origins", "get"); + db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn, + &key, svn_fs_base__result_dbt(&value), 0); + svn_fs_base__track_dbt(&value, pool); + if (db_err != DB_NOTFOUND) + { + const svn_string_t *origin_id_str = + svn_fs_base__id_unparse(origin_id, pool); + const svn_string_t *old_origin_id_str = + svn_string_ncreate(value.data, value.size, pool); + + if (! svn_string_compare(origin_id_str, old_origin_id_str)) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, NULL, + _("Node origin for '%s' exists in filesystem '%s' with a different " + "value (%s) than what we were about to store (%s)"), + node_id, fs->path, old_origin_id_str->data, origin_id_str->data); + else + return SVN_NO_ERROR; + } + + /* Create a value from our ORIGIN_ID, and add this record to the table. */ + svn_fs_base__id_to_dbt(&value, origin_id, pool); + svn_fs_base__trail_debug(trail, "node-origins", "put"); + return BDB_WRAP(fs, N_("storing node-origins record"), + bfd->node_origins->put(bfd->node_origins, trail->db_txn, + &key, &value, 0)); +} + +svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs, + const char *node_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + + svn_fs_base__str_to_dbt(&key, node_id); + svn_fs_base__trail_debug(trail, "node-origins", "del"); + return BDB_WRAP(fs, N_("deleting entry from 'node-origins' table"), + bfd->node_origins->del(bfd->node_origins, + trail->db_txn, &key, 0)); +} diff --git a/subversion/libsvn_fs_base/bdb/node-origins-table.h b/subversion/libsvn_fs_base/bdb/node-origins-table.h new file mode 100644 index 0000000..44587ca --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/node-origins-table.h @@ -0,0 +1,76 @@ +/* node-origins-table.h : internal interface to ops on `node-origins' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H +#define SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `node-origins' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *NODE_ORIGINS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_node_origins_table(DB **node_origins_p, + DB_ENV *env, + svn_boolean_t create); + +/* Set *ORIGIN_ID to the node revision ID from which the history of + all nodes in FS whose node ID is NODE_ID springs, as determined by + a look in the `node-origins' table. Do this as part of TRAIL. Use + POOL for allocations. + + If no such node revision ID is stored for NODE_ID, return + SVN_ERR_FS_NO_SUCH_NODE_ORIGIN. */ +svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id, + svn_fs_t *fs, + const char *node_id, + trail_t *trail, + apr_pool_t *pool); + +/* Store in the `node-origins' table a mapping of NODE_ID to original + node revision ID ORIGIN_ID for FS. Do this as part of TRAIL. Use + POOL for temporary allocations. */ +svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs, + const char *node_id, + const svn_fs_id_t *origin_id, + trail_t *trail, + apr_pool_t *pool); + +/* Delete from the `node-origins' table the record for NODE_ID in FS. + Do this as part of TRAIL. Use POOL for temporary allocations. */ +svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs, + const char *node_id, + trail_t *trail, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/nodes-table.c b/subversion/libsvn_fs_base/bdb/nodes-table.c new file mode 100644 index 0000000..d0f475f --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/nodes-table.c @@ -0,0 +1,259 @@ +/* nodes-table.c : working with the `nodes' table + * + * ==================================================================== + * 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 <assert.h> + +#include "bdb_compat.h" + +#include "svn_fs.h" +#include "private/svn_skel.h" + +#include "../fs.h" +#include "../err.h" +#include "dbt.h" +#include "../util/fs_skels.h" +#include "../trail.h" +#include "../key-gen.h" +#include "../id.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "nodes-table.h" + +#include "svn_private_config.h" + + + +/* Opening/creating the `nodes' table. */ + + +int +svn_fs_bdb__open_nodes_table(DB **nodes_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *nodes; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&nodes, env, 0)); + BDB_ERR((nodes->open)(SVN_BDB_OPEN_PARAMS(nodes, NULL), + "nodes", 0, DB_BTREE, + open_flags, 0666)); + + /* Create the `next-key' table entry (use '1' because '0' is + reserved for the root directory to use). */ + if (create) + { + DBT key, value; + + BDB_ERR(nodes->put(nodes, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "1"), 0)); + } + + *nodes_p = nodes; + return 0; +} + + + +/* Choosing node revision ID's. */ + +svn_error_t * +svn_fs_bdb__new_node_id(svn_fs_id_t **id_p, + svn_fs_t *fs, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + apr_size_t len; + char next_key[MAX_KEY_SIZE]; + int db_err; + const char *next_node_id; + + SVN_ERR_ASSERT(txn_id); + + /* Get the current value associated with the `next-key' key in the table. */ + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + svn_fs_base__trail_debug(trail, "nodes", "get"); + SVN_ERR(BDB_WRAP(fs, N_("allocating new node ID (getting 'next-key')"), + bfd->nodes->get(bfd->nodes, trail->db_txn, + &query, + svn_fs_base__result_dbt(&result), + 0))); + svn_fs_base__track_dbt(&result, pool); + + /* Squirrel away our next node id value. */ + next_node_id = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + len = result.size; + svn_fs_base__next_key(result.data, &len, next_key); + svn_fs_base__trail_debug(trail, "nodes", "put"); + db_err = bfd->nodes->put(bfd->nodes, trail->db_txn, + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&result, next_key), + 0); + SVN_ERR(BDB_WRAP(fs, N_("bumping next node ID key"), db_err)); + + /* Create and return the new node id. */ + *id_p = svn_fs_base__id_create(next_node_id, copy_id, txn_id, pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p, + svn_fs_t *fs, + const svn_fs_id_t *id, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_id_t *new_id; + svn_error_t *err; + + SVN_ERR_ASSERT(txn_id); + + /* Create and return the new successor ID. */ + new_id = svn_fs_base__id_create(svn_fs_base__id_node_id(id), + copy_id ? copy_id + : svn_fs_base__id_copy_id(id), + txn_id, pool); + + /* Now, make sure this NEW_ID doesn't already exist in FS. */ + err = svn_fs_bdb__get_node_revision(NULL, fs, new_id, trail, trail->pool); + if ((! err) || (err->apr_err != SVN_ERR_FS_ID_NOT_FOUND)) + { + svn_string_t *id_str = svn_fs_base__id_unparse(id, pool); + svn_string_t *new_id_str = svn_fs_base__id_unparse(new_id, pool); + return svn_error_createf + (SVN_ERR_FS_ALREADY_EXISTS, err, + _("Successor id '%s' (for '%s') already exists in filesystem '%s'"), + new_id_str->data, id_str->data, fs->path); + } + + /* err is SVN_ERR_FS_ID_NOT_FOUND, meaning the ID is available. But + we don't want this error. */ + svn_error_clear(err); + + /* Return the new node revision ID. */ + *successor_p = new_id; + return SVN_NO_ERROR; +} + + + +/* Removing node revisions. */ +svn_error_t * +svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + + svn_fs_base__trail_debug(trail, "nodes", "del"); + return BDB_WRAP(fs, N_("deleting entry from 'nodes' table"), + bfd->nodes->del(bfd->nodes, + trail->db_txn, + svn_fs_base__id_to_dbt(&key, id, pool), + 0)); +} + + + + +/* Storing and retrieving NODE-REVISIONs. */ + + +svn_error_t * +svn_fs_bdb__get_node_revision(node_revision_t **noderev_p, + svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + node_revision_t *noderev; + svn_skel_t *skel; + int db_err; + DBT key, value; + + svn_fs_base__trail_debug(trail, "nodes", "get"); + db_err = bfd->nodes->get(bfd->nodes, trail->db_txn, + svn_fs_base__id_to_dbt(&key, id, pool), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_dangling_id(fs, id); + + /* Handle any other error conditions. */ + SVN_ERR(BDB_WRAP(fs, N_("reading node revision"), db_err)); + + /* If our caller doesn't really care about the return value here, + just return successfully. */ + if (! noderev_p) + return SVN_NO_ERROR; + + /* Parse and the NODE-REVISION skel. */ + skel = svn_skel__parse(value.data, value.size, pool); + + /* Convert to a native FS type. */ + SVN_ERR(svn_fs_base__parse_node_revision_skel(&noderev, skel, pool)); + *noderev_p = noderev; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__put_node_revision(svn_fs_t *fs, + const svn_fs_id_t *id, + node_revision_t *noderev, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DB_TXN *db_txn = trail->db_txn; + DBT key, value; + svn_skel_t *skel; + + /* Convert from native type into skel */ + SVN_ERR(svn_fs_base__unparse_node_revision_skel(&skel, noderev, + bfd->format, pool)); + svn_fs_base__trail_debug(trail, "nodes", "put"); + return BDB_WRAP(fs, N_("storing node revision"), + bfd->nodes->put(bfd->nodes, db_txn, + svn_fs_base__id_to_dbt(&key, id, pool), + svn_fs_base__skel_to_dbt(&value, skel, + pool), + 0)); +} diff --git a/subversion/libsvn_fs_base/bdb/nodes-table.h b/subversion/libsvn_fs_base/bdb/nodes-table.h new file mode 100644 index 0000000..c1687ab --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/nodes-table.h @@ -0,0 +1,121 @@ +/* nodes-table.h : interface to `nodes' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_NODES_TABLE_H +#define SVN_LIBSVN_FS_NODES_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Creating and opening the `nodes' table. */ + + +/* Open a `nodes' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *NODES_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_nodes_table(DB **nodes_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Check FS's `nodes' table to find an unused node number, and set + *ID_P to the ID of the first revision of an entirely new node in + FS, with copy_id COPY_ID, created in transaction TXN_ID, as part + of TRAIL. Allocate the new ID, and do all temporary allocation, + in POOL. */ +svn_error_t *svn_fs_bdb__new_node_id(svn_fs_id_t **id_p, + svn_fs_t *fs, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete node revision ID from FS's `nodes' table, as part of TRAIL. + WARNING: This does not check that the node revision is mutable! + Callers should do that check themselves. + + todo: Jim and Karl are both not sure whether it would be better for + this to check mutability or not. On the one hand, having the + lowest level do that check would seem intuitively good. On the + other hand, we'll need a way to delete even immutable nodes someday + -- for example, someone accidentally commits NDA-protected data to + a public repository and wants to remove it. Thoughts? */ +svn_error_t *svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *SUCCESSOR_P to the ID of an immediate successor to node + revision ID in FS that does not exist yet, as part of TRAIL. + Allocate *SUCCESSOR_P in POOL. + + Use the current Subversion transaction name TXN_ID, and optionally + a copy id COPY_ID, in the determination of the new node revision + ID. */ +svn_error_t *svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p, + svn_fs_t *fs, + const svn_fs_id_t *id, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *NODEREV_P to the node-revision for the node ID in FS, as + part of TRAIL. Do any allocations in POOL. Allow NODEREV_P + to be NULL, in which case it is not used, and this function acts as + an existence check for ID in FS. */ +svn_error_t *svn_fs_bdb__get_node_revision(node_revision_t **noderev_p, + svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool); + + +/* Store NODEREV as the node-revision for the node whose id + is ID in FS, as part of TRAIL. Do any necessary temporary + allocation in POOL. + + After this call, the node table manager assumes that NODE's + contents will change frequently. */ +svn_error_t *svn_fs_bdb__put_node_revision(svn_fs_t *fs, + const svn_fs_id_t *id, + node_revision_t *noderev, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_NODES_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/reps-table.c b/subversion/libsvn_fs_base/bdb/reps-table.c new file mode 100644 index 0000000..1c8ea6d --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/reps-table.c @@ -0,0 +1,204 @@ +/* reps-table.c : operations on the `representations' table + * + * ==================================================================== + * 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 "bdb_compat.h" +#include "svn_fs.h" +#include "../fs.h" +#include "../util/fs_skels.h" +#include "../err.h" +#include "dbt.h" +#include "../trail.h" +#include "../key-gen.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "reps-table.h" +#include "strings-table.h" + + +#include "svn_private_config.h" + + +/*** Creating and opening the representations table. ***/ + +int +svn_fs_bdb__open_reps_table(DB **reps_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *reps; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&reps, env, 0)); + BDB_ERR((reps->open)(SVN_BDB_OPEN_PARAMS(reps, NULL), + "representations", 0, DB_BTREE, + open_flags, 0666)); + + /* Create the `next-key' table entry. */ + if (create) + { + DBT key, value; + + BDB_ERR(reps->put + (reps, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *reps_p = reps; + return 0; +} + + + +/*** Storing and retrieving reps. ***/ + +svn_error_t * +svn_fs_bdb__read_rep(representation_t **rep_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + svn_skel_t *skel; + int db_err; + DBT query, result; + + svn_fs_base__trail_debug(trail, "representations", "get"); + db_err = bfd->representations->get(bfd->representations, + trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), + svn_fs_base__result_dbt(&result), 0); + svn_fs_base__track_dbt(&result, pool); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0, + _("No such representation '%s'"), key); + + /* Handle any other error conditions. */ + SVN_ERR(BDB_WRAP(fs, N_("reading representation"), db_err)); + + /* Parse the REPRESENTATION skel. */ + skel = svn_skel__parse(result.data, result.size, pool); + + /* Convert to a native type. */ + return svn_fs_base__parse_representation_skel(rep_p, skel, pool); +} + + +svn_error_t * +svn_fs_bdb__write_rep(svn_fs_t *fs, + const char *key, + const representation_t *rep, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + svn_skel_t *skel; + + /* Convert from native type to skel. */ + SVN_ERR(svn_fs_base__unparse_representation_skel(&skel, rep, + bfd->format, pool)); + + /* Now write the record. */ + svn_fs_base__trail_debug(trail, "representations", "put"); + return BDB_WRAP(fs, N_("storing representation"), + bfd->representations->put + (bfd->representations, trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), + svn_fs_base__skel_to_dbt(&result, skel, pool), + 0)); +} + + +svn_error_t * +svn_fs_bdb__write_new_rep(const char **key, + svn_fs_t *fs, + const representation_t *rep, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + int db_err; + apr_size_t len; + char next_key[MAX_KEY_SIZE]; + + /* ### todo: see issue #409 for why bumping the key as part of this + trail is problematic. */ + + /* Get the current value associated with `next-key'. */ + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + svn_fs_base__trail_debug(trail, "representations", "get"); + SVN_ERR(BDB_WRAP(fs, N_("allocating new representation (getting next-key)"), + bfd->representations->get + (bfd->representations, trail->db_txn, &query, + svn_fs_base__result_dbt(&result), 0))); + + svn_fs_base__track_dbt(&result, pool); + + /* Store the new rep. */ + *key = apr_pstrmemdup(pool, result.data, result.size); + SVN_ERR(svn_fs_bdb__write_rep(fs, *key, rep, trail, pool)); + + /* Bump to future key. */ + len = result.size; + svn_fs_base__next_key(result.data, &len, next_key); + svn_fs_base__trail_debug(trail, "representations", "put"); + db_err = bfd->representations->put + (bfd->representations, trail->db_txn, + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&result, next_key), + 0); + + return BDB_WRAP(fs, N_("bumping next representation key"), db_err); +} + + +svn_error_t * +svn_fs_bdb__delete_rep(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT query; + + svn_fs_base__trail_debug(trail, "representations", "del"); + db_err = bfd->representations->del + (bfd->representations, trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), 0); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0, + _("No such representation '%s'"), key); + + /* Handle any other error conditions. */ + return BDB_WRAP(fs, N_("deleting representation"), db_err); +} diff --git a/subversion/libsvn_fs_base/bdb/reps-table.h b/subversion/libsvn_fs_base/bdb/reps-table.h new file mode 100644 index 0000000..b5cd27d --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/reps-table.h @@ -0,0 +1,94 @@ +/* reps-table.h : internal interface to `representations' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_REPS_TABLE_H +#define SVN_LIBSVN_FS_REPS_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_io.h" +#include "svn_fs.h" +#include "../fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** Creating the `representations' table. ***/ + +/* Open a `representations' table in ENV. If CREATE is non-zero, + create one if it doesn't exist. Set *REPS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_reps_table(DB **reps_p, + DB_ENV *env, + svn_boolean_t create); + + + +/*** Storing and retrieving reps. ***/ + +/* Set *REP_P to point to the representation for the key KEY in + FS, as part of TRAIL. Perform all allocations in POOL. + + If KEY is not a representation in FS, the error + SVN_ERR_FS_NO_SUCH_REPRESENTATION is returned. */ +svn_error_t *svn_fs_bdb__read_rep(representation_t **rep_p, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +/* Store REP as the representation for KEY in FS, as part of + TRAIL. Do any necessary temporary allocation in POOL. */ +svn_error_t *svn_fs_bdb__write_rep(svn_fs_t *fs, + const char *key, + const representation_t *rep, + trail_t *trail, + apr_pool_t *pool); + + +/* Store REP as a new representation in FS, and the new rep's key in + *KEY, as part of trail. The new key is allocated in POOL. */ +svn_error_t *svn_fs_bdb__write_new_rep(const char **key, + svn_fs_t *fs, + const representation_t *rep, + trail_t *trail, + apr_pool_t *pool); + +/* Delete representation KEY from FS, as part of TRAIL. + WARNING: This does not ensure that no one references this + representation! Callers should ensure that themselves. */ +svn_error_t *svn_fs_bdb__delete_rep(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_REPS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/rev-table.c b/subversion/libsvn_fs_base/bdb/rev-table.c new file mode 100644 index 0000000..b752249 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/rev-table.c @@ -0,0 +1,221 @@ + /* rev-table.c : working with the `revisions' table + * + * ==================================================================== + * 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 "bdb_compat.h" + +#include "svn_fs.h" +#include "private/svn_skel.h" + +#include "../fs.h" +#include "../err.h" +#include "../util/fs_skels.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "dbt.h" +#include "rev-table.h" + +#include "svn_private_config.h" +#include "private/svn_fs_util.h" + + +/* Opening/creating the `revisions' table. */ + +int svn_fs_bdb__open_revisions_table(DB **revisions_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *revisions; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&revisions, env, 0)); + BDB_ERR((revisions->open)(SVN_BDB_OPEN_PARAMS(revisions, NULL), + "revisions", 0, DB_RECNO, + open_flags, 0666)); + + *revisions_p = revisions; + return 0; +} + + + +/* Storing and retrieving filesystem revisions. */ + + +svn_error_t * +svn_fs_bdb__get_rev(revision_t **revision_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT key, value; + svn_skel_t *skel; + revision_t *revision; + + /* Turn the revision number into a Berkeley DB record number. + Revisions are numbered starting with zero; Berkeley DB record + numbers begin with one. */ + db_recno_t recno = (db_recno_t) rev + 1; + + svn_fs_base__trail_debug(trail, "revisions", "get"); + db_err = bfd->revisions->get(bfd->revisions, trail->db_txn, + svn_fs_base__set_dbt(&key, &recno, + sizeof(recno)), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + /* If there's no such revision, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_dangling_rev(fs, rev); + + /* Handle any other error conditions. */ + SVN_ERR(BDB_WRAP(fs, N_("reading filesystem revision"), db_err)); + + /* Parse REVISION skel. */ + skel = svn_skel__parse(value.data, value.size, pool); + if (! skel) + return svn_fs_base__err_corrupt_fs_revision(fs, rev); + + /* Convert skel to native type. */ + SVN_ERR(svn_fs_base__parse_revision_skel(&revision, skel, pool)); + + *revision_p = revision; + return SVN_NO_ERROR; +} + + +/* Write REVISION to FS as part of TRAIL. If *REV is a valid revision + number, write this revision as one that corresponds to *REV, else + write a new revision and return its newly created revision number + in *REV. */ +svn_error_t * +svn_fs_bdb__put_rev(svn_revnum_t *rev, + svn_fs_t *fs, + const revision_t *revision, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + db_recno_t recno = 0; + svn_skel_t *skel; + DBT key, value; + + /* Convert native type to skel. */ + SVN_ERR(svn_fs_base__unparse_revision_skel(&skel, revision, pool)); + + if (SVN_IS_VALID_REVNUM(*rev)) + { + DBT query, result; + + /* Update the filesystem revision with the new skel. */ + recno = (db_recno_t) *rev + 1; + svn_fs_base__trail_debug(trail, "revisions", "put"); + db_err = bfd->revisions->put + (bfd->revisions, trail->db_txn, + svn_fs_base__set_dbt(&query, &recno, sizeof(recno)), + svn_fs_base__skel_to_dbt(&result, skel, pool), 0); + return BDB_WRAP(fs, N_("updating filesystem revision"), db_err); + } + + svn_fs_base__trail_debug(trail, "revisions", "put"); + db_err = bfd->revisions->put(bfd->revisions, trail->db_txn, + svn_fs_base__recno_dbt(&key, &recno), + svn_fs_base__skel_to_dbt(&value, skel, pool), + DB_APPEND); + SVN_ERR(BDB_WRAP(fs, N_("storing filesystem revision"), db_err)); + + /* Turn the record number into a Subversion revision number. + Revisions are numbered starting with zero; Berkeley DB record + numbers begin with one. */ + *rev = recno - 1; + return SVN_NO_ERROR; +} + + + +/* Getting the youngest revision. */ + + +svn_error_t * +svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBC *cursor = 0; + DBT key, value; + db_recno_t recno; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + /* Create a database cursor. */ + svn_fs_base__trail_debug(trail, "revisions", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (creating cursor)"), + bfd->revisions->cursor(bfd->revisions, trail->db_txn, + &cursor, 0))); + + /* Find the last entry in the `revisions' table. */ + db_err = svn_bdb_dbc_get(cursor, + svn_fs_base__recno_dbt(&key, &recno), + svn_fs_base__nodata_dbt(&value), + DB_LAST); + + if (db_err) + { + /* Free the cursor. Ignore any error value --- the error above + is more interesting. */ + svn_bdb_dbc_close(cursor); + + if (db_err == DB_NOTFOUND) + /* The revision 0 should always be present, at least. */ + return + svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + "Corrupt DB: revision 0 missing from 'revisions' table, in " + "filesystem '%s'", fs->path); + + SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (finding last entry)"), + db_err)); + } + + /* You can't commit a transaction with open cursors, because: + 1) key/value pairs don't get deleted until the cursors referring + to them are closed, so closing a cursor can fail for various + reasons, and txn_commit shouldn't fail that way, and + 2) using a cursor after committing its transaction can cause + undetectable database corruption. */ + SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (closing cursor)"), + svn_bdb_dbc_close(cursor))); + + /* Turn the record number into a Subversion revision number. + Revisions are numbered starting with zero; Berkeley DB record + numbers begin with one. */ + *youngest_p = recno - 1; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/rev-table.h b/subversion/libsvn_fs_base/bdb/rev-table.h new file mode 100644 index 0000000..47209a8 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/rev-table.h @@ -0,0 +1,85 @@ +/* rev-table.h : internal interface to revision table operations + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_REV_TABLE_H +#define SVN_LIBSVN_FS_REV_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" + +#include "../fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Creating and opening the `revisions' table. */ + +/* Open a `revisions' table in ENV. If CREATE is non-zero, create one + if it doesn't exist. Set *REVS_P to the new table. Return a + Berkeley DB error code. */ +int svn_fs_bdb__open_revisions_table(DB **revisions_p, + DB_ENV *env, + svn_boolean_t create); + + + +/* Storing and retrieving filesystem revisions. */ + + +/* Set *REVISION_P to point to the revision structure for the + filesystem revision REV in FS, as part of TRAIL. Perform all + allocations in POOL. */ +svn_error_t *svn_fs_bdb__get_rev(revision_t **revision_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool); + +/* Store REVISION in FS as revision *REV as part of TRAIL. If *REV is + an invalid revision number, create a brand new revision and return + its revision number as *REV to the caller. Do any necessary + temporary allocation in POOL. */ +svn_error_t *svn_fs_bdb__put_rev(svn_revnum_t *rev, + svn_fs_t *fs, + const revision_t *revision, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *YOUNGEST_P to the youngest revision in filesystem FS, + as part of TRAIL. Use POOL for all temporary allocation. */ +svn_error_t *svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_REV_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/strings-table.c b/subversion/libsvn_fs_base/bdb/strings-table.c new file mode 100644 index 0000000..f5348e7 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/strings-table.c @@ -0,0 +1,541 @@ +/* strings-table.c : operations on the `strings' table + * + * ==================================================================== + * 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 "bdb_compat.h" +#include "svn_fs.h" +#include "svn_pools.h" +#include "../fs.h" +#include "../err.h" +#include "dbt.h" +#include "../trail.h" +#include "../key-gen.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "strings-table.h" + +#include "svn_private_config.h" + + +/*** Creating and opening the strings table. ***/ + +int +svn_fs_bdb__open_strings_table(DB **strings_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *strings; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&strings, env, 0)); + + /* Enable duplicate keys. This allows the data to be spread out across + multiple records. Note: this must occur before ->open(). */ + BDB_ERR(strings->set_flags(strings, DB_DUP)); + + BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL), + "strings", 0, DB_BTREE, + open_flags, 0666)); + + if (create) + { + DBT key, value; + + /* Create the `next-key' table entry. */ + BDB_ERR(strings->put + (strings, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *strings_p = strings; + return 0; +} + + + +/*** Storing and retrieving strings. ***/ + +/* Allocate *CURSOR and advance it to first row in the set of rows + whose key is defined by QUERY. Set *LENGTH to the size of that + first row. */ +static svn_error_t * +locate_key(apr_size_t *length, + DBC **cursor, + DBT *query, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT result; + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + cursor, 0))); + + /* Set up the DBT for reading the length of the record. */ + svn_fs_base__clear_dbt(&result); + result.ulen = 0; + result.flags |= DB_DBT_USERMEM; + + /* Advance the cursor to the key that we're looking for. */ + db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET); + + /* We don't need to svn_fs_base__track_dbt() the result, because nothing + was allocated in it. */ + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + { + svn_bdb_dbc_close(*cursor); + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", (const char *)query->data); + } + if (db_err) + { + DBT rerun; + + if (db_err != SVN_BDB_DB_BUFFER_SMALL) + { + svn_bdb_dbc_close(*cursor); + return BDB_WRAP(fs, N_("moving cursor"), db_err); + } + + /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a + zero length buf), so we need to re-run the operation to make + it happen. */ + svn_fs_base__clear_dbt(&rerun); + rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; + db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET); + if (db_err) + { + svn_bdb_dbc_close(*cursor); + return BDB_WRAP(fs, N_("rerunning cursor move"), db_err); + } + } + + /* ### this cast might not be safe? */ + *length = (apr_size_t) result.size; + + return SVN_NO_ERROR; +} + + +/* Advance CURSOR by a single row in the set of rows whose keys match + CURSOR's current location. Set *LENGTH to the size of that next + row. If any error occurs, CURSOR will be destroyed. */ +static int +get_next_length(apr_size_t *length, DBC *cursor, DBT *query) +{ + DBT result; + int db_err; + + /* Set up the DBT for reading the length of the record. */ + svn_fs_base__clear_dbt(&result); + result.ulen = 0; + result.flags |= DB_DBT_USERMEM; + + /* Note: this may change the QUERY DBT, but that's okay: we're going + to be sticking with the same key anyways. */ + db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP); + + /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */ + if (db_err) + { + DBT rerun; + + if (db_err != SVN_BDB_DB_BUFFER_SMALL) + { + svn_bdb_dbc_close(cursor); + return db_err; + } + + /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a + zero length buf), so we need to re-run the operation to make + it happen. */ + svn_fs_base__clear_dbt(&rerun); + rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; + db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP); + if (db_err) + svn_bdb_dbc_close(cursor); + } + + /* ### this cast might not be safe? */ + *length = (apr_size_t) result.size; + return db_err; +} + + +svn_error_t * +svn_fs_bdb__string_read(svn_fs_t *fs, + const char *key, + char *buf, + svn_filesize_t offset, + apr_size_t *len, + trail_t *trail, + apr_pool_t *pool) +{ + int db_err; + DBT query, result; + DBC *cursor; + apr_size_t length, bytes_read = 0; + + svn_fs_base__str_to_dbt(&query, key); + + SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); + + /* Seek through the records for this key, trying to find the record that + includes OFFSET. Note that we don't require reading from more than + one record since we're allowed to return partial reads. */ + while (length <= offset) + { + offset -= length; + + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + + /* No more records? They tried to read past the end. */ + if (db_err == DB_NOTFOUND) + { + *len = 0; + return SVN_NO_ERROR; + } + if (db_err) + return BDB_WRAP(fs, N_("reading string"), db_err); + } + + /* The current record contains OFFSET. Fetch the contents now. Note that + OFFSET has been moved to be relative to this record. The length could + quite easily extend past this record, so we use DB_DBT_PARTIAL and + read successive records until we've filled the request. */ + while (1) + { + svn_fs_base__clear_dbt(&result); + result.data = buf + bytes_read; + result.ulen = *len - bytes_read; + result.doff = (u_int32_t)offset; + result.dlen = *len - bytes_read; + result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("reading string"), db_err); + } + + bytes_read += result.size; + if (bytes_read == *len) + { + /* Done with the cursor. */ + SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor))); + break; + } + + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + if (db_err == DB_NOTFOUND) + break; + if (db_err) + return BDB_WRAP(fs, N_("reading string"), db_err); + + /* We'll be reading from the beginning of the next record */ + offset = 0; + } + + *len = bytes_read; + return SVN_NO_ERROR; +} + + +/* Get the current 'next-key' value and bump the record. */ +static svn_error_t * +get_key_and_bump(svn_fs_t *fs, + const char **key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBC *cursor; + char next_key[MAX_KEY_SIZE]; + apr_size_t key_len; + int db_err; + DBT query; + DBT result; + + /* ### todo: see issue #409 for why bumping the key as part of this + trail is problematic. */ + + /* Open a cursor and move it to the 'next-key' value. We can then fetch + the contents and use the cursor to overwrite those contents. Since + this database allows duplicates, we can't do an arbitrary 'put' to + write the new value -- that would append, not overwrite. */ + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + &cursor, 0))); + + /* Advance the cursor to 'next-key' and read it. */ + + db_err = svn_bdb_dbc_get(cursor, + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), + svn_fs_base__result_dbt(&result), + DB_SET); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("getting next-key value"), db_err); + } + + svn_fs_base__track_dbt(&result, pool); + *key = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + key_len = result.size; + svn_fs_base__next_key(result.data, &key_len, next_key); + + /* Shove the new key back into the database, at the cursor position. */ + db_err = svn_bdb_dbc_put(cursor, &query, + svn_fs_base__str_to_dbt(&result, next_key), + DB_CURRENT); + if (db_err) + { + svn_bdb_dbc_close(cursor); /* ignore the error, the original is + more important. */ + return BDB_WRAP(fs, N_("bumping next string key"), db_err); + } + + return BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor)); +} + +svn_error_t * +svn_fs_bdb__string_append(svn_fs_t *fs, + const char **key, + apr_size_t len, + const char *buf, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + + /* If the passed-in key is NULL, we graciously generate a new string + using the value of the `next-key' record in the strings table. */ + if (*key == NULL) + { + SVN_ERR(get_key_and_bump(fs, key, trail, pool)); + } + + /* Store a new record into the database. */ + svn_fs_base__trail_debug(trail, "strings", "put"); + return BDB_WRAP(fs, N_("appending string"), + bfd->strings->put + (bfd->strings, trail->db_txn, + svn_fs_base__str_to_dbt(&query, *key), + svn_fs_base__set_dbt(&result, buf, len), + 0)); +} + + +svn_error_t * +svn_fs_bdb__string_clear(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT query, result; + + svn_fs_base__str_to_dbt(&query, key); + + /* Torch the prior contents */ + svn_fs_base__trail_debug(trail, "strings", "del"); + db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", key); + + /* Handle any other error conditions. */ + SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err)); + + /* Shove empty data back in for this key. */ + svn_fs_base__clear_dbt(&result); + result.data = 0; + result.size = 0; + result.flags |= DB_DBT_USERMEM; + + svn_fs_base__trail_debug(trail, "strings", "put"); + return BDB_WRAP(fs, N_("storing empty contents"), + bfd->strings->put(bfd->strings, trail->db_txn, + &query, &result, 0)); +} + + +svn_error_t * +svn_fs_bdb__string_size(svn_filesize_t *size, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + int db_err; + DBT query; + DBC *cursor; + apr_size_t length; + svn_filesize_t total; + + svn_fs_base__str_to_dbt(&query, key); + + SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); + + total = length; + while (1) + { + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + + /* No more records? Then return the total length. */ + if (db_err == DB_NOTFOUND) + { + *size = total; + return SVN_NO_ERROR; + } + if (db_err) + return BDB_WRAP(fs, N_("fetching string length"), db_err); + + total += length; + } + + /* NOTREACHED */ +} + + +svn_error_t * +svn_fs_bdb__string_delete(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT query; + + svn_fs_base__trail_debug(trail, "strings", "del"); + db_err = bfd->strings->del(bfd->strings, trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), 0); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", key); + + /* Handle any other error conditions. */ + return BDB_WRAP(fs, N_("deleting string"), db_err); +} + + +svn_error_t * +svn_fs_bdb__string_copy(svn_fs_t *fs, + const char **new_key, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query; + DBT result; + DBT copykey; + DBC *cursor; + int db_err; + + /* Copy off the old key in case the caller is sharing storage + between the old and new keys. */ + const char *old_key = apr_pstrdup(pool, key); + + SVN_ERR(get_key_and_bump(fs, new_key, trail, pool)); + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + &cursor, 0))); + + svn_fs_base__str_to_dbt(&query, old_key); + svn_fs_base__str_to_dbt(©key, *new_key); + + svn_fs_base__clear_dbt(&result); + + /* Move to the first record and fetch its data (under BDB's mem mgmt). */ + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("getting next-key value"), db_err); + } + + while (1) + { + /* ### can we pass a BDB-provided buffer to another BDB function? + ### they are supposed to have a duration up to certain points + ### of calling back into BDB, but I'm not sure what the exact + ### rules are. it is definitely nicer to use BDB buffers here + ### to simplify things and reduce copies, but... hrm. + */ + + /* Write the data to the database */ + svn_fs_base__trail_debug(trail, "strings", "put"); + db_err = bfd->strings->put(bfd->strings, trail->db_txn, + ©key, &result, 0); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("writing copied data"), db_err); + } + + /* Read the next chunk. Terminate loop if we're done. */ + svn_fs_base__clear_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); + if (db_err == DB_NOTFOUND) + break; + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err); + } + } + + return BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor)); +} diff --git a/subversion/libsvn_fs_base/bdb/strings-table.h b/subversion/libsvn_fs_base/bdb/strings-table.h new file mode 100644 index 0000000..443cb72 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/strings-table.h @@ -0,0 +1,143 @@ +/* strings-table.h : internal interface to `strings' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_STRINGS_TABLE_H +#define SVN_LIBSVN_FS_STRINGS_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_io.h" +#include "svn_fs.h" +#include "../trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* This interface provides raw access to the `strings' table. It does + not deal with deltification, undeltification, or skels. It just + reads and writes strings of bytes. */ + + +/* Open a `strings' table in ENV. If CREATE is non-zero, create + * one if it doesn't exist. Set *STRINGS_P to the new table. + * Return a Berkeley DB error code. + */ +int svn_fs_bdb__open_strings_table(DB **strings_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part + * of TRAIL. + * + * On return, *LEN is set to the number of bytes read. If this value + * is less than the number requested, the end of the string has been + * reached (no error is returned on end-of-string). + * + * If OFFSET is past the end of the string, then *LEN will be set to + * zero. Callers which are advancing OFFSET as they read portions of + * the string can terminate their loop when *LEN is returned as zero + * (which will occur when OFFSET == length(the string)). + * + * If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING + * is returned. + */ +svn_error_t *svn_fs_bdb__string_read(svn_fs_t *fs, + const char *key, + char *buf, + svn_filesize_t offset, + apr_size_t *len, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *SIZE to the size in bytes of string KEY in FS, as part of + * TRAIL. + * + * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING. + */ +svn_error_t *svn_fs_bdb__string_size(svn_filesize_t *size, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +/* Append LEN bytes from BUF to string *KEY in FS, as part of TRAIL. + * + * If *KEY is null, then create a new string and store the new key in + * *KEY (allocating it in POOL), and write LEN bytes from BUF + * as the initial contents of the string. + * + * If *KEY is not null but there is no string named *KEY, return + * SVN_ERR_FS_NO_SUCH_STRING. + * + * Note: to overwrite the old contents of a string, call + * svn_fs_bdb__string_clear() and then svn_fs_bdb__string_append(). */ +svn_error_t *svn_fs_bdb__string_append(svn_fs_t *fs, + const char **key, + apr_size_t len, + const char *buf, + trail_t *trail, + apr_pool_t *pool); + + +/* Make string KEY in FS zero length, as part of TRAIL. + * If the string does not exist, return SVN_ERR_FS_NO_SUCH_STRING. + */ +svn_error_t *svn_fs_bdb__string_clear(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete string KEY from FS, as part of TRAIL. + * + * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING. + * + * WARNING: Deleting a string renders unusable any representations + * that refer to it. Be careful. + */ +svn_error_t *svn_fs_bdb__string_delete(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +/* Copy the contents of the string referred to by KEY in FS into a new + * record, returning the new record's key in *NEW_KEY. All + * allocations (including *NEW_KEY) occur in POOL. */ +svn_error_t *svn_fs_bdb__string_copy(svn_fs_t *fs, + const char **new_key, + const char *key, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_STRINGS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/txn-table.c b/subversion/libsvn_fs_base/bdb/txn-table.c new file mode 100644 index 0000000..54a0e28 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/txn-table.c @@ -0,0 +1,325 @@ +/* txn-table.c : operations on the `transactions' table + * + * ==================================================================== + * 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 <assert.h> + +#include "bdb_compat.h" + +#include "svn_pools.h" +#include "private/svn_skel.h" + +#include "dbt.h" +#include "../err.h" +#include "../fs.h" +#include "../key-gen.h" +#include "../util/fs_skels.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "txn-table.h" + +#include "svn_private_config.h" + + +static svn_boolean_t +is_committed(transaction_t *txn) +{ + return (txn->kind == transaction_kind_committed); +} + + +int +svn_fs_bdb__open_transactions_table(DB **transactions_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *txns; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&txns, env, 0)); + BDB_ERR((txns->open)(SVN_BDB_OPEN_PARAMS(txns, NULL), + "transactions", 0, DB_BTREE, + open_flags, 0666)); + + /* Create the `next-key' table entry. */ + if (create) + { + DBT key, value; + + BDB_ERR(txns->put(txns, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *transactions_p = txns; + return 0; +} + + +svn_error_t * +svn_fs_bdb__put_txn(svn_fs_t *fs, + const transaction_t *txn, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + svn_skel_t *txn_skel; + DBT key, value; + + /* Convert native type to skel. */ + SVN_ERR(svn_fs_base__unparse_transaction_skel(&txn_skel, txn, pool)); + + /* Only in the context of this function do we know that the DB call + will not attempt to modify txn_name, so the cast belongs here. */ + svn_fs_base__str_to_dbt(&key, txn_name); + svn_fs_base__skel_to_dbt(&value, txn_skel, pool); + svn_fs_base__trail_debug(trail, "transactions", "put"); + return BDB_WRAP(fs, N_("storing transaction record"), + bfd->transactions->put(bfd->transactions, trail->db_txn, + &key, &value, 0)); +} + + +/* Allocate a Subversion transaction ID in FS, as part of TRAIL. Set + *ID_P to the new transaction ID, allocated in POOL. */ +static svn_error_t * +allocate_txn_id(const char **id_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + apr_size_t len; + char next_key[MAX_KEY_SIZE]; + int db_err; + + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + + /* Get the current value associated with the `next-key' key in the table. */ + svn_fs_base__trail_debug(trail, "transactions", "get"); + SVN_ERR(BDB_WRAP(fs, N_("allocating new transaction ID (getting 'next-key')"), + bfd->transactions->get(bfd->transactions, trail->db_txn, + &query, + svn_fs_base__result_dbt(&result), + 0))); + svn_fs_base__track_dbt(&result, pool); + + /* Set our return value. */ + *id_p = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + len = result.size; + svn_fs_base__next_key(result.data, &len, next_key); + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); + svn_fs_base__str_to_dbt(&result, next_key); + svn_fs_base__trail_debug(trail, "transactions", "put"); + db_err = bfd->transactions->put(bfd->transactions, trail->db_txn, + &query, &result, 0); + + return BDB_WRAP(fs, N_("bumping next transaction key"), db_err); +} + + +svn_error_t * +svn_fs_bdb__create_txn(const char **txn_name_p, + svn_fs_t *fs, + const svn_fs_id_t *root_id, + trail_t *trail, + apr_pool_t *pool) +{ + const char *txn_name; + transaction_t txn; + + SVN_ERR(allocate_txn_id(&txn_name, fs, trail, pool)); + txn.kind = transaction_kind_normal; + txn.root_id = root_id; + txn.base_id = root_id; + txn.proplist = NULL; + txn.copies = NULL; + txn.revision = SVN_INVALID_REVNUM; + SVN_ERR(svn_fs_bdb__put_txn(fs, &txn, txn_name, trail, pool)); + + *txn_name_p = txn_name; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__delete_txn(svn_fs_t *fs, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key; + transaction_t *txn; + + /* Make sure TXN is dead. */ + SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_name, trail, pool)); + if (is_committed(txn)) + return svn_fs_base__err_txn_not_mutable(fs, txn_name); + + /* Delete the transaction from the `transactions' table. */ + svn_fs_base__str_to_dbt(&key, txn_name); + svn_fs_base__trail_debug(trail, "transactions", "del"); + return BDB_WRAP(fs, N_("deleting entry from 'transactions' table"), + bfd->transactions->del(bfd->transactions, + trail->db_txn, &key, 0)); +} + + +svn_error_t * +svn_fs_bdb__get_txn(transaction_t **txn_p, + svn_fs_t *fs, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT key, value; + int db_err; + svn_skel_t *skel; + transaction_t *transaction; + + /* Only in the context of this function do we know that the DB call + will not attempt to modify txn_name, so the cast belongs here. */ + svn_fs_base__trail_debug(trail, "transactions", "get"); + db_err = bfd->transactions->get(bfd->transactions, trail->db_txn, + svn_fs_base__str_to_dbt(&key, txn_name), + svn_fs_base__result_dbt(&value), + 0); + svn_fs_base__track_dbt(&value, pool); + + if (db_err == DB_NOTFOUND) + return svn_fs_base__err_no_such_txn(fs, txn_name); + SVN_ERR(BDB_WRAP(fs, N_("reading transaction"), db_err)); + + /* Parse TRANSACTION skel */ + skel = svn_skel__parse(value.data, value.size, pool); + if (! skel) + return svn_fs_base__err_corrupt_txn(fs, txn_name); + + /* Convert skel to native type. */ + SVN_ERR(svn_fs_base__parse_transaction_skel(&transaction, skel, pool)); + *txn_p = transaction; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_bdb__get_txn_list(apr_array_header_t **names_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + apr_size_t const next_key_key_len = strlen(NEXT_KEY_KEY); + apr_pool_t *subpool = svn_pool_create(pool); + apr_array_header_t *names; + DBC *cursor; + DBT key, value; + int db_err, db_c_err; + + /* Allocate the initial names array */ + names = apr_array_make(pool, 4, sizeof(const char *)); + + /* Create a database cursor to list the transaction names. */ + svn_fs_base__trail_debug(trail, "transactions", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (opening cursor)"), + bfd->transactions->cursor(bfd->transactions, + trail->db_txn, &cursor, 0))); + + /* Build a null-terminated array of keys in the transactions table. */ + for (db_err = svn_bdb_dbc_get(cursor, + svn_fs_base__result_dbt(&key), + svn_fs_base__result_dbt(&value), + DB_FIRST); + db_err == 0; + db_err = svn_bdb_dbc_get(cursor, + svn_fs_base__result_dbt(&key), + svn_fs_base__result_dbt(&value), + DB_NEXT)) + { + transaction_t *txn; + svn_skel_t *txn_skel; + svn_error_t *err; + + /* Clear the per-iteration subpool */ + svn_pool_clear(subpool); + + /* Track the memory alloc'd for fetching the key and value here + so that when the containing pool is cleared, this memory is + freed. */ + svn_fs_base__track_dbt(&key, subpool); + svn_fs_base__track_dbt(&value, subpool); + + /* Ignore the "next-key" key. */ + if (key.size == next_key_key_len + && 0 == memcmp(key.data, NEXT_KEY_KEY, next_key_key_len)) + continue; + + /* Parse TRANSACTION skel */ + txn_skel = svn_skel__parse(value.data, value.size, subpool); + if (! txn_skel) + { + svn_bdb_dbc_close(cursor); + return svn_fs_base__err_corrupt_txn + (fs, apr_pstrmemdup(pool, key.data, key.size)); + } + + /* Convert skel to native type. */ + if ((err = svn_fs_base__parse_transaction_skel(&txn, txn_skel, + subpool))) + { + svn_bdb_dbc_close(cursor); + return svn_error_trace(err); + } + + /* If this is an immutable "committed" transaction, ignore it. */ + if (is_committed(txn)) + continue; + + /* Add the transaction name to the NAMES array, duping it into POOL. */ + APR_ARRAY_PUSH(names, const char *) = apr_pstrmemdup(pool, key.data, + key.size); + } + + /* Check for errors, but close the cursor first. */ + db_c_err = svn_bdb_dbc_close(cursor); + if (db_err != DB_NOTFOUND) + { + SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (listing keys)"), + db_err)); + } + SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (closing cursor)"), + db_c_err)); + + /* Destroy the per-iteration subpool */ + svn_pool_destroy(subpool); + + *names_p = names; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/bdb/txn-table.h b/subversion/libsvn_fs_base/bdb/txn-table.h new file mode 100644 index 0000000..ff0cc9c --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/txn-table.h @@ -0,0 +1,100 @@ +/* txn-table.h : internal interface to ops on `transactions' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_TXN_TABLE_H +#define SVN_LIBSVN_FS_TXN_TABLE_H + +#include "svn_fs.h" +#include "svn_error.h" +#include "../trail.h" +#include "../fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `transactions' table in ENV. If CREATE is non-zero, create + one if it doesn't exist. Set *TRANSACTIONS_P to the new table. + Return a Berkeley DB error code. */ +int svn_fs_bdb__open_transactions_table(DB **transactions_p, + DB_ENV *env, + svn_boolean_t create); + + +/* Create a new transaction in FS as part of TRAIL, with an initial + root and base root ID of ROOT_ID. Set *TXN_NAME_P to the name of the + new transaction, allocated in POOL. */ +svn_error_t *svn_fs_bdb__create_txn(const char **txn_name_p, + svn_fs_t *fs, + const svn_fs_id_t *root_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Remove the transaction whose name is TXN_NAME from the `transactions' + table of FS, as part of TRAIL. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +svn_error_t *svn_fs_bdb__delete_txn(svn_fs_t *fs, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool); + + +/* Retrieve the transaction *TXN_P for the Subversion transaction + named TXN_NAME from the `transactions' table of FS, as part of + TRAIL. Perform all allocations in POOL. + + If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is + the error returned. */ +svn_error_t *svn_fs_bdb__get_txn(transaction_t **txn_p, + svn_fs_t *fs, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool); + + +/* Store the Subversion transaction TXN in FS with an ID of TXN_NAME as + part of TRAIL. */ +svn_error_t *svn_fs_bdb__put_txn(svn_fs_t *fs, + const transaction_t *txn, + const char *txn_name, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *NAMES_P to an array of const char * IDs (unfinished + transactions in FS) as part of TRAIL. Allocate the array and the + names in POOL, and use POOL for any temporary allocations. */ +svn_error_t *svn_fs_bdb__get_txn_list(apr_array_header_t **names_p, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_TXN_TABLE_H */ diff --git a/subversion/libsvn_fs_base/bdb/uuids-table.c b/subversion/libsvn_fs_base/bdb/uuids-table.c new file mode 100644 index 0000000..0481894 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/uuids-table.c @@ -0,0 +1,149 @@ +/* uuids-table.c : operations on the `uuids' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_uuid.h> + +#include "bdb_compat.h" +#include "svn_fs.h" +#include "../fs.h" +#include "../err.h" +#include "dbt.h" +#include "../trail.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "uuids-table.h" + +#include "svn_private_config.h" + + +/*** Creating and opening the uuids table. + When the table is created, the repository's uuid is + generated and stored as record #1. ***/ + +int +svn_fs_bdb__open_uuids_table(DB **uuids_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *uuids; + int error; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&uuids, env, 0)); + BDB_ERR(uuids->set_re_len(uuids, APR_UUID_FORMATTED_LENGTH)); + + error = (uuids->open)(SVN_BDB_OPEN_PARAMS(uuids, NULL), + "uuids", 0, DB_RECNO, + open_flags, 0666); + + /* This is a temporary compatibility check; it creates the + UUIDs table if one does not already exist. */ + if (error == ENOENT && (! create)) + { + BDB_ERR(uuids->close(uuids, 0)); + return svn_fs_bdb__open_uuids_table(uuids_p, env, TRUE); + } + + BDB_ERR(error); + + if (create) + { + char buffer[APR_UUID_FORMATTED_LENGTH + 1]; + DBT key, value; + apr_uuid_t uuid; + int recno = 0; + + svn_fs_base__clear_dbt(&key); + key.data = &recno; + key.size = sizeof(recno); + key.ulen = key.size; + key.flags |= DB_DBT_USERMEM; + + svn_fs_base__clear_dbt(&value); + value.data = buffer; + value.size = sizeof(buffer) - 1; + + apr_uuid_get(&uuid); + apr_uuid_format(buffer, &uuid); + + BDB_ERR(uuids->put(uuids, 0, &key, &value, DB_APPEND)); + } + + *uuids_p = uuids; + return 0; +} + +svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs, + int idx, + const char **uuid, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + char buffer[APR_UUID_FORMATTED_LENGTH + 1]; + DB *uuids = bfd->uuids; + DBT key; + DBT value; + + svn_fs_base__clear_dbt(&key); + key.data = &idx; + key.size = sizeof(idx); + + svn_fs_base__clear_dbt(&value); + value.data = buffer; + value.size = sizeof(buffer) - 1; + value.ulen = value.size; + value.flags |= DB_DBT_USERMEM; + + svn_fs_base__trail_debug(trail, "uuids", "get"); + SVN_ERR(BDB_WRAP(fs, N_("get repository uuid"), + uuids->get(uuids, trail->db_txn, &key, &value, 0))); + + *uuid = apr_pstrmemdup(pool, value.data, value.size); + + return SVN_NO_ERROR; +} + +svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs, + int idx, + const char *uuid, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DB *uuids = bfd->uuids; + DBT key; + DBT value; + + svn_fs_base__clear_dbt(&key); + key.data = &idx; + key.size = sizeof(idx); + + svn_fs_base__clear_dbt(&value); + value.size = (u_int32_t) strlen(uuid); + value.data = apr_pstrmemdup(pool, uuid, value.size + 1); + + svn_fs_base__trail_debug(trail, "uuids", "put"); + return BDB_WRAP(fs, N_("set repository uuid"), + uuids->put(uuids, trail->db_txn, &key, &value, 0)); +} diff --git a/subversion/libsvn_fs_base/bdb/uuids-table.h b/subversion/libsvn_fs_base/bdb/uuids-table.h new file mode 100644 index 0000000..f6d38df --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/uuids-table.h @@ -0,0 +1,69 @@ +/* uuids-table.h : internal interface to `uuids' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_UUIDS_TABLE_H +#define SVN_LIBSVN_FS_UUIDS_TABLE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_io.h" +#include "svn_fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Open a `uuids' table in @a env. + * + * Open a `uuids' table in @a env. If @a create is non-zero, create + * one if it doesn't exist. Set @a *uuids_p to the new table. + * Return a Berkeley DB error code. + */ +int svn_fs_bdb__open_uuids_table(DB **uuids_p, + DB_ENV *env, + svn_boolean_t create); + +/* Get the UUID at index @a idx in the uuids table within @a fs, + * storing the result in @a *uuid. + */ +svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs, + int idx, + const char **uuid, + trail_t *trail, + apr_pool_t *pool); + +/* Set the UUID at index @a idx in the uuids table within @a fs + * to @a uuid. + */ +svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs, + int idx, + const char *uuid, + trail_t *trail, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_UUIDS_TABLE_H */ diff --git a/subversion/libsvn_fs_base/dag.c b/subversion/libsvn_fs_base/dag.c new file mode 100644 index 0000000..510ccbb --- /dev/null +++ b/subversion/libsvn_fs_base/dag.c @@ -0,0 +1,1758 @@ +/* dag.c : DAG-like interface filesystem, private to libsvn_fs + * + * ==================================================================== + * 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 "svn_path.h" +#include "svn_time.h" +#include "svn_error.h" +#include "svn_fs.h" +#include "svn_hash.h" +#include "svn_props.h" +#include "svn_pools.h" + +#include "dag.h" +#include "err.h" +#include "fs.h" +#include "key-gen.h" +#include "node-rev.h" +#include "trail.h" +#include "reps-strings.h" +#include "revs-txns.h" +#include "id.h" + +#include "util/fs_skels.h" + +#include "bdb/txn-table.h" +#include "bdb/rev-table.h" +#include "bdb/nodes-table.h" +#include "bdb/copies-table.h" +#include "bdb/reps-table.h" +#include "bdb/strings-table.h" +#include "bdb/checksum-reps-table.h" +#include "bdb/changes-table.h" +#include "bdb/node-origins-table.h" + +#include "private/svn_skel.h" +#include "private/svn_fs_util.h" +#include "private/svn_fspath.h" +#include "../libsvn_fs/fs-loader.h" + +#include "svn_private_config.h" + + +/* Initializing a filesystem. */ + +struct dag_node_t +{ + /*** NOTE: Keeping in-memory representations of disk data that can + be changed by other accessors is a nasty business. Such + representations are basically a cache with some pretty complex + invalidation rules. For example, the "node revision" + associated with a DAG node ID can look completely different to + a process that has modified that information as part of a + Berkeley DB transaction than it does to some other process. + That said, there are some aspects of a "node revision" which + never change, like its 'id' or 'kind'. Our best bet is to + limit ourselves to exposing outside of this interface only + those immutable aspects of a DAG node representation. ***/ + + /* The filesystem this dag node came from. */ + svn_fs_t *fs; + + /* The node revision ID for this dag node. */ + svn_fs_id_t *id; + + /* The node's type (file, dir, etc.) */ + svn_node_kind_t kind; + + /* the path at which this node was created. */ + const char *created_path; +}; + + + +/* Trivial helper/accessor functions. */ +svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node) +{ + return node->kind; +} + + +const svn_fs_id_t * +svn_fs_base__dag_get_id(dag_node_t *node) +{ + return node->id; +} + + +const char * +svn_fs_base__dag_get_created_path(dag_node_t *node) +{ + return node->created_path; +} + + +svn_fs_t * +svn_fs_base__dag_get_fs(dag_node_t *node) +{ + return node->fs; +} + + +svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node, + const char *txn_id) +{ + return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), + txn_id) == 0); +} + + +svn_error_t * +svn_fs_base__dag_get_node(dag_node_t **node, + svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool) +{ + dag_node_t *new_node; + node_revision_t *noderev; + + /* Construct the node. */ + new_node = apr_pcalloc(pool, sizeof(*new_node)); + new_node->fs = fs; + new_node->id = svn_fs_base__id_copy(id, pool); + + /* Grab the contents so we can cache some of the immutable parts of it. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool)); + + /* Initialize the KIND and CREATED_PATH attributes */ + new_node->kind = noderev->kind; + new_node->created_path = noderev->created_path; + + /* Return a fresh new node */ + *node = new_node; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_get_revision(svn_revnum_t *rev, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + /* Use the txn ID from the NODE's id to look up the transaction and + get its revision number. */ + return svn_fs_base__txn_get_revision + (rev, svn_fs_base__dag_get_fs(node), + svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, + trail, pool)); + *id_p = noderev->predecessor_id; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_get_predecessor_count(int *count, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, + trail, pool)); + *count = noderev->predecessor_count; + return SVN_NO_ERROR; +} + + +/* Trail body for svn_fs_base__dag_init_fs. */ +static svn_error_t * +txn_body_dag_init_fs(void *baton, + trail_t *trail) +{ + node_revision_t noderev; + revision_t revision; + svn_revnum_t rev = SVN_INVALID_REVNUM; + svn_fs_t *fs = trail->fs; + svn_string_t date; + const char *txn_id; + const char *copy_id; + svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool); + + /* Create empty root directory with node revision 0.0.0. */ + memset(&noderev, 0, sizeof(noderev)); + noderev.kind = svn_node_dir; + noderev.created_path = "/"; + SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev, + trail, trail->pool)); + + /* Create a new transaction (better have an id of "0") */ + SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool)); + if (strcmp(txn_id, "0")) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"), + fs->path); + + /* Create a default copy (better have an id of "0") */ + SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, trail->pool)); + if (strcmp(copy_id, "0")) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path); + SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id, + copy_kind_real, trail, trail->pool)); + + /* Link it into filesystem revision 0. */ + revision.txn_id = txn_id; + SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool)); + if (rev != 0) + return svn_error_createf(SVN_ERR_FS_CORRUPT, 0, + _("Corrupt DB: initial revision number " + "is not '0' in filesystem '%s'"), fs->path); + + /* Promote our transaction to a "committed" transaction. */ + SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev, + trail, trail->pool)); + + /* Set a date on revision 0. */ + date.data = svn_time_to_cstring(apr_time_now(), trail->pool); + date.len = strlen(date.data); + return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__dag_init_fs(svn_fs_t *fs) +{ + return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL, + TRUE, fs->pool); +} + + + +/*** Directory node functions ***/ + +/* Some of these are helpers for functions outside this section. */ + +/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list + hash, as part of TRAIL, or to NULL if NODEREV has no entries. The + entries list will be allocated in POOL, and the entries in that + list will not have interesting value in their 'kind' fields. If + NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */ +static svn_error_t * +get_dir_entries(apr_hash_t **entries_p, + svn_fs_t *fs, + node_revision_t *noderev, + trail_t *trail, + apr_pool_t *pool) +{ + apr_hash_t *entries = NULL; + apr_hash_index_t *hi; + svn_string_t entries_raw; + svn_skel_t *entries_skel; + + /* Error if this is not a directory. */ + if (noderev->kind != svn_node_dir) + return svn_error_create + (SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Attempted to get entries of a non-directory node")); + + /* If there's a DATA-KEY, there might be entries to fetch. */ + if (noderev->data_key) + { + /* Now we have a rep, follow through to get the entries. */ + SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key, + trail, pool)); + entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool); + + /* Were there entries? Make a hash from them. */ + if (entries_skel) + SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, + pool)); + } + + /* No hash? No problem. */ + *entries_p = NULL; + if (! entries) + return SVN_NO_ERROR; + + /* Else, convert the hash from a name->id mapping to a name->dirent one. */ + *entries_p = apr_hash_make(pool); + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + void *val; + svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent)); + + /* KEY will be the entry name in ancestor, VAL the id. */ + apr_hash_this(hi, &key, &klen, &val); + dirent->name = key; + dirent->id = val; + dirent->kind = svn_node_unknown; + apr_hash_set(*entries_p, key, klen, dirent); + } + + /* Return our findings. */ + return SVN_NO_ERROR; +} + + +/* Set *ID_P to the node-id for entry NAME in PARENT, as part of + TRAIL. If no such entry, set *ID_P to NULL but do not error. The + entry is allocated in POOL or in the same pool as PARENT; + the caller should copy if it cares. */ +static svn_error_t * +dir_entry_id_from_node(const svn_fs_id_t **id_p, + dag_node_t *parent, + const char *name, + trail_t *trail, + apr_pool_t *pool) +{ + apr_hash_t *entries; + svn_fs_dirent_t *dirent; + + SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool)); + if (entries) + dirent = svn_hash_gets(entries, name); + else + dirent = NULL; + + *id_p = dirent ? dirent->id : NULL; + return SVN_NO_ERROR; +} + + +/* Add or set in PARENT a directory entry NAME pointing to ID. + Allocations are done in TRAIL. + + Assumptions: + - PARENT is a mutable directory. + - ID does not refer to an ancestor of parent + - NAME is a single path component +*/ +static svn_error_t * +set_entry(dag_node_t *parent, + const char *name, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *parent_noderev; + const char *rep_key, *mutable_rep_key; + apr_hash_t *entries = NULL; + svn_stream_t *wstream; + apr_size_t len; + svn_string_t raw_entries; + svn_stringbuf_t *raw_entries_buf; + svn_skel_t *entries_skel; + svn_fs_t *fs = svn_fs_base__dag_get_fs(parent); + + /* Get the parent's node-revision. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id, + trail, pool)); + rep_key = parent_noderev->data_key; + SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, + fs, txn_id, trail, pool)); + + /* If the parent node already pointed at a mutable representation, + we don't need to do anything. But if it didn't, either because + the parent didn't refer to any rep yet or because it referred to + an immutable one, we must make the parent refer to the mutable + rep we just created. */ + if (! svn_fs_base__same_keys(rep_key, mutable_rep_key)) + { + parent_noderev->data_key = mutable_rep_key; + SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev, + trail, pool)); + } + + /* If the new representation inherited nothing, start a new entries + list for it. Else, go read its existing entries list. */ + if (rep_key) + { + SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key, + trail, pool)); + entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool); + if (entries_skel) + SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, + pool)); + } + + /* If we still have no ENTRIES hash, make one here. */ + if (! entries) + entries = apr_hash_make(pool); + + /* Now, add our new entry to the entries list. */ + svn_hash_sets(entries, name, id); + + /* Finally, replace the old entries list with the new one. */ + SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, + pool)); + raw_entries_buf = svn_skel__unparse(entries_skel, pool); + SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs, + mutable_rep_key, txn_id, + TRUE, trail, pool)); + len = raw_entries_buf->len; + SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len)); + return svn_stream_close(wstream); +} + + +/* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR + is true, then the node revision the new entry points to will be a + directory, else it will be a file. The new node will be allocated + in POOL. PARENT must be mutable, and must not have an entry + named NAME. */ +static svn_error_t * +make_entry(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + svn_boolean_t is_dir, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *new_node_id; + node_revision_t new_noderev; + + /* Make sure that NAME is a single path component. */ + if (! svn_path_is_single_path_component(name)) + return svn_error_createf + (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, + _("Attempted to create a node with an illegal name '%s'"), name); + + /* Make sure that parent is a directory */ + if (parent->kind != svn_node_dir) + return svn_error_create + (SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Attempted to create entry in non-directory parent")); + + /* Check that the parent is mutable. */ + if (! svn_fs_base__dag_check_mutable(parent, txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to clone child of non-mutable node")); + + /* Check that parent does not already have an entry named NAME. */ + SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool)); + if (new_node_id) + return svn_error_createf + (SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Attempted to create entry that already exists")); + + /* Create the new node's NODE-REVISION */ + memset(&new_noderev, 0, sizeof(new_noderev)); + new_noderev.kind = is_dir ? svn_node_dir : svn_node_file; + new_noderev.created_path = svn_fspath__join(parent_path, name, pool); + SVN_ERR(svn_fs_base__create_node + (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev, + svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)), + txn_id, trail, pool)); + + /* Create a new dag_node_t for our new node */ + SVN_ERR(svn_fs_base__dag_get_node(child_p, + svn_fs_base__dag_get_fs(parent), + new_node_id, trail, pool)); + + /* We can safely call set_entry because we already know that + PARENT is mutable, and we just created CHILD, so we know it has + no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */ + return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p), + txn_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_dir_entries(apr_hash_t **entries, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, + trail, pool)); + return get_dir_entries(entries, node->fs, noderev, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_set_entry(dag_node_t *node, + const char *entry_name, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + /* Check it's a directory. */ + if (node->kind != svn_node_dir) + return svn_error_create + (SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Attempted to set entry in non-directory node")); + + /* Check it's mutable. */ + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + return svn_error_create + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to set entry in immutable node")); + + return set_entry(node, entry_name, id, txn_id, trail, pool); +} + + + +/*** Proplists. ***/ + +svn_error_t * +svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + apr_hash_t *proplist = NULL; + svn_string_t raw_proplist; + svn_skel_t *proplist_skel; + + /* Go get a fresh NODE-REVISION for this node. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, + trail, pool)); + + /* Get property key (returning early if there isn't one) . */ + if (! noderev->prop_key) + { + *proplist_p = NULL; + return SVN_NO_ERROR; + } + + /* Get the string associated with the property rep, parsing it as a + skel, and then attempt to parse *that* into a property hash. */ + SVN_ERR(svn_fs_base__rep_contents(&raw_proplist, + svn_fs_base__dag_get_fs(node), + noderev->prop_key, trail, pool)); + proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool); + if (proplist_skel) + SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool)); + + *proplist_p = proplist; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_set_proplist(dag_node_t *node, + const apr_hash_t *proplist, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + const char *rep_key, *mutable_rep_key; + svn_fs_t *fs = svn_fs_base__dag_get_fs(node); + svn_stream_t *wstream; + apr_size_t len; + svn_skel_t *proplist_skel; + svn_stringbuf_t *raw_proplist_buf; + base_fs_data_t *bfd = fs->fsap_data; + + /* Sanity check: this node better be mutable! */ + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + { + svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool); + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Can't set proplist on *immutable* node-revision %s"), + idstr->data); + } + + /* Go get a fresh NODE-REVISION for this node. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id, + trail, pool)); + rep_key = noderev->prop_key; + + /* Flatten the proplist into a string. */ + SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool)); + raw_proplist_buf = svn_skel__unparse(proplist_skel, pool); + + /* If this repository supports representation sharing, and the + resulting property list is exactly the same as another string in + the database, just use the previously existing string and get + outta here. */ + if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) + { + svn_error_t *err; + const char *dup_rep_key; + svn_checksum_t *checksum; + + SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data, + raw_proplist_buf->len, pool)); + + err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum, + trail, pool); + if (! err) + { + if (noderev->prop_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key, + txn_id, trail, pool)); + noderev->prop_key = dup_rep_key; + return svn_fs_bdb__put_node_revision(fs, node->id, noderev, + trail, pool); + } + else if (err) + { + if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP) + return svn_error_trace(err); + + svn_error_clear(err); + err = SVN_NO_ERROR; + } + } + + /* Get a mutable version of this rep (updating the node revision if + this isn't a NOOP) */ + SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, + fs, txn_id, trail, pool)); + if (! svn_fs_base__same_keys(mutable_rep_key, rep_key)) + { + noderev->prop_key = mutable_rep_key; + SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev, + trail, pool)); + } + + /* Replace the old property list with the new one. */ + SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs, + mutable_rep_key, txn_id, + TRUE, trail, pool)); + len = raw_proplist_buf->len; + SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len)); + SVN_ERR(svn_stream_close(wstream)); + + return SVN_NO_ERROR; +} + + + +/*** Roots. ***/ + +svn_error_t * +svn_fs_base__dag_revision_root(dag_node_t **node_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *root_id; + + SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool)); + return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_txn_root(dag_node_t **node_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *root_id, *ignored; + + SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id, + trail, pool)); + return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_txn_base_root(dag_node_t **node_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *base_root_id, *ignored; + + SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id, + trail, pool)); + return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_clone_child(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + dag_node_t *cur_entry; /* parent's current entry named NAME */ + const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */ + svn_fs_t *fs = svn_fs_base__dag_get_fs(parent); + + /* First check that the parent is mutable. */ + if (! svn_fs_base__dag_check_mutable(parent, txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to clone child of non-mutable node")); + + /* Make sure that NAME is a single path component. */ + if (! svn_path_is_single_path_component(name)) + return svn_error_createf + (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, + _("Attempted to make a child clone with an illegal name '%s'"), name); + + /* Find the node named NAME in PARENT's entries list if it exists. */ + SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool)); + + /* Check for mutability in the node we found. If it's mutable, we + don't need to clone it. */ + if (svn_fs_base__dag_check_mutable(cur_entry, txn_id)) + { + /* This has already been cloned */ + new_node_id = cur_entry->id; + } + else + { + node_revision_t *noderev; + + /* Go get a fresh NODE-REVISION for current child node. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id, + trail, pool)); + + /* Do the clone thingy here. */ + noderev->predecessor_id = cur_entry->id; + if (noderev->predecessor_count != -1) + noderev->predecessor_count++; + noderev->created_path = svn_fspath__join(parent_path, name, pool); + SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id, + noderev, copy_id, txn_id, + trail, pool)); + + /* Replace the ID in the parent's ENTRY list with the ID which + refers to the mutable clone of this child. */ + SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool)); + } + + /* Initialize the youngster. */ + return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool); +} + + + +svn_error_t * +svn_fs_base__dag_clone_root(dag_node_t **root_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *base_root_id, *root_id; + node_revision_t *noderev; + + /* Get the node ID's of the root directories of the transaction and + its base revision. */ + SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id, + trail, pool)); + + /* Oh, give me a clone... + (If they're the same, we haven't cloned the transaction's root + directory yet.) */ + if (svn_fs_base__id_eq(root_id, base_root_id)) + { + const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id); + + /* Of my own flesh and bone... + (Get the NODE-REVISION for the base node, and then write + it back out as the clone.) */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id, + trail, pool)); + + /* With its Y-chromosome changed to X... + (Store it with an updated predecessor count.) */ + /* ### TODO: Does it even makes sense to have a different copy id for + the root node? That is, does this function need a copy_id + passed in? */ + noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool); + if (noderev->predecessor_count != -1) + noderev->predecessor_count++; + SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id, + noderev, base_copy_id, + txn_id, trail, pool)); + + /* ... And when it is grown + * Then my own little clone + * Will be of the opposite sex! + */ + SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool)); + } + + /* + * (Sung to the tune of "Home, Home on the Range", with thanks to + * Randall Garrett and Isaac Asimov.) + */ + + /* One way or another, root_id now identifies a cloned root node. */ + return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_delete(dag_node_t *parent, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *parent_noderev; + const char *rep_key, *mutable_rep_key; + apr_hash_t *entries = NULL; + svn_skel_t *entries_skel; + svn_fs_t *fs = parent->fs; + svn_string_t str; + svn_fs_id_t *id = NULL; + dag_node_t *node; + + /* Make sure parent is a directory. */ + if (parent->kind != svn_node_dir) + return svn_error_createf + (SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Attempted to delete entry '%s' from *non*-directory node"), name); + + /* Make sure parent is mutable. */ + if (! svn_fs_base__dag_check_mutable(parent, txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to delete entry '%s' from immutable directory node"), + name); + + /* Make sure that NAME is a single path component. */ + if (! svn_path_is_single_path_component(name)) + return svn_error_createf + (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, + _("Attempted to delete a node with an illegal name '%s'"), name); + + /* Get a fresh NODE-REVISION for the parent node. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id, + trail, pool)); + + /* Get the key for the parent's entries list (data) representation. */ + rep_key = parent_noderev->data_key; + + /* No REP_KEY means no representation, and no representation means + no data, and no data means no entries...there's nothing here to + delete! */ + if (! rep_key) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_ENTRY, NULL, + _("Delete failed: directory has no entry '%s'"), name); + + /* Ensure we have a key to a mutable representation of the entries + list. We'll have to update the NODE-REVISION if it points to an + immutable version. */ + SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, + fs, txn_id, trail, pool)); + if (! svn_fs_base__same_keys(mutable_rep_key, rep_key)) + { + parent_noderev->data_key = mutable_rep_key; + SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev, + trail, pool)); + } + + /* Read the representation, then use it to get the string that holds + the entries list. Parse that list into a skel, and parse *that* + into a hash. */ + + SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool)); + entries_skel = svn_skel__parse(str.data, str.len, pool); + if (entries_skel) + SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool)); + + /* Find NAME in the ENTRIES skel. */ + if (entries) + id = svn_hash_gets(entries, name); + + /* If we never found ID in ENTRIES (perhaps because there are no + ENTRIES, perhaps because ID just isn't in the existing ENTRIES + ... it doesn't matter), return an error. */ + if (! id) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_ENTRY, NULL, + _("Delete failed: directory has no entry '%s'"), name); + + /* Use the ID of this ENTRY to get the entry's node. */ + SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent), + id, trail, pool)); + + /* If mutable, remove it and any mutable children from db. */ + SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id, + trail, pool)); + + /* Remove this entry from its parent's entries list. */ + svn_hash_sets(entries, name, NULL); + + /* Replace the old entries list with the new one. */ + { + svn_stream_t *ws; + svn_stringbuf_t *unparsed_entries; + apr_size_t len; + + SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool)); + unparsed_entries = svn_skel__unparse(entries_skel, pool); + SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key, + txn_id, TRUE, trail, + pool)); + len = unparsed_entries->len; + SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len)); + SVN_ERR(svn_stream_close(ws)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_remove_node(svn_fs_t *fs, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + dag_node_t *node; + node_revision_t *noderev; + + /* Fetch the node. */ + SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool)); + + /* If immutable, do nothing and return immediately. */ + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted removal of immutable node")); + + /* Get a fresh node-revision. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool)); + + /* Delete any mutable property representation. */ + if (noderev->prop_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key, + txn_id, trail, pool)); + + /* Delete any mutable data representation. */ + if (noderev->data_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key, + txn_id, trail, pool)); + + /* Delete any mutable edit representation (files only). */ + if (noderev->edit_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key, + txn_id, trail, pool)); + + /* Delete the node revision itself. */ + return svn_fs_base__delete_node_revision(fs, id, + noderev->predecessor_id == NULL, + trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + dag_node_t *node; + + /* Get the node. */ + SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool)); + + /* If immutable, do nothing and return immediately. */ + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + return SVN_NO_ERROR; + + /* Else it's mutable. Recurse on directories... */ + if (node->kind == svn_node_dir) + { + apr_hash_t *entries; + apr_hash_index_t *hi; + + /* Loop over hash entries */ + SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool)); + if (entries) + { + apr_pool_t *subpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, entries); + hi; + hi = apr_hash_next(hi)) + { + void *val; + svn_fs_dirent_t *dirent; + + apr_hash_this(hi, NULL, NULL, &val); + dirent = val; + SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id, + txn_id, trail, + subpool)); + } + } + } + + /* ... then delete the node itself, any mutable representations and + strings it points to, and possibly its node-origins record. */ + return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_make_file(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + /* Call our little helper function */ + return make_entry(child_p, parent, parent_path, name, FALSE, + txn_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_make_dir(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + /* Call our little helper function */ + return make_entry(child_p, parent, parent_path, name, TRUE, + txn_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_get_contents(svn_stream_t **contents, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + /* Make sure our node is a file. */ + if (file->kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, + _("Attempted to get textual contents of a *non*-file node")); + + /* Go get a fresh node-revision for FILE. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, + trail, pool)); + + /* Our job is to _return_ a stream on the file's contents, so the + stream has to be trail-independent. Here, we pass NULL to tell + the stream that we're not providing it a trail that lives across + reads. This means the stream will do each read in a one-off, + temporary trail. */ + return svn_fs_base__rep_contents_read_stream(contents, file->fs, + noderev->data_key, + FALSE, trail, pool); + + /* Note that we're not registering any `close' func, because there's + nothing to cleanup outside of our trail. When the trail is + freed, the stream/baton will be too. */ +} + + +svn_error_t * +svn_fs_base__dag_file_length(svn_filesize_t *length, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + /* Make sure our node is a file. */ + if (file->kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, + _("Attempted to get length of a *non*-file node")); + + /* Go get a fresh node-revision for FILE, and . */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, + trail, pool)); + if (noderev->data_key) + SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs, + noderev->data_key, trail, pool)); + else + *length = 0; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_file_checksum(svn_checksum_t **checksum, + svn_checksum_kind_t checksum_kind, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + if (file->kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, + _("Attempted to get checksum of a *non*-file node")); + + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, + trail, pool)); + if (! noderev->data_key) + { + *checksum = NULL; + return SVN_NO_ERROR; + } + + if (checksum_kind == svn_checksum_md5) + return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs, + noderev->data_key, + trail, pool); + else if (checksum_kind == svn_checksum_sha1) + return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs, + noderev->data_key, + trail, pool); + else + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); +} + + +svn_error_t * +svn_fs_base__dag_get_edit_stream(svn_stream_t **contents, + dag_node_t *file, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_t *fs = file->fs; /* just for nicer indentation */ + node_revision_t *noderev; + const char *mutable_rep_key; + svn_stream_t *ws; + + /* Make sure our node is a file. */ + if (file->kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, + _("Attempted to set textual contents of a *non*-file node")); + + /* Make sure our node is mutable. */ + if (! svn_fs_base__dag_check_mutable(file, txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to set textual contents of an immutable node")); + + /* Get the node revision. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, + trail, pool)); + + /* If this node already has an EDIT-DATA-KEY, destroy the data + associated with that key. */ + if (noderev->edit_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key, + txn_id, trail, pool)); + + /* Now, let's ensure that we have a new EDIT-DATA-KEY available for + use. */ + SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs, + txn_id, trail, pool)); + + /* We made a new rep, so update the node revision. */ + noderev->edit_key = mutable_rep_key; + SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, + trail, pool)); + + /* Return a writable stream with which to set new contents. */ + SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key, + txn_id, FALSE, trail, + pool)); + *contents = ws; + + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_fs_base__dag_finalize_edits(dag_node_t *file, + const svn_checksum_t *checksum, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_t *fs = file->fs; /* just for nicer indentation */ + node_revision_t *noderev; + const char *old_data_key, *new_data_key, *useless_data_key = NULL; + const char *data_key_uniquifier = NULL; + svn_checksum_t *md5_checksum, *sha1_checksum; + base_fs_data_t *bfd = fs->fsap_data; + + /* Make sure our node is a file. */ + if (file->kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, + _("Attempted to set textual contents of a *non*-file node")); + + /* Make sure our node is mutable. */ + if (! svn_fs_base__dag_check_mutable(file, txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted to set textual contents of an immutable node")); + + /* Get the node revision. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, + trail, pool)); + + /* If this node has no EDIT-DATA-KEY, this is a no-op. */ + if (! noderev->edit_key) + return SVN_NO_ERROR; + + /* Get our representation's checksums. */ + SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum, + fs, noderev->edit_key, + trail, pool)); + + /* If our caller provided a checksum of the right kind to compare, do so. */ + if (checksum) + { + svn_checksum_t *test_checksum; + + if (checksum->kind == svn_checksum_md5) + test_checksum = md5_checksum; + else if (checksum->kind == svn_checksum_sha1) + test_checksum = sha1_checksum; + else + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); + + if (! svn_checksum_match(checksum, test_checksum)) + return svn_checksum_mismatch_err(checksum, test_checksum, pool, + _("Checksum mismatch on representation '%s'"), + noderev->edit_key); + } + + /* Now, we want to delete the old representation and replace it with + the new. Of course, we don't actually delete anything until + everything is being properly referred to by the node-revision + skel. + + Now, if the result of all this editing is that we've created a + representation that describes content already represented + immutably in our database, we don't even need to keep these edits. + We can simply point our data_key at that pre-existing + representation and throw away our work! In this situation, + though, we'll need a unique ID to help other code distinguish + between "the contents weren't touched" and "the contents were + touched but still look the same" (to state it oversimply). */ + old_data_key = noderev->data_key; + if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) + { + svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs, + sha1_checksum, + trail, pool); + if (! err) + { + useless_data_key = noderev->edit_key; + err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier, + trail->fs, trail, pool); + } + else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + new_data_key = noderev->edit_key; + } + SVN_ERR(err); + } + else + { + new_data_key = noderev->edit_key; + } + + noderev->data_key = new_data_key; + noderev->data_key_uniquifier = data_key_uniquifier; + noderev->edit_key = NULL; + + SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool)); + + /* Only *now* can we safely destroy the old representation (if it + even existed in the first place). */ + if (old_data_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id, + trail, pool)); + + /* If we've got a discardable rep (probably because we ended up + re-using a preexisting one), throw out the discardable rep. */ + if (useless_data_key) + SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key, + txn_id, trail, pool)); + + return SVN_NO_ERROR; +} + + + +dag_node_t * +svn_fs_base__dag_dup(dag_node_t *node, + apr_pool_t *pool) +{ + /* Allocate our new node. */ + dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node)); + + new_node->fs = node->fs; + new_node->id = svn_fs_base__id_copy(node->id, pool); + new_node->kind = node->kind; + new_node->created_path = apr_pstrdup(pool, node->created_path); + return new_node; +} + + +svn_error_t * +svn_fs_base__dag_open(dag_node_t **child_p, + dag_node_t *parent, + const char *name, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *node_id; + + /* Ensure that NAME exists in PARENT's entry list. */ + SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool)); + if (! node_id) + return svn_error_createf + (SVN_ERR_FS_NOT_FOUND, NULL, + _("Attempted to open non-existent child node '%s'"), name); + + /* Make sure that NAME is a single path component. */ + if (! svn_path_is_single_path_component(name)) + return svn_error_createf + (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, + _("Attempted to open node with an illegal name '%s'"), name); + + /* Now get the node that was requested. */ + return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent), + node_id, trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_copy(dag_node_t *to_node, + const char *entry, + dag_node_t *from_node, + svn_boolean_t preserve_history, + svn_revnum_t from_rev, + const char *from_path, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *id; + + if (preserve_history) + { + node_revision_t *noderev; + const char *copy_id; + svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node); + const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node); + const char *from_txn_id = NULL; + + /* Make a copy of the original node revision. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id, + trail, pool)); + + /* Reserve a copy ID for this new copy. */ + SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool)); + + /* Create a successor with its predecessor pointing at the copy + source. */ + noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool); + if (noderev->predecessor_count != -1) + noderev->predecessor_count++; + noderev->created_path = svn_fspath__join + (svn_fs_base__dag_get_created_path(to_node), entry, pool); + SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev, + copy_id, txn_id, trail, pool)); + + /* Translate FROM_REV into a transaction ID. */ + SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev, + trail, pool)); + + /* Now that we've done the copy, we need to add the information + about the copy to the `copies' table, using the COPY_ID we + reserved above. */ + SVN_ERR(svn_fs_bdb__create_copy + (fs, copy_id, + svn_fs__canonicalize_abspath(from_path, pool), + from_txn_id, id, copy_kind_real, trail, pool)); + + /* Finally, add the COPY_ID to the transaction's list of copies + so that, if this transaction is aborted, the `copies' table + entry we added above will be cleaned up. */ + SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool)); + } + else /* don't preserve history */ + { + id = svn_fs_base__dag_get_id(from_node); + } + + /* Set the entry in to_node to the new id. */ + return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id, + trail, pool); +} + + + +/*** Deltification ***/ + +/* Maybe change the representation identified by TARGET_REP_KEY to be + a delta against the representation identified by SOURCE_REP_KEY. + Some reasons why we wouldn't include: + + - TARGET_REP_KEY and SOURCE_REP_KEY are the same key. + + - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if + TXN_ID is non-NULL). + + - The delta provides less space savings that a fulltext (this is + a detail handled by lower logic layers, not this function). + + Do this work in TRAIL, using POOL for necessary allocations. +*/ +static svn_error_t * +maybe_deltify_mutable_rep(const char *target_rep_key, + const char *source_rep_key, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + if (! (target_rep_key && source_rep_key + && (strcmp(target_rep_key, source_rep_key) != 0))) + return SVN_NO_ERROR; + + if (txn_id) + { + representation_t *target_rep; + SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key, + trail, pool)); + if (strcmp(target_rep->txn_id, txn_id) != 0) + return SVN_NO_ERROR; + } + + return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key, + trail, pool); +} + + +svn_error_t * +svn_fs_base__dag_deltify(dag_node_t *target, + dag_node_t *source, + svn_boolean_t props_only, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *source_nr, *target_nr; + svn_fs_t *fs = svn_fs_base__dag_get_fs(target); + + /* Get node revisions for the two nodes. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id, + trail, pool)); + SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id, + trail, pool)); + + /* If TARGET and SOURCE both have properties, and are not sharing a + property key, deltify TARGET's properties. */ + SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key, + txn_id, trail, pool)); + + /* If we are not only attending to properties, and if TARGET and + SOURCE both have data, and are not sharing a data key, deltify + TARGET's data. */ + if (! props_only) + SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key, + txn_id, trail, pool)); + + return SVN_NO_ERROR; +} + +/* Maybe store a `checksum-reps' index record for the representation whose + key is REP. (If there's already a rep for this checksum, we don't + bother overwriting it.) */ +static svn_error_t * +maybe_store_checksum_rep(const char *rep, + trail_t *trail, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + svn_fs_t *fs = trail->fs; + svn_checksum_t *sha1_checksum; + + /* We want the SHA1 checksum, if any. */ + SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum, + fs, rep, trail, pool)); + if (sha1_checksum) + { + err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool); + if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + } + return svn_error_trace(err); +} + +svn_error_t * +svn_fs_base__dag_index_checksums(dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *node_rev; + + SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id, + trail, pool)); + if ((node_rev->kind == svn_node_file) && node_rev->data_key) + SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool)); + if (node_rev->prop_key) + SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool)); + + return SVN_NO_ERROR; +} + + + +/*** Committing ***/ + +svn_error_t * +svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev, + svn_fs_txn_t *txn, + trail_t *trail, + apr_pool_t *pool) +{ + revision_t revision; + svn_string_t date; + apr_hash_t *txnprops; + svn_fs_t *fs = txn->fs; + const char *txn_id = txn->id; + + /* Remove any temporary transaction properties initially created by + begin_txn(). */ + SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail)); + + /* Add new revision entry to `revisions' table. */ + revision.txn_id = txn_id; + *new_rev = SVN_INVALID_REVNUM; + SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool)); + + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) + SVN_ERR(svn_fs_base__set_txn_prop + (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool)); + + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) + SVN_ERR(svn_fs_base__set_txn_prop + (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool)); + + /* Promote the unfinished transaction to a committed one. */ + SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev, + trail, pool)); + + /* Set a date on the commit. We wait until now to fetch the date, + so it's definitely newer than any previous revision's date. */ + date.data = svn_time_to_cstring(apr_time_now(), pool); + date.len = strlen(date.data); + return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE, + NULL, &date, trail, pool); +} + + +/*** Comparison. ***/ + +svn_error_t * +svn_fs_base__things_different(svn_boolean_t *props_changed, + svn_boolean_t *contents_changed, + dag_node_t *node1, + dag_node_t *node2, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev1, *noderev2; + + /* If we have no place to store our results, don't bother doing + anything. */ + if (! props_changed && ! contents_changed) + return SVN_NO_ERROR; + + /* The node revision skels for these two nodes. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id, + trail, pool)); + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id, + trail, pool)); + + /* Compare property keys. */ + if (props_changed != NULL) + *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key, + noderev2->prop_key)); + + /* Compare contents keys and their (optional) uniquifiers. */ + if (contents_changed != NULL) + *contents_changed = + (! (svn_fs_base__same_keys(noderev1->data_key, + noderev2->data_key) + /* Technically, these uniquifiers aren't used and "keys", + but keys are base-36 stringified numbers, so we'll take + this liberty. */ + && (svn_fs_base__same_keys(noderev1->data_key_uniquifier, + noderev2->data_key_uniquifier)))); + + return SVN_NO_ERROR; +} + + + +/*** Mergeinfo tracking stuff ***/ + +svn_error_t * +svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo, + apr_int64_t *count, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *node_rev; + svn_fs_t *fs = svn_fs_base__dag_get_fs(node); + const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); + + SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); + if (has_mergeinfo) + *has_mergeinfo = node_rev->has_mergeinfo; + if (count) + *count = node_rev->mergeinfo_count; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node, + svn_boolean_t has_mergeinfo, + svn_boolean_t *had_mergeinfo, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *node_rev; + svn_fs_t *fs = svn_fs_base__dag_get_fs(node); + const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); + + SVN_ERR(svn_fs_base__test_required_feature_format + (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); + + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted merge tracking info change on " + "immutable node")); + + SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); + *had_mergeinfo = node_rev->has_mergeinfo; + + /* Are we changing the node? */ + if ((! has_mergeinfo) != (! *had_mergeinfo)) + { + /* Note the new has-mergeinfo state. */ + node_rev->has_mergeinfo = has_mergeinfo; + + /* Increment or decrement the mergeinfo count as necessary. */ + if (has_mergeinfo) + node_rev->mergeinfo_count++; + else + node_rev->mergeinfo_count--; + + SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node, + apr_int64_t count_delta, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *node_rev; + svn_fs_t *fs = svn_fs_base__dag_get_fs(node); + const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); + + SVN_ERR(svn_fs_base__test_required_feature_format + (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); + + if (! svn_fs_base__dag_check_mutable(node, txn_id)) + return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Attempted mergeinfo count change on " + "immutable node")); + + if (count_delta == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); + node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta; + if ((node_rev->mergeinfo_count < 0) + || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1))) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + apr_psprintf(pool, + _("Invalid value (%%%s) for node " + "revision mergeinfo count"), + APR_INT64_T_FMT), + node_rev->mergeinfo_count); + + return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool); +} diff --git a/subversion/libsvn_fs_base/dag.h b/subversion/libsvn_fs_base/dag.h new file mode 100644 index 0000000..4c50c84 --- /dev/null +++ b/subversion/libsvn_fs_base/dag.h @@ -0,0 +1,587 @@ +/* dag.h : DAG-like interface filesystem, private to libsvn_fs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_DAG_H +#define SVN_LIBSVN_FS_DAG_H + +#include "svn_fs.h" + +#include "trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* The interface in this file provides all the essential filesystem + operations, but exposes the filesystem's DAG structure. This makes + it simpler to implement than the public interface, since a client + of this interface has to understand and cope with shared structure + directly as it appears in the database. However, it's still a + self-consistent set of invariants to maintain, making it + (hopefully) a useful interface boundary. + + In other words: + + - The dag_node_t interface exposes the internal DAG structure of + the filesystem, while the svn_fs.h interface does any cloning + necessary to make the filesystem look like a tree. + + - The dag_node_t interface exposes the existence of copy nodes, + whereas the svn_fs.h handles them transparently. + + - dag_node_t's must be explicitly cloned, whereas the svn_fs.h + operations make clones implicitly. + + - Callers of the dag_node_t interface use Berkeley DB transactions + to ensure consistency between operations, while callers of the + svn_fs.h interface use Subversion transactions. */ + + +/* Initializing a filesystem. */ + + +/* Given a filesystem FS, which contains all the necessary tables, + create the initial revision 0, and the initial root directory. */ +svn_error_t *svn_fs_base__dag_init_fs(svn_fs_t *fs); + + + +/* Generic DAG node stuff. */ + +typedef struct dag_node_t dag_node_t; + + +/* Fill *NODE with a dag_node_t representing node revision ID in FS, + allocating in POOL. */ +svn_error_t *svn_fs_base__dag_get_node(dag_node_t **node, + svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail, + apr_pool_t *pool); + + +/* Return a new dag_node_t object referring to the same node as NODE, + allocated in POOL. */ +dag_node_t *svn_fs_base__dag_dup(dag_node_t *node, + apr_pool_t *pool); + + +/* Return the filesystem containing NODE. */ +svn_fs_t *svn_fs_base__dag_get_fs(dag_node_t *node); + + +/* Set *REV to NODE's revision number, as part of TRAIL. If NODE has + never been committed as part of a revision, set *REV to + SVN_INVALID_REVNUM. */ +svn_error_t *svn_fs_base__dag_get_revision(svn_revnum_t *rev, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + + +/* Return the node revision ID of NODE. The value returned is shared + with NODE, and will be deallocated when NODE is. */ +const svn_fs_id_t *svn_fs_base__dag_get_id(dag_node_t *node); + + +/* Return the created path of NODE. The value returned is shared + with NODE, and will be deallocated when NODE is. */ +const char *svn_fs_base__dag_get_created_path(dag_node_t *node); + + +/* Set *ID_P to the node revision ID of NODE's immediate predecessor, + or NULL if NODE has no predecessor, as part of TRAIL. The returned + ID will be allocated in POOL. */ +svn_error_t *svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *COUNT to the number of predecessors NODE has (recursively), or + -1 if not known, as part of TRAIL. */ +svn_error_t *svn_fs_base__dag_get_predecessor_count(int *count, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + + +/* Return non-zero IFF NODE is currently mutable under Subversion + transaction TXN_ID. */ +svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node, + const char *txn_id); + +/* Return the node kind of NODE. */ +svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node); + +/* Set *PROPLIST_P to a PROPLIST hash representing the entire property + list of NODE, as part of TRAIL. The hash has const char * names + (the property names) and svn_string_t * values (the property values). + + If properties do not exist on NODE, *PROPLIST_P will be set to NULL. + + The returned property list is allocated in POOL. */ +svn_error_t *svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + +/* Set the property list of NODE to PROPLIST, as part of TRAIL. The + node being changed must be mutable. TXN_ID is the Subversion + transaction under which this occurs. */ +svn_error_t *svn_fs_base__dag_set_proplist(dag_node_t *node, + const apr_hash_t *proplist, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + + +/* Mergeinfo tracking stuff. */ + +/* If HAS_MERGEINFO is not null, set *HAS_MERGEINFO to TRUE iff NODE + records that its property list contains merge tracking information. + + If COUNT is not null, set *COUNT to the number of nodes -- + including NODE itself -- in the subtree rooted at NODE which claim + to carry merge tracking information. + + Do this as part of TRAIL, and use POOL for necessary allocations. + + NOTE: No validation against NODE's actual property list is + performed. */ +svn_error_t *svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo, + apr_int64_t *count, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + +/* If HAS_MERGEINFO is set, record on NODE that its property list + carries merge tracking information. Otherwise, record on NODE its + property list does *not* carry merge tracking information. NODE + must be mutable under TXN_ID (the Subversion transaction under + which this operation occurs). Set *HAD_MERGEINFO to the previous + state of this record. + + Update the mergeinfo count on NODE as necessary. + + Do all of this as part of TRAIL, and use POOL for necessary + allocations. + + NOTE: No validation against NODE's actual property list is + performed. */ +svn_error_t *svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node, + svn_boolean_t has_mergeinfo, + svn_boolean_t *had_mergeinfo, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + +/* Record on NODE a change of COUNT_DELTA nodes -- including NODE + itself -- in the subtree rooted at NODE claim to carry merge + tracking information. That is, add COUNT_DELTA to NODE's current + mergeinfo count (regardless of whether COUNT_DELTA is a positive or + negative integer). + + NODE must be mutable under TXN_ID (the Subversion transaction under + which this operation occurs). Do this as part of TRAIL, and use + POOL for necessary allocations. + + NOTE: No validation of these claims is performed. */ +svn_error_t *svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node, + apr_int64_t count_delta, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Revision and transaction roots. */ + + +/* Open the root of revision REV of filesystem FS, as part of TRAIL. + Set *NODE_P to the new node. Allocate the node in POOL. */ +svn_error_t *svn_fs_base__dag_revision_root(dag_node_t **node_p, + svn_fs_t *fs, + svn_revnum_t rev, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *NODE_P to the root of transaction TXN_ID in FS, as part + of TRAIL. Allocate the node in POOL. + + Note that the root node of TXN_ID is not necessarily mutable. If no + changes have been made in the transaction, then it may share its + root directory with its base revision. To get a mutable root node + for a transaction, call svn_fs_base__dag_clone_root. */ +svn_error_t *svn_fs_base__dag_txn_root(dag_node_t **node_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *NODE_P to the base root of transaction TXN_ID in FS, as part + of TRAIL. Allocate the node in POOL. */ +svn_error_t *svn_fs_base__dag_txn_base_root(dag_node_t **node_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Clone the root directory of TXN_ID in FS, and update the + `transactions' table entry to point to it, unless this has been + done already. In either case, set *ROOT_P to a reference to the + root directory clone. Do all this as part of TRAIL, and allocate + *ROOT_P in POOL. */ +svn_error_t *svn_fs_base__dag_clone_root(dag_node_t **root_p, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Commit the transaction TXN->id in TXN->FS, as part of TRAIL. Store the + new revision number in *NEW_REV. This entails: + - marking the tree of mutable nodes at TXN->id's root as immutable, + and marking all their contents as stable + - creating a new revision, with TXN->id's root as its root directory + - promoting TXN->id to a "committed" transaction. + + Beware! This does not make sure that TXN->id is based on the very + latest revision in TXN->FS. If the caller doesn't take care of this, + you may lose people's work! + + Do any necessary temporary allocation in a subpool of POOL. + Consume temporary space at most proportional to the maximum depth + of SVN_TXN's tree of mutable nodes. */ +svn_error_t *svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev, + svn_fs_txn_t *txn, + trail_t *trail, + apr_pool_t *pool); + + +/* Directories. */ + + +/* Open the node named NAME in the directory PARENT, as part of TRAIL. + Set *CHILD_P to the new node, allocated in POOL. NAME must be a + single path component; it cannot be a slash-separated directory + path. */ +svn_error_t *svn_fs_base__dag_open(dag_node_t **child_p, + dag_node_t *parent, + const char *name, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *ENTRIES_P to a hash table of NODE's entries, as part of TRAIL, + or NULL if NODE has no entries. The keys of the table are entry + names, and the values are svn_fs_dirent_t's. + + The returned table is allocated in POOL. + + NOTE: the 'kind' field of the svn_fs_dirent_t's is set to + svn_node_unknown by this function -- callers that need in + interesting value in these slots should fill them in using a new + TRAIL, since the list of entries can be arbitrarily large. */ +svn_error_t *svn_fs_base__dag_dir_entries(apr_hash_t **entries_p, + dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + + +/* Set ENTRY_NAME in NODE to point to ID, as part of TRAIL. NODE must + be a mutable directory. ID can refer to a mutable or immutable + node. If ENTRY_NAME does not exist, it will be created. TXN_ID is + the Subversion transaction under which this occurs.*/ +svn_error_t *svn_fs_base__dag_set_entry(dag_node_t *node, + const char *entry_name, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Make a new mutable clone of the node named NAME in PARENT, and + adjust PARENT's directory entry to point to it, as part of TRAIL, + unless NAME in PARENT already refers to a mutable node. In either + case, set *CHILD_P to a reference to the new node, allocated in + POOL. PARENT must be mutable. NAME must be a single path + component; it cannot be a slash-separated directory path. + PARENT_PATH must be the canonicalized absolute path of the parent + directory. + + COPY_ID, if non-NULL, is a key into the `copies' table, and + indicates that this new node is being created as the result of a + copy operation, and specifically which operation that was. + + PATH is the canonicalized absolute path at which this node is being + created. + + TXN_ID is the Subversion transaction under which this occurs. */ +svn_error_t *svn_fs_base__dag_clone_child(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete the directory entry named NAME from PARENT, as part of + TRAIL. PARENT must be mutable. NAME must be a single path + component; it cannot be a slash-separated directory path. If the + entry being deleted points to a mutable node revision, also remove + that node revision and (if it is a directory) all mutable node + revisions reachable from it. Also delete the node-origins record + for each deleted node revision that had no predecessor. + + TXN_ID is the Subversion transaction under which this occurs. + + If return SVN_ERR_FS_NO_SUCH_ENTRY, then there is no entry NAME in + PARENT. */ +svn_error_t *svn_fs_base__dag_delete(dag_node_t *parent, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete the node revision assigned to node ID from FS's `nodes' + table, as part of TRAIL. Also delete any mutable representations + and strings associated with that node revision. Also delete the + node-origins record for this node revision's node id, if this node + revision had no predecessor. + + ID may refer to a file or directory, which must be mutable. TXN_ID + is the Subversion transaction under which this occurs. + + NOTE: If ID represents a directory, and that directory has mutable + children, you risk orphaning those children by leaving them + dangling, disconnected from all DAG trees. It is assumed that + callers of this interface know what in the world they are doing. */ +svn_error_t *svn_fs_base__dag_remove_node(svn_fs_t *fs, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete all mutable node revisions reachable from node ID, including + ID itself, from FS's `nodes' table, as part of TRAIL. Also delete + any mutable representations and strings associated with that node + revision. Also delete the node-origins record for each deleted + node revision that had no predecessor. + + ID may refer to a file or directory, which may be mutable or + immutable. TXN_ID is the Subversion transaction under which this + occurs. */ +svn_error_t *svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs, + const svn_fs_id_t *id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Create a new mutable directory named NAME in PARENT, as part of + TRAIL. Set *CHILD_P to a reference to the new node, allocated in + POOL. The new directory has no contents, and no properties. + PARENT must be mutable. NAME must be a single path component; it + cannot be a slash-separated directory path. PARENT_PATH must be + the canonicalized absolute path of the parent directory. PARENT + must not currently have an entry named NAME. Do any temporary + allocation in POOL. TXN_ID is the Subversion transaction + under which this occurs. */ +svn_error_t *svn_fs_base__dag_make_dir(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + + +/* Files. */ + + +/* Set *CONTENTS to a readable generic stream which yields the + contents of FILE, as part of TRAIL. Allocate the stream in POOL. + If FILE is not a file, return SVN_ERR_FS_NOT_FILE. */ +svn_error_t *svn_fs_base__dag_get_contents(svn_stream_t **contents, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool); + + +/* Return a generic writable stream in *CONTENTS with which to set the + contents of FILE as part of TRAIL. Allocate the stream in POOL. + TXN_ID is the Subversion transaction under which this occurs. Any + previous edits on the file will be deleted, and a new edit stream + will be constructed. */ +svn_error_t *svn_fs_base__dag_get_edit_stream(svn_stream_t **contents, + dag_node_t *file, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Signify the completion of edits to FILE made using the stream + returned by svn_fs_base__dag_get_edit_stream, as part of TRAIL. TXN_ID + is the Subversion transaction under which this occurs. + + If CHECKSUM is non-null, it must match the checksum for FILE's + contents (note: this is not recalculated, the recorded checksum is + used), else the error SVN_ERR_CHECKSUM_MISMATCH is returned. + + This operation is a no-op if no edits are present. */ +svn_error_t *svn_fs_base__dag_finalize_edits(dag_node_t *file, + const svn_checksum_t *checksum, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *LENGTH to the length of the contents of FILE, as part of TRAIL. */ +svn_error_t *svn_fs_base__dag_file_length(svn_filesize_t *length, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool); + +/* Put the checksum of type CHECKSUM_KIND recorded for FILE into + * CHECKSUM, as part of TRAIL. + * + * If no stored checksum of the requested kind is available, do not + * calculate the checksum, just put NULL into CHECKSUM. + */ +svn_error_t *svn_fs_base__dag_file_checksum(svn_checksum_t **checksum, + svn_checksum_kind_t checksum_kind, + dag_node_t *file, + trail_t *trail, + apr_pool_t *pool); + +/* Create a new mutable file named NAME in PARENT, as part of TRAIL. + Set *CHILD_P to a reference to the new node, allocated in + POOL. The new file's contents are the empty string, and it + has no properties. PARENT must be mutable. NAME must be a single + path component; it cannot be a slash-separated directory path. + PARENT_PATH must be the canonicalized absolute path of the parent + directory. TXN_ID is the Subversion transaction under which this + occurs. */ +svn_error_t *svn_fs_base__dag_make_file(dag_node_t **child_p, + dag_node_t *parent, + const char *parent_path, + const char *name, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + + +/* Copies */ + +/* Make ENTRY in TO_NODE be a copy of FROM_NODE, as part of TRAIL. + TO_NODE must be mutable. TXN_ID is the Subversion transaction + under which this occurs. + + If PRESERVE_HISTORY is true, the new node will record that it was + copied from FROM_PATH in FROM_REV; therefore, FROM_NODE should be + the node found at FROM_PATH in FROM_REV, although this is not + checked. + + If PRESERVE_HISTORY is false, FROM_PATH and FROM_REV are ignored. */ +svn_error_t *svn_fs_base__dag_copy(dag_node_t *to_node, + const char *entry, + dag_node_t *from_node, + svn_boolean_t preserve_history, + svn_revnum_t from_rev, + const char *from_path, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + + +/* Deltification */ + +/* Change TARGET's representation to be a delta against SOURCE, as + part of TRAIL. If TARGET or SOURCE does not exist, do nothing and + return success. If PROPS_ONLY is non-zero, only the node property + portion of TARGET will be deltified. + + If TXN_ID is non-NULL, it is the transaction ID in which TARGET's + representation(s) must have been created (otherwise deltification + is silently not attempted). + + WARNING WARNING WARNING: Do *NOT* call this with a mutable SOURCE + node. Things will go *very* sour if you deltify TARGET against a + node that might just disappear from the filesystem in the (near) + future. */ +svn_error_t *svn_fs_base__dag_deltify(dag_node_t *target, + dag_node_t *source, + svn_boolean_t props_only, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + +/* Index NODE's backing data representations by their checksum. Do + this as part of TRAIL. Use POOL for allocations. */ +svn_error_t *svn_fs_base__dag_index_checksums(dag_node_t *node, + trail_t *trail, + apr_pool_t *pool); + + +/* Comparison */ + +/* Find out what is the same between two nodes. + + If PROPS_CHANGED is non-null, set *PROPS_CHANGED to 1 if the two + nodes have different property lists, or to 0 if same. + + If CONTENTS_CHANGED is non-null, set *CONTENTS_CHANGED to 1 if the + two nodes have different contents, or to 0 if same. For files, + file contents are compared; for directories, the entries lists are + compared. If one is a file and the other is a directory, the one's + contents will be compared to the other's entries list. (Not + terribly useful, I suppose, but that's the caller's business.) + + ### todo: This function only compares rep keys at the moment. This + may leave us with a slight chance of a false positive, though I + don't really see how that would happen in practice. Nevertheless, + it should probably be fixed. */ +svn_error_t *svn_fs_base__things_different(svn_boolean_t *props_changed, + svn_boolean_t *contents_changed, + dag_node_t *node1, + dag_node_t *node2, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_DAG_H */ diff --git a/subversion/libsvn_fs_base/err.c b/subversion/libsvn_fs_base/err.c new file mode 100644 index 0000000..c1e691d --- /dev/null +++ b/subversion/libsvn_fs_base/err.c @@ -0,0 +1,177 @@ +/* + * err.c : implementation of fs-private error functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <stdarg.h> + +#include "svn_private_config.h" +#include "svn_fs.h" +#include "err.h" +#include "id.h" + +#include "../libsvn_fs/fs-loader.h" + + + +/* Building common error objects. */ + + +svn_error_t * +svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs, svn_revnum_t rev) +{ + return svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt filesystem revision %ld in filesystem '%s'"), + rev, fs->path); +} + + +svn_error_t * +svn_fs_base__err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id) +{ + svn_string_t *id_str = svn_fs_base__id_unparse(id, fs->pool); + return svn_error_createf + (SVN_ERR_FS_ID_NOT_FOUND, 0, + _("Reference to non-existent node '%s' in filesystem '%s'"), + id_str->data, fs->path); +} + + +svn_error_t * +svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev) +{ + /* Log the UUID as this error may be reported to the client. */ + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("No such revision %ld in filesystem '%s'"), + rev, fs->uuid); +} + + +svn_error_t * +svn_fs_base__err_corrupt_txn(svn_fs_t *fs, + const char *txn) +{ + return + svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt entry in 'transactions' table for '%s'" + " in filesystem '%s'"), txn, fs->path); +} + + +svn_error_t * +svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id) +{ + return + svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt entry in 'copies' table for '%s' in filesystem '%s'"), + copy_id, fs->path); +} + + +svn_error_t * +svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn) +{ + return + svn_error_createf + (SVN_ERR_FS_NO_SUCH_TRANSACTION, 0, + _("No transaction named '%s' in filesystem '%s'"), + txn, fs->path); +} + + +svn_error_t * +svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn) +{ + return + svn_error_createf + (SVN_ERR_FS_TRANSACTION_NOT_MUTABLE, 0, + _("Cannot modify transaction named '%s' in filesystem '%s'"), + txn, fs->path); +} + + +svn_error_t * +svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id) +{ + return + svn_error_createf + (SVN_ERR_FS_NO_SUCH_COPY, 0, + _("No copy with id '%s' in filesystem '%s'"), copy_id, fs->path); +} + + +svn_error_t * +svn_fs_base__err_bad_lock_token(svn_fs_t *fs, const char *lock_token) +{ + return + svn_error_createf + (SVN_ERR_FS_BAD_LOCK_TOKEN, 0, + _("Token '%s' does not point to any existing lock in filesystem '%s'"), + lock_token, fs->path); +} + +svn_error_t * +svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path) +{ + return + svn_error_createf + (SVN_ERR_FS_NO_LOCK_TOKEN, 0, + _("No token given for path '%s' in filesystem '%s'"), path, fs->path); +} + +svn_error_t * +svn_fs_base__err_corrupt_lock(svn_fs_t *fs, const char *lock_token) +{ + return + svn_error_createf + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt lock in 'locks' table for '%s' in filesystem '%s'"), + lock_token, fs->path); +} + +svn_error_t * +svn_fs_base__err_no_such_node_origin(svn_fs_t *fs, const char *node_id) +{ + return + svn_error_createf + (SVN_ERR_FS_NO_SUCH_NODE_ORIGIN, 0, + _("No record in 'node-origins' table for node id '%s' in " + "filesystem '%s'"), node_id, fs->path); +} + +svn_error_t * +svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs, svn_checksum_t *checksum) +{ + return + svn_error_createf + (SVN_ERR_FS_NO_SUCH_CHECKSUM_REP, 0, + _("No record in 'checksum-reps' table for checksum '%s' in " + "filesystem '%s'"), svn_checksum_to_cstring_display(checksum, + fs->pool), + fs->path); +} diff --git a/subversion/libsvn_fs_base/err.h b/subversion/libsvn_fs_base/err.h new file mode 100644 index 0000000..5c03d9f --- /dev/null +++ b/subversion/libsvn_fs_base/err.h @@ -0,0 +1,98 @@ +/* + * err.h : interface to routines for returning Berkeley DB errors + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#ifndef SVN_LIBSVN_FS_ERR_H +#define SVN_LIBSVN_FS_ERR_H + +#include <apr_pools.h> + +#include "svn_error.h" +#include "svn_fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* Building common error objects. */ + + +/* SVN_ERR_FS_CORRUPT: the REVISION skel of revision REV in FS is corrupt. */ +svn_error_t *svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs, + svn_revnum_t rev); + +/* SVN_ERR_FS_ID_NOT_FOUND: something in FS refers to node revision + ID, but that node revision doesn't exist. */ +svn_error_t *svn_fs_base__err_dangling_id(svn_fs_t *fs, + const svn_fs_id_t *id); + +/* SVN_ERR_FS_CORRUPT: something in FS refers to filesystem revision REV, + but that filesystem revision doesn't exist. */ +svn_error_t *svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev); + +/* SVN_ERR_FS_CORRUPT: the entry for TXN in the `transactions' table + is corrupt. */ +svn_error_t *svn_fs_base__err_corrupt_txn(svn_fs_t *fs, const char *txn); + +/* SVN_ERR_FS_CORRUPT: the entry for COPY_ID in the `copies' table + is corrupt. */ +svn_error_t *svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id); + +/* SVN_ERR_FS_NO_SUCH_TRANSACTION: there is no transaction named TXN in FS. */ +svn_error_t *svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn); + +/* SVN_ERR_FS_TRANSACTION_NOT_MUTABLE: trying to change the + unchangeable transaction named TXN in FS. */ +svn_error_t *svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn); + +/* SVN_ERR_FS_NO_SUCH_COPY: there is no copy with id COPY_ID in FS. */ +svn_error_t *svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id); + +/* SVN_ERR_FS_BAD_LOCK_TOKEN: LOCK_TOKEN does not refer to a lock in FS. */ +svn_error_t *svn_fs_base__err_bad_lock_token(svn_fs_t *fs, + const char *lock_token); + +/* SVN_ERR_FS_NO_LOCK_TOKEN: no lock token given for PATH in FS. */ +svn_error_t *svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path); + +/* SVN_ERR_FS_CORRUPT: a lock in `locks' table is corrupt. */ +svn_error_t *svn_fs_base__err_corrupt_lock(svn_fs_t *fs, + const char *lock_token); + +/* SVN_ERR_FS_NO_SUCH_NODE_ORIGIN: no recorded node origin for NODE_ID + in FS. */ +svn_error_t *svn_fs_base__err_no_such_node_origin(svn_fs_t *fs, + const char *node_id); + +/* SVN_ERR_FS_NO_SUCH_CHECKSUM_REP: no recorded rep key for CHECKSUM in FS. */ +svn_error_t *svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs, + svn_checksum_t *checksum); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_ERR_H */ diff --git a/subversion/libsvn_fs_base/fs.c b/subversion/libsvn_fs_base/fs.c new file mode 100644 index 0000000..bee921b --- /dev/null +++ b/subversion/libsvn_fs_base/fs.c @@ -0,0 +1,1436 @@ +/* fs.c --- creating, opening and closing filesystems + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <apr_general.h> +#include <apr_pools.h> +#include <apr_file_io.h> + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_fs.h" +#include "svn_path.h" +#include "svn_utf.h" +#include "svn_delta.h" +#include "svn_version.h" +#include "fs.h" +#include "err.h" +#include "dag.h" +#include "revs-txns.h" +#include "uuid.h" +#include "tree.h" +#include "id.h" +#include "lock.h" +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "bdb/bdb-err.h" +#include "bdb/bdb_compat.h" +#include "bdb/env.h" +#include "bdb/nodes-table.h" +#include "bdb/rev-table.h" +#include "bdb/txn-table.h" +#include "bdb/copies-table.h" +#include "bdb/changes-table.h" +#include "bdb/reps-table.h" +#include "bdb/strings-table.h" +#include "bdb/uuids-table.h" +#include "bdb/locks-table.h" +#include "bdb/lock-tokens-table.h" +#include "bdb/node-origins-table.h" +#include "bdb/miscellaneous-table.h" +#include "bdb/checksum-reps-table.h" + +#include "../libsvn_fs/fs-loader.h" +#include "private/svn_fs_util.h" + + +/* Checking for return values, and reporting errors. */ + +/* Check that we're using the right Berkeley DB version. */ +/* FIXME: This check should be abstracted into the DB back-end layer. */ +static svn_error_t * +check_bdb_version(void) +{ + int major, minor, patch; + + db_version(&major, &minor, &patch); + + /* First, check that we're using a reasonably correct of Berkeley DB. */ + if ((major < SVN_FS_WANT_DB_MAJOR) + || (major == SVN_FS_WANT_DB_MAJOR && minor < SVN_FS_WANT_DB_MINOR) + || (major == SVN_FS_WANT_DB_MAJOR && minor == SVN_FS_WANT_DB_MINOR + && patch < SVN_FS_WANT_DB_PATCH)) + return svn_error_createf(SVN_ERR_FS_GENERAL, 0, + _("Bad database version: got %d.%d.%d," + " should be at least %d.%d.%d"), + major, minor, patch, + SVN_FS_WANT_DB_MAJOR, + SVN_FS_WANT_DB_MINOR, + SVN_FS_WANT_DB_PATCH); + + /* Now, check that the version we're running against is the same as + the one we compiled with. */ + if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR) + return svn_error_createf(SVN_ERR_FS_GENERAL, 0, + _("Bad database version:" + " compiled with %d.%d.%d," + " running against %d.%d.%d"), + DB_VERSION_MAJOR, + DB_VERSION_MINOR, + DB_VERSION_PATCH, + major, minor, patch); + return SVN_NO_ERROR; +} + + + +/* Cleanup functions. */ + +/* Close a database in the filesystem FS. + DB_PTR is a pointer to the DB pointer in *FS to close. + NAME is the name of the database, for use in error messages. */ +static svn_error_t * +cleanup_fs_db(svn_fs_t *fs, DB **db_ptr, const char *name) +{ + /* If the BDB environment is panicked, don't do anything, since + attempting to close the database will fail anyway. */ + base_fs_data_t *bfd = fs->fsap_data; + if (*db_ptr && !svn_fs_bdb__get_panic(bfd->bdb)) + { + DB *db = *db_ptr; + char *msg = apr_psprintf(fs->pool, "closing '%s' database", name); + int db_err; + + *db_ptr = 0; + db_err = db->close(db, 0); + if (db_err == DB_RUNRECOVERY) + { + /* We can ignore DB_RUNRECOVERY errors from DB->close, but + must set the panic flag in the environment baton. The + error will be propagated appropriately from + svn_fs_bdb__close. */ + svn_fs_bdb__set_panic(bfd->bdb); + db_err = 0; + } + +#if SVN_BDB_HAS_DB_INCOMPLETE + /* We can ignore DB_INCOMPLETE on db->close and db->sync; it + * just means someone else was using the db at the same time + * we were. See the Berkeley documentation at: + * http://www.sleepycat.com/docs/ref/program/errorret.html#DB_INCOMPLETE + * http://www.sleepycat.com/docs/api_c/db_close.html + */ + if (db_err == DB_INCOMPLETE) + db_err = 0; +#endif /* SVN_BDB_HAS_DB_INCOMPLETE */ + + SVN_ERR(BDB_WRAP(fs, msg, db_err)); + } + + return SVN_NO_ERROR; +} + +/* Close whatever Berkeley DB resources are allocated to FS. */ +static svn_error_t * +cleanup_fs(svn_fs_t *fs) +{ + base_fs_data_t *bfd = fs->fsap_data; + bdb_env_baton_t *bdb = (bfd ? bfd->bdb : NULL); + + if (!bdb) + return SVN_NO_ERROR; + + /* Close the databases. */ + SVN_ERR(cleanup_fs_db(fs, &bfd->nodes, "nodes")); + SVN_ERR(cleanup_fs_db(fs, &bfd->revisions, "revisions")); + SVN_ERR(cleanup_fs_db(fs, &bfd->transactions, "transactions")); + SVN_ERR(cleanup_fs_db(fs, &bfd->copies, "copies")); + SVN_ERR(cleanup_fs_db(fs, &bfd->changes, "changes")); + SVN_ERR(cleanup_fs_db(fs, &bfd->representations, "representations")); + SVN_ERR(cleanup_fs_db(fs, &bfd->strings, "strings")); + SVN_ERR(cleanup_fs_db(fs, &bfd->uuids, "uuids")); + SVN_ERR(cleanup_fs_db(fs, &bfd->locks, "locks")); + SVN_ERR(cleanup_fs_db(fs, &bfd->lock_tokens, "lock-tokens")); + SVN_ERR(cleanup_fs_db(fs, &bfd->node_origins, "node-origins")); + SVN_ERR(cleanup_fs_db(fs, &bfd->checksum_reps, "checksum-reps")); + SVN_ERR(cleanup_fs_db(fs, &bfd->miscellaneous, "miscellaneous")); + + /* Finally, close the environment. */ + bfd->bdb = 0; + { + svn_error_t *err = svn_fs_bdb__close(bdb); + if (err) + return svn_error_createf + (err->apr_err, err, + _("Berkeley DB error for filesystem '%s'" + " while closing environment:\n"), + fs->path); + } + return SVN_NO_ERROR; +} + +#if 0 /* Set to 1 for instrumenting. */ +static void print_fs_stats(svn_fs_t *fs) +{ + base_fs_data_t *bfd = fs->fsap_data; + DB_TXN_STAT *t; + DB_LOCK_STAT *l; + int db_err; + + /* Print transaction statistics for this DB env. */ + if ((db_err = bfd->bdb->env->txn_stat(bfd->bdb->env, &t, 0)) != 0) + fprintf(stderr, "Error running bfd->bdb->env->txn_stat(): %s", + db_strerror(db_err)); + else + { + printf("*** DB transaction stats, right before closing env:\n"); + printf(" Number of transactions currently active: %d\n", + t->st_nactive); + printf(" Max number of active transactions at any one time: %d\n", + t->st_maxnactive); + printf(" Number of transactions that have begun: %d\n", + t->st_nbegins); + printf(" Number of transactions that have aborted: %d\n", + t->st_naborts); + printf(" Number of transactions that have committed: %d\n", + t->st_ncommits); + printf(" Number of times a thread was forced to wait: %d\n", + t->st_region_wait); + printf(" Number of times a thread didn't need to wait: %d\n", + t->st_region_nowait); + printf("*** End DB transaction stats.\n\n"); + } + + /* Print transaction statistics for this DB env. */ + if ((db_err = bfd->bdb->env->lock_stat(bfd->bdb->env, &l, 0)) != 0) + fprintf(stderr, "Error running bfd->bdb->env->lock_stat(): %s", + db_strerror(db_err)); + else + { + printf("*** DB lock stats, right before closing env:\n"); + printf(" The number of current locks: %d\n", + l->st_nlocks); + printf(" Max number of locks at any one time: %d\n", + l->st_maxnlocks); + printf(" Number of current lockers: %d\n", + l->st_nlockers); + printf(" Max number of lockers at any one time: %d\n", + l->st_maxnlockers); + printf(" Number of current objects: %d\n", + l->st_nobjects); + printf(" Max number of objects at any one time: %d\n", + l->st_maxnobjects); + printf(" Total number of locks requested: %d\n", + l->st_nrequests); + printf(" Total number of locks released: %d\n", + l->st_nreleases); + printf(" Total number of lock reqs failed because " + "DB_LOCK_NOWAIT was set: %d\n", l->st_nnowaits); + printf(" Total number of locks not immediately available " + "due to conflicts: %d\n", l->st_nconflicts); + printf(" Number of deadlocks detected: %d\n", l->st_ndeadlocks); + printf(" Number of times a thread waited before " + "obtaining the region lock: %d\n", l->st_region_wait); + printf(" Number of times a thread didn't have to wait: %d\n", + l->st_region_nowait); + printf("*** End DB lock stats.\n\n"); + } + +} +#else +# define print_fs_stats(fs) +#endif /* 0/1 */ + +/* An APR pool cleanup function for a filesystem. DATA must be a + pointer to the filesystem to clean up. + + When the filesystem object's pool is freed, we want the resources + held by Berkeley DB to go away, just like everything else. So we + register this cleanup function with the filesystem's pool, and let + it take care of closing the databases, the environment, and any + other DB objects we might be using. APR calls this function before + actually freeing the pool's memory. + + It's a pity that we can't return an svn_error_t object from an APR + cleanup function. For now, we return the rather generic + SVN_ERR_FS_CLEANUP, and pass the real svn_error_t to the registered + warning callback. */ + +static apr_status_t +cleanup_fs_apr(void *data) +{ + svn_fs_t *fs = data; + svn_error_t *err; + + print_fs_stats(fs); + + err = cleanup_fs(fs); + if (! err) + return APR_SUCCESS; + + /* Darn. An error during cleanup. Call the warning handler to + try and do something "right" with this error. Note that + the default will simply abort(). */ + (*fs->warning)(fs->warning_baton, err); + + svn_error_clear(err); + + return SVN_ERR_FS_CLEANUP; +} + + +static svn_error_t * +base_bdb_set_errcall(svn_fs_t *fs, + void (*db_errcall_fcn)(const char *errpfx, char *msg)) +{ + base_fs_data_t *bfd = fs->fsap_data; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + bfd->bdb->error_info->user_callback = db_errcall_fcn; + + return SVN_NO_ERROR; +} + + +/* Write the DB_CONFIG file. */ +static svn_error_t * +bdb_write_config(svn_fs_t *fs) +{ + const char *dbconfig_file_name = + svn_dirent_join(fs->path, BDB_CONFIG_FILE, fs->pool); + apr_file_t *dbconfig_file = NULL; + int i; + + static const char dbconfig_contents[] = + "# This is the configuration file for the Berkeley DB environment\n" + "# used by your Subversion repository.\n" + "# You must run 'svnadmin recover' whenever you modify this file,\n" + "# for your changes to take effect.\n" + "\n" + "### Lock subsystem\n" + "#\n" + "# Make sure you read the documentation at:\n" + "#\n" + "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/lock_max.html\n" + "#\n" + "# before tweaking these values.\n" + "#\n" + "set_lk_max_locks 2000\n" + "set_lk_max_lockers 2000\n" + "set_lk_max_objects 2000\n" + "\n" + "### Log file subsystem\n" + "#\n" + "# Make sure you read the documentation at:\n" + "#\n" + "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_bsize.html\n" + "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_max.html\n" + "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_limits.html\n" + "#\n" + "# Increase the size of the in-memory log buffer from the default\n" + "# of 32 Kbytes to 256 Kbytes. Decrease the log file size from\n" + "# 10 Mbytes to 1 Mbyte. This will help reduce the amount of disk\n" + "# space required for hot backups. The size of the log file must be\n" + "# at least four times the size of the in-memory log buffer.\n" + "#\n" + "# Note: Decreasing the in-memory buffer size below 256 Kbytes will hurt\n" + "# hurt commit performance. For details, see:\n" + "#\n" + "# http://svn.haxx.se/dev/archive-2002-02/0141.shtml\n" + "#\n" + "set_lg_bsize 262144\n" + "set_lg_max 1048576\n" + "#\n" + "# If you see \"log region out of memory\" errors, bump lg_regionmax.\n" + "# For more information, see:\n" + "#\n" + "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" + "# http://svn.haxx.se/users/archive-2004-10/1000.shtml\n" + "#\n" + "set_lg_regionmax 131072\n" + "#\n" + /* ### Configure this with "svnadmin create --bdb-cache-size" */ + "# The default cache size in BDB is only 256k. As explained in\n" + "# http://svn.haxx.se/dev/archive-2004-12/0368.shtml, this is too\n" + "# small for most applications. Bump this number if \"db_stat -m\"\n" + "# shows too many cache misses.\n" + "#\n" + "set_cachesize 0 1048576 1\n"; + + /* Run-time configurable options. + Each option set consists of a minimum required BDB version, a + config hash key, a header, an inactive form and an active + form. We always write the header; then, depending on the + run-time configuration and the BDB version we're compiling + against, we write either the active or inactive form of the + value. */ + static const struct + { + int bdb_major; + int bdb_minor; + const char *config_key; + const char *header; + const char *inactive; + const char *active; + } dbconfig_options[] = { + /* Controlled by "svnadmin create --bdb-txn-nosync" */ + { 4, 0, SVN_FS_CONFIG_BDB_TXN_NOSYNC, + /* header */ + "#\n" + "# Disable fsync of log files on transaction commit. Read the\n" + "# documentation about DB_TXN_NOSYNC at:\n" + "#\n" + "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" + "#\n" + "# [requires Berkeley DB 4.0]\n" + "#\n", + /* inactive */ + "#set_flags DB_TXN_NOSYNC\n", + /* active */ + "set_flags DB_TXN_NOSYNC\n" }, + /* Controlled by "svnadmin create --bdb-log-keep" */ + { 4, 2, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE, + /* header */ + "#\n" + "# Enable automatic removal of unused transaction log files.\n" + "# Read the documentation about DB_LOG_AUTOREMOVE at:\n" + "#\n" + "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" + "#\n" + "# [requires Berkeley DB 4.2]\n" + "#\n", + /* inactive */ + "#set_flags DB_LOG_AUTOREMOVE\n", + /* active */ + "set_flags DB_LOG_AUTOREMOVE\n" }, + }; + static const int dbconfig_options_length = + sizeof(dbconfig_options)/sizeof(*dbconfig_options); + + + SVN_ERR(svn_io_file_open(&dbconfig_file, dbconfig_file_name, + APR_WRITE | APR_CREATE, APR_OS_DEFAULT, + fs->pool)); + + SVN_ERR(svn_io_file_write_full(dbconfig_file, dbconfig_contents, + sizeof(dbconfig_contents) - 1, NULL, + fs->pool)); + + /* Write the variable DB_CONFIG flags. */ + for (i = 0; i < dbconfig_options_length; ++i) + { + void *value = NULL; + const char *choice; + + if (fs->config) + { + value = svn_hash_gets(fs->config, dbconfig_options[i].config_key); + } + + SVN_ERR(svn_io_file_write_full(dbconfig_file, + dbconfig_options[i].header, + strlen(dbconfig_options[i].header), + NULL, fs->pool)); + + if (((DB_VERSION_MAJOR == dbconfig_options[i].bdb_major + && DB_VERSION_MINOR >= dbconfig_options[i].bdb_minor) + || DB_VERSION_MAJOR > dbconfig_options[i].bdb_major) + && value != NULL && strcmp(value, "0") != 0) + choice = dbconfig_options[i].active; + else + choice = dbconfig_options[i].inactive; + + SVN_ERR(svn_io_file_write_full(dbconfig_file, choice, strlen(choice), + NULL, fs->pool)); + } + + return svn_io_file_close(dbconfig_file, fs->pool); +} + +static svn_error_t * +base_bdb_verify_root(svn_fs_root_t *root, + apr_pool_t *scratch_pool) +{ + /* Verifying is currently a no op for BDB. */ + return SVN_NO_ERROR; +} + +static svn_error_t * +base_bdb_freeze(svn_fs_t *fs, + svn_fs_freeze_func_t freeze_func, + void *freeze_baton, + apr_pool_t *pool) +{ + SVN__NOT_IMPLEMENTED(); +} + + +/* Creating a new filesystem */ + +static fs_vtable_t fs_vtable = { + svn_fs_base__youngest_rev, + svn_fs_base__revision_prop, + svn_fs_base__revision_proplist, + svn_fs_base__change_rev_prop, + svn_fs_base__set_uuid, + svn_fs_base__revision_root, + svn_fs_base__begin_txn, + svn_fs_base__open_txn, + svn_fs_base__purge_txn, + svn_fs_base__list_transactions, + svn_fs_base__deltify, + svn_fs_base__lock, + svn_fs_base__generate_lock_token, + svn_fs_base__unlock, + svn_fs_base__get_lock, + svn_fs_base__get_locks, + base_bdb_verify_root, + base_bdb_freeze, + base_bdb_set_errcall, +}; + +/* Where the format number is stored. */ +#define FORMAT_FILE "format" + +/* Depending on CREATE, create or open the environment and databases + for filesystem FS in PATH. Use POOL for temporary allocations. */ +static svn_error_t * +open_databases(svn_fs_t *fs, + svn_boolean_t create, + int format, + const char *path, + apr_pool_t *pool) +{ + base_fs_data_t *bfd; + + SVN_ERR(svn_fs__check_fs(fs, FALSE)); + + bfd = apr_pcalloc(fs->pool, sizeof(*bfd)); + fs->vtable = &fs_vtable; + fs->fsap_data = bfd; + + /* Initialize the fs's path. */ + fs->path = apr_pstrdup(fs->pool, path); + + if (create) + SVN_ERR(bdb_write_config(fs)); + + /* Create the Berkeley DB environment. */ + { + svn_error_t *err = svn_fs_bdb__open(&(bfd->bdb), path, + SVN_BDB_STANDARD_ENV_FLAGS, + 0666, fs->pool); + if (err) + { + if (create) + return svn_error_createf + (err->apr_err, err, + _("Berkeley DB error for filesystem '%s'" + " while creating environment:\n"), + fs->path); + else + return svn_error_createf + (err->apr_err, err, + _("Berkeley DB error for filesystem '%s'" + " while opening environment:\n"), + fs->path); + } + } + + /* We must register the FS cleanup function *after* opening the + environment, so that it's run before the environment baton + cleanup. */ + apr_pool_cleanup_register(fs->pool, fs, cleanup_fs_apr, + apr_pool_cleanup_null); + + + /* Create the databases in the environment. */ + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'nodes' table") + : N_("opening 'nodes' table")), + svn_fs_bdb__open_nodes_table(&bfd->nodes, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'revisions' table") + : N_("opening 'revisions' table")), + svn_fs_bdb__open_revisions_table(&bfd->revisions, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'transactions' table") + : N_("opening 'transactions' table")), + svn_fs_bdb__open_transactions_table(&bfd->transactions, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'copies' table") + : N_("opening 'copies' table")), + svn_fs_bdb__open_copies_table(&bfd->copies, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'changes' table") + : N_("opening 'changes' table")), + svn_fs_bdb__open_changes_table(&bfd->changes, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'representations' table") + : N_("opening 'representations' table")), + svn_fs_bdb__open_reps_table(&bfd->representations, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'strings' table") + : N_("opening 'strings' table")), + svn_fs_bdb__open_strings_table(&bfd->strings, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'uuids' table") + : N_("opening 'uuids' table")), + svn_fs_bdb__open_uuids_table(&bfd->uuids, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'locks' table") + : N_("opening 'locks' table")), + svn_fs_bdb__open_locks_table(&bfd->locks, + bfd->bdb->env, + create))); + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'lock-tokens' table") + : N_("opening 'lock-tokens' table")), + svn_fs_bdb__open_lock_tokens_table(&bfd->lock_tokens, + bfd->bdb->env, + create))); + + if (format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT) + { + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'node-origins' table") + : N_("opening 'node-origins' table")), + svn_fs_bdb__open_node_origins_table(&bfd->node_origins, + bfd->bdb->env, + create))); + } + + if (format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT) + { + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'miscellaneous' table") + : N_("opening 'miscellaneous' table")), + svn_fs_bdb__open_miscellaneous_table(&bfd->miscellaneous, + bfd->bdb->env, + create))); + } + + if (format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) + { + SVN_ERR(BDB_WRAP(fs, (create + ? N_("creating 'checksum-reps' table") + : N_("opening 'checksum-reps' table")), + svn_fs_bdb__open_checksum_reps_table(&bfd->checksum_reps, + bfd->bdb->env, + create))); + } + + return SVN_NO_ERROR; +} + + +/* Called by functions that initialize an svn_fs_t struct, after that + initialization is done, to populate svn_fs_t->uuid. */ +static svn_error_t * +populate_opened_fs(svn_fs_t *fs, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_fs_base__populate_uuid(fs, scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +base_create(svn_fs_t *fs, const char *path, apr_pool_t *pool, + apr_pool_t *common_pool) +{ + int format = SVN_FS_BASE__FORMAT_NUMBER; + svn_error_t *svn_err; + + /* See if compatibility with older versions was explicitly requested. */ + if (fs->config) + { + if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)) + format = 1; + else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)) + format = 2; + else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE)) + format = 3; + } + + /* Create the environment and databases. */ + svn_err = open_databases(fs, TRUE, format, path, pool); + if (svn_err) goto error; + + /* Initialize the DAG subsystem. */ + svn_err = svn_fs_base__dag_init_fs(fs); + if (svn_err) goto error; + + /* This filesystem is ready. Stamp it with a format number. */ + svn_err = svn_io_write_version_file( + svn_dirent_join(fs->path, FORMAT_FILE, pool), format, pool); + if (svn_err) goto error; + + ((base_fs_data_t *) fs->fsap_data)->format = format; + + SVN_ERR(populate_opened_fs(fs, pool)); + return SVN_NO_ERROR;; + +error: + svn_error_clear(cleanup_fs(fs)); + return svn_err; +} + + +/* Gaining access to an existing Berkeley DB-based filesystem. */ + +svn_error_t * +svn_fs_base__test_required_feature_format(svn_fs_t *fs, + const char *feature, + int requires) +{ + base_fs_data_t *bfd = fs->fsap_data; + if (bfd->format < requires) + return svn_error_createf + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The '%s' feature requires version %d of the filesystem schema; " + "filesystem '%s' uses only version %d"), + feature, requires, fs->path, bfd->format); + return SVN_NO_ERROR; +} + +/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format + number is not the same as the format number supported by this + Subversion. */ +static svn_error_t * +check_format(int format) +{ + /* We currently support any format less than the compiled format number + simultaneously. */ + if (format <= SVN_FS_BASE__FORMAT_NUMBER) + return SVN_NO_ERROR; + + return svn_error_createf( + SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, + _("Expected FS format '%d'; found format '%d'"), + SVN_FS_BASE__FORMAT_NUMBER, format); +} + +static svn_error_t * +base_open(svn_fs_t *fs, const char *path, apr_pool_t *pool, + apr_pool_t *common_pool) +{ + int format; + svn_error_t *svn_err; + svn_boolean_t write_format_file = FALSE; + + /* Read the FS format number. */ + svn_err = svn_io_read_version_file(&format, + svn_dirent_join(path, FORMAT_FILE, pool), + pool); + if (svn_err && APR_STATUS_IS_ENOENT(svn_err->apr_err)) + { + /* Pre-1.2 filesystems did not have a format file (you could say + they were format "0"), so they get upgraded on the fly. + However, we stopped "upgrading on the fly" in 1.5, so older + filesystems should only be bumped to 1.3, which is format "1". */ + svn_error_clear(svn_err); + svn_err = SVN_NO_ERROR; + format = 1; + write_format_file = TRUE; + } + else if (svn_err) + goto error; + + /* Create the environment and databases. */ + svn_err = open_databases(fs, FALSE, format, path, pool); + if (svn_err) goto error; + + ((base_fs_data_t *) fs->fsap_data)->format = format; + SVN_ERR(check_format(format)); + + /* If we lack a format file, write one. */ + if (write_format_file) + { + svn_err = svn_io_write_version_file(svn_dirent_join(path, FORMAT_FILE, + pool), + format, pool); + if (svn_err) goto error; + } + + SVN_ERR(populate_opened_fs(fs, pool)); + return SVN_NO_ERROR; + + error: + svn_error_clear(cleanup_fs(fs)); + return svn_err; +} + + +/* Running recovery on a Berkeley DB-based filesystem. */ + + +/* Recover a database at PATH. Perform catastrophic recovery if FATAL + is TRUE. Use POOL for temporary allocation. */ +static svn_error_t * +bdb_recover(const char *path, svn_boolean_t fatal, apr_pool_t *pool) +{ + bdb_env_baton_t *bdb; + + /* Here's the comment copied from db_recover.c: + + Initialize the environment -- we don't actually do anything + else, that all that's needed to run recovery. + + Note that we specify a private environment, as we're about to + create a region, and we don't want to leave it around. If we + leave the region around, the application that should create it + will simply join it instead, and will then be running with + incorrectly sized (and probably terribly small) caches. */ + + /* Note that since we're using a private environment, we shoudl + /not/ initialize locking. We want the environment files to go + away. */ + + SVN_ERR(svn_fs_bdb__open(&bdb, path, + ((fatal ? DB_RECOVER_FATAL : DB_RECOVER) + | SVN_BDB_PRIVATE_ENV_FLAGS), + 0666, pool)); + return svn_fs_bdb__close(bdb); +} + +static svn_error_t * +base_open_for_recovery(svn_fs_t *fs, const char *path, apr_pool_t *pool, + apr_pool_t *common_pool) +{ + /* Just stash the path in the fs pointer - it's all we really need. */ + fs->path = apr_pstrdup(fs->pool, path); + + return SVN_NO_ERROR; +} + +static svn_error_t * +base_upgrade(svn_fs_t *fs, const char *path, apr_pool_t *pool, + apr_pool_t *common_pool) +{ + const char *version_file_path; + int old_format_number; + svn_error_t *err; + + version_file_path = svn_dirent_join(path, FORMAT_FILE, pool); + + /* Read the old number so we've got it on hand later on. */ + err = svn_io_read_version_file(&old_format_number, version_file_path, pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + /* Pre-1.2 filesystems do not have a 'format' file. */ + old_format_number = 0; + svn_error_clear(err); + err = SVN_NO_ERROR; + } + SVN_ERR(err); + + /* Bump the format file's stored version number. */ + SVN_ERR(svn_io_write_version_file(version_file_path, + SVN_FS_BASE__FORMAT_NUMBER, pool)); + + /* Check and see if we need to record the "bump" revision. */ + if (old_format_number < SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT) + { + apr_pool_t *subpool = svn_pool_create(pool); + svn_revnum_t youngest_rev; + const char *value; + + /* Open the filesystem in a subpool (so we can control its + closure) and do our fiddling. + + NOTE: By using base_open() here instead of open_databases(), + we will end up re-reading the format file that we just wrote. + But it's better to use the existing encapsulation of "opening + the filesystem" rather than duplicating (or worse, partially + duplicating) that logic here. */ + SVN_ERR(base_open(fs, path, subpool, common_pool)); + + /* Fetch the youngest rev, and record it */ + SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, subpool)); + value = apr_psprintf(subpool, "%ld", youngest_rev); + SVN_ERR(svn_fs_base__miscellaneous_set + (fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, + value, subpool)); + svn_pool_destroy(subpool); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +base_verify(svn_fs_t *fs, const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_fs_progress_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool, + apr_pool_t *common_pool) +{ + /* Verifying is currently a no op for BDB. */ + return SVN_NO_ERROR; +} + +static svn_error_t * +base_bdb_recover(svn_fs_t *fs, + svn_cancel_func_t cancel_func, void *cancel_baton, + apr_pool_t *pool) +{ + /* The fs pointer is a fake created in base_open_for_recovery above. + We only care about the path. */ + return bdb_recover(fs->path, FALSE, pool); +} + +static svn_error_t * +base_bdb_pack(svn_fs_t *fs, + const char *path, + svn_fs_pack_notify_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel, + void *cancel_baton, + apr_pool_t *pool, + apr_pool_t *common_pool) +{ + /* Packing is currently a no op for BDB. */ + return SVN_NO_ERROR; +} + + + +/* Running the 'archive' command on a Berkeley DB-based filesystem. */ + + +static svn_error_t * +base_bdb_logfiles(apr_array_header_t **logfiles, + const char *path, + svn_boolean_t only_unused, + apr_pool_t *pool) +{ + bdb_env_baton_t *bdb; + char **filelist; + char **filename; + u_int32_t flags = only_unused ? 0 : DB_ARCH_LOG; + + *logfiles = apr_array_make(pool, 4, sizeof(const char *)); + + SVN_ERR(svn_fs_bdb__open(&bdb, path, + SVN_BDB_STANDARD_ENV_FLAGS, + 0666, pool)); + SVN_BDB_ERR(bdb, bdb->env->log_archive(bdb->env, &filelist, flags)); + + if (filelist == NULL) + return svn_fs_bdb__close(bdb); + + for (filename = filelist; *filename != NULL; ++filename) + { + APR_ARRAY_PUSH(*logfiles, const char *) = apr_pstrdup(pool, *filename); + } + + free(filelist); + + return svn_fs_bdb__close(bdb); +} + + + +/* Copying a live Berkeley DB-base filesystem. */ + +/** + * Delete all unused log files from DBD enviroment at @a live_path that exist + * in @a backup_path. + */ +static svn_error_t * +svn_fs_base__clean_logs(const char *live_path, + const char *backup_path, + apr_pool_t *pool) +{ + apr_array_header_t *logfiles; + + SVN_ERR(base_bdb_logfiles(&logfiles, + live_path, + TRUE, /* Only unused logs */ + pool)); + + { /* Process unused logs from live area */ + int idx; + apr_pool_t *sub_pool = svn_pool_create(pool); + + /* Process log files. */ + for (idx = 0; idx < logfiles->nelts; idx++) + { + const char *log_file = APR_ARRAY_IDX(logfiles, idx, const char *); + const char *live_log_path; + const char *backup_log_path; + + svn_pool_clear(sub_pool); + live_log_path = svn_dirent_join(live_path, log_file, sub_pool); + backup_log_path = svn_dirent_join(backup_path, log_file, sub_pool); + + { /* Compare files. No point in using MD5 and wasting CPU cycles as we + got full copies of both logs */ + + svn_boolean_t files_match = FALSE; + svn_node_kind_t kind; + + /* Check to see if there is a corresponding log file in the backup + directory */ + SVN_ERR(svn_io_check_path(backup_log_path, &kind, pool)); + + /* If the copy of the log exists, compare them */ + if (kind == svn_node_file) + SVN_ERR(svn_io_files_contents_same_p(&files_match, + live_log_path, + backup_log_path, + sub_pool)); + + /* If log files do not match, go to the next log file. */ + if (!files_match) + continue; + } + + SVN_ERR(svn_io_remove_file2(live_log_path, FALSE, sub_pool)); + } + + svn_pool_destroy(sub_pool); + } + + return SVN_NO_ERROR; +} + + +/* DB_ENV->get_flags() and DB->get_pagesize() don't exist prior to + Berkeley DB 4.2. */ +#if SVN_BDB_VERSION_AT_LEAST(4, 2) + +/* Open the BDB environment at PATH and compare its configuration + flags with FLAGS. If every flag in FLAGS is set in the + environment, then set *MATCH to true. Else set *MATCH to false. */ +static svn_error_t * +check_env_flags(svn_boolean_t *match, + u_int32_t flags, + const char *path, + apr_pool_t *pool) +{ + bdb_env_baton_t *bdb; +#if SVN_BDB_VERSION_AT_LEAST(4, 7) + int flag_state; +#else + u_int32_t envflags; +#endif + + SVN_ERR(svn_fs_bdb__open(&bdb, path, + SVN_BDB_STANDARD_ENV_FLAGS, + 0666, pool)); +#if SVN_BDB_VERSION_AT_LEAST(4, 7) + SVN_BDB_ERR(bdb, bdb->env->log_get_config(bdb->env, flags, &flag_state)); +#else + SVN_BDB_ERR(bdb, bdb->env->get_flags(bdb->env, &envflags)); +#endif + + SVN_ERR(svn_fs_bdb__close(bdb)); + +#if SVN_BDB_VERSION_AT_LEAST(4, 7) + if (flag_state == 0) +#else + if (flags & envflags) +#endif + *match = TRUE; + else + *match = FALSE; + + return SVN_NO_ERROR; +} + + +/* Set *PAGESIZE to the size of pages used to hold items in the + database environment located at PATH. +*/ +static svn_error_t * +get_db_pagesize(u_int32_t *pagesize, + const char *path, + apr_pool_t *pool) +{ + bdb_env_baton_t *bdb; + DB *nodes_table; + + SVN_ERR(svn_fs_bdb__open(&bdb, path, + SVN_BDB_STANDARD_ENV_FLAGS, + 0666, pool)); + + /* ### We're only asking for the pagesize on the 'nodes' table. + Is this enough? We never call DB->set_pagesize() on any of + our tables, so presumably BDB is using the same default + pagesize for all our databases, right? */ + SVN_BDB_ERR(bdb, svn_fs_bdb__open_nodes_table(&nodes_table, bdb->env, + FALSE)); + SVN_BDB_ERR(bdb, nodes_table->get_pagesize(nodes_table, pagesize)); + SVN_BDB_ERR(bdb, nodes_table->close(nodes_table, 0)); + + return svn_fs_bdb__close(bdb); +} +#endif /* SVN_BDB_VERSION_AT_LEAST(4, 2) */ + + +/* Copy FILENAME from SRC_DIR to DST_DIR in byte increments of size + CHUNKSIZE. The read/write buffer of size CHUNKSIZE will be + allocated in POOL. If ALLOW_MISSING is set, we won't make a fuss + if FILENAME isn't found in SRC_DIR; otherwise, we will. */ +static svn_error_t * +copy_db_file_safely(const char *src_dir, + const char *dst_dir, + const char *filename, + u_int32_t chunksize, + svn_boolean_t allow_missing, + apr_pool_t *pool) +{ + apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */ + const char *file_src_path = svn_dirent_join(src_dir, filename, pool); + const char *file_dst_path = svn_dirent_join(dst_dir, filename, pool); + svn_error_t *err; + char *buf; + + /* Open source file. If it's missing and that's allowed, there's + nothing more to do here. */ + err = svn_io_file_open(&s, file_src_path, + (APR_READ | APR_LARGEFILE), + APR_OS_DEFAULT, pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err) && allow_missing) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + /* Open destination file. */ + SVN_ERR(svn_io_file_open(&d, file_dst_path, (APR_WRITE | APR_CREATE | + APR_LARGEFILE), + APR_OS_DEFAULT, pool)); + + /* Allocate our read/write buffer. */ + buf = apr_palloc(pool, chunksize); + + /* Copy bytes till the cows come home. */ + while (1) + { + apr_size_t bytes_this_time = chunksize; + svn_error_t *read_err, *write_err; + + /* Read 'em. */ + if ((read_err = svn_io_file_read(s, buf, &bytes_this_time, pool))) + { + if (APR_STATUS_IS_EOF(read_err->apr_err)) + svn_error_clear(read_err); + else + { + svn_error_clear(svn_io_file_close(s, pool)); + svn_error_clear(svn_io_file_close(d, pool)); + return read_err; + } + } + + /* Write 'em. */ + if ((write_err = svn_io_file_write_full(d, buf, bytes_this_time, NULL, + pool))) + { + svn_error_clear(svn_io_file_close(s, pool)); + svn_error_clear(svn_io_file_close(d, pool)); + return write_err; + } + + /* read_err is either NULL, or a dangling pointer - but it is only a + dangling pointer if it used to be an EOF error. */ + if (read_err) + { + SVN_ERR(svn_io_file_close(s, pool)); + SVN_ERR(svn_io_file_close(d, pool)); + break; /* got EOF on read, all files closed, all done. */ + } + } + + return SVN_NO_ERROR; +} + + + + +static svn_error_t * +base_hotcopy(svn_fs_t *src_fs, + svn_fs_t *dst_fs, + const char *src_path, + const char *dest_path, + svn_boolean_t clean_logs, + svn_boolean_t incremental, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_error_t *err; + u_int32_t pagesize; + svn_boolean_t log_autoremove = FALSE; + int format; + + if (incremental) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("BDB repositories do not support incremental " + "hotcopy")); + + /* Check the FS format number to be certain that we know how to + hotcopy this FS. Pre-1.2 filesystems did not have a format file (you + could say they were format "0"), so we will error here. This is not + optimal, but since this has been the case since 1.2.0, and no one has + complained, it apparently isn't much of a concern. (We did not check + the 'format' file in 1.2.x, but we did blindly try to copy 'locks', + which would have errored just the same.) */ + SVN_ERR(svn_io_read_version_file( + &format, svn_dirent_join(src_path, FORMAT_FILE, pool), pool)); + SVN_ERR(check_format(format)); + + /* If using Berkeley DB 4.2 or later, note whether the DB_LOG_AUTO_REMOVE + feature is on. If it is, we have a potential race condition: + another process might delete a logfile while we're in the middle + of copying all the logfiles. (This is not a huge deal; at worst, + the hotcopy fails with a file-not-found error.) */ +#if SVN_BDB_VERSION_AT_LEAST(4, 2) + err = check_env_flags(&log_autoremove, +#if SVN_BDB_VERSION_AT_LEAST(4, 7) + DB_LOG_AUTO_REMOVE, + /* DB_LOG_AUTO_REMOVE was named DB_LOG_AUTOREMOVE before Berkeley DB 4.7. */ +#else + DB_LOG_AUTOREMOVE, +#endif + src_path, pool); +#endif + SVN_ERR(err); + + /* Copy the DB_CONFIG file. */ + SVN_ERR(svn_io_dir_file_copy(src_path, dest_path, "DB_CONFIG", pool)); + + /* In order to copy the database files safely and atomically, we + must copy them in chunks which are multiples of the page-size + used by BDB. See sleepycat docs for details, or svn issue #1818. */ +#if SVN_BDB_VERSION_AT_LEAST(4, 2) + SVN_ERR(get_db_pagesize(&pagesize, src_path, pool)); + if (pagesize < SVN__STREAM_CHUNK_SIZE) + { + /* use the largest multiple of BDB pagesize we can. */ + int multiple = SVN__STREAM_CHUNK_SIZE / pagesize; + pagesize *= multiple; + } +#else + /* default to 128K chunks, which should be safe. + BDB almost certainly uses a power-of-2 pagesize. */ + pagesize = (4096 * 32); +#endif + + /* Copy the databases. */ + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "nodes", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "transactions", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "revisions", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "copies", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "changes", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "representations", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "strings", pagesize, FALSE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "uuids", pagesize, TRUE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "locks", pagesize, TRUE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "lock-tokens", pagesize, TRUE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "node-origins", pagesize, TRUE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "checksum-reps", pagesize, TRUE, pool)); + SVN_ERR(copy_db_file_safely(src_path, dest_path, + "miscellaneous", pagesize, TRUE, pool)); + + { + apr_array_header_t *logfiles; + int idx; + apr_pool_t *subpool; + + SVN_ERR(base_bdb_logfiles(&logfiles, + src_path, + FALSE, /* All logs */ + pool)); + + /* Process log files. */ + subpool = svn_pool_create(pool); + for (idx = 0; idx < logfiles->nelts; idx++) + { + svn_pool_clear(subpool); + err = svn_io_dir_file_copy(src_path, dest_path, + APR_ARRAY_IDX(logfiles, idx, + const char *), + subpool); + if (err) + { + if (log_autoremove) + return + svn_error_quick_wrap + (err, + _("Error copying logfile; the DB_LOG_AUTOREMOVE feature\n" + "may be interfering with the hotcopy algorithm. If\n" + "the problem persists, try deactivating this feature\n" + "in DB_CONFIG")); + else + return svn_error_trace(err); + } + } + svn_pool_destroy(subpool); + } + + /* Since this is a copy we will have exclusive access to the repository. */ + err = bdb_recover(dest_path, TRUE, pool); + if (err) + { + if (log_autoremove) + return + svn_error_quick_wrap + (err, + _("Error running catastrophic recovery on hotcopy; the\n" + "DB_LOG_AUTOREMOVE feature may be interfering with the\n" + "hotcopy algorithm. If the problem persists, try deactivating\n" + "this feature in DB_CONFIG")); + else + return svn_error_trace(err); + } + + /* Only now that the hotcopied filesystem is complete, + stamp it with a format file. */ + SVN_ERR(svn_io_write_version_file( + svn_dirent_join(dest_path, FORMAT_FILE, pool), format, pool)); + + if (clean_logs) + SVN_ERR(svn_fs_base__clean_logs(src_path, dest_path, pool)); + + return SVN_NO_ERROR; +} + + + +/* Deleting a Berkeley DB-based filesystem. */ + + +static svn_error_t * +base_delete_fs(const char *path, + apr_pool_t *pool) +{ + /* First, use the Berkeley DB library function to remove any shared + memory segments. */ + SVN_ERR(svn_fs_bdb__remove(path, pool)); + + /* Remove the environment directory. */ + return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); +} + +static const svn_version_t * +base_version(void) +{ + SVN_VERSION_BODY; +} + +static const char * +base_get_description(void) +{ + return _("Module for working with a Berkeley DB repository."); +} + +static svn_error_t * +base_set_svn_fs_open(svn_fs_t *fs, + svn_error_t *(*svn_fs_open_)(svn_fs_t **, + const char *, + apr_hash_t *, + apr_pool_t *)) +{ + return SVN_NO_ERROR; +} + + +/* Base FS library vtable, used by the FS loader library. */ +static fs_library_vtable_t library_vtable = { + base_version, + base_create, + base_open, + base_open_for_recovery, + base_upgrade, + base_verify, + base_delete_fs, + base_hotcopy, + base_get_description, + base_bdb_recover, + base_bdb_pack, + base_bdb_logfiles, + svn_fs_base__id_parse, + base_set_svn_fs_open +}; + +svn_error_t * +svn_fs_base__init(const svn_version_t *loader_version, + fs_library_vtable_t **vtable, apr_pool_t* common_pool) +{ + static const svn_version_checklist_t checklist[] = + { + { "svn_subr", svn_subr_version }, + { "svn_delta", svn_delta_version }, + { NULL, NULL } + }; + + /* Simplified version check to make sure we can safely use the + VTABLE parameter. The FS loader does a more exhaustive check. */ + if (loader_version->major != SVN_VER_MAJOR) + return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, + _("Unsupported FS loader version (%d) for bdb"), + loader_version->major); + SVN_ERR(svn_ver_check_list(base_version(), checklist)); + SVN_ERR(check_bdb_version()); + SVN_ERR(svn_fs_bdb__init(common_pool)); + + *vtable = &library_vtable; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/fs.h b/subversion/libsvn_fs_base/fs.h new file mode 100644 index 0000000..017c898 --- /dev/null +++ b/subversion/libsvn_fs_base/fs.h @@ -0,0 +1,357 @@ +/* fs.h : interface to Subversion filesystem, private to libsvn_fs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_BASE_H +#define SVN_LIBSVN_FS_BASE_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include <apr_pools.h> +#include <apr_hash.h> +#include "svn_fs.h" + +#include "bdb/env.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** Filesystem schema versions ***/ + +/* The format number of this filesystem. This is independent of the + repository format number, and independent of any other FS back + ends. See the SVN_FS_BASE__MIN_*_FORMAT defines to get a sense of + what changes and features were added in which versions of this + back-end's format. */ +#define SVN_FS_BASE__FORMAT_NUMBER 4 + +/* Minimum format number that supports representation sharing. This + also brings in the support for storing SHA1 checksums. */ +#define SVN_FS_BASE__MIN_REP_SHARING_FORMAT 4 + +/* Minimum format number that supports the 'miscellaneous' table */ +#define SVN_FS_BASE__MIN_MISCELLANY_FORMAT 4 + +/* Minimum format number that supports forward deltas */ +#define SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT 4 + +/* Minimum format number that supports node-origins tracking */ +#define SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT 3 + +/* Minimum format number that supports mergeinfo */ +#define SVN_FS_BASE__MIN_MERGEINFO_FORMAT 3 + +/* Minimum format number that supports svndiff version 1. */ +#define SVN_FS_BASE__MIN_SVNDIFF1_FORMAT 2 + +/* Return SVN_ERR_UNSUPPORTED_FEATURE if the version of filesystem FS does + not indicate support for FEATURE (which REQUIRES a newer version). */ +svn_error_t * +svn_fs_base__test_required_feature_format(svn_fs_t *fs, + const char *feature, + int requires); + + + +/*** Miscellany keys. ***/ + +/* Revision at which the repo started using forward deltas. */ +#define SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE "forward-delta-rev" + + + +/*** The filesystem structure. ***/ + +typedef struct base_fs_data_t +{ + /* A Berkeley DB environment for all the filesystem's databases. + This establishes the scope of the filesystem's transactions. */ + bdb_env_baton_t *bdb; + + /* The filesystem's various tables. See `structure' for details. */ + DB *changes; + DB *copies; + DB *nodes; + DB *representations; + DB *revisions; + DB *strings; + DB *transactions; + DB *uuids; + DB *locks; + DB *lock_tokens; + DB *node_origins; + DB *miscellaneous; + DB *checksum_reps; + + /* A boolean for tracking when we have a live Berkeley DB + transaction trail alive. */ + svn_boolean_t in_txn_trail; + + /* The format number of this FS. */ + int format; + +} base_fs_data_t; + + +/*** Filesystem Revision ***/ +typedef struct revision_t +{ + /* id of the transaction that was committed to create this + revision. */ + const char *txn_id; + +} revision_t; + + +/*** Transaction Kind ***/ +typedef enum transaction_kind_t +{ + transaction_kind_normal = 1, /* normal, uncommitted */ + transaction_kind_committed, /* committed */ + transaction_kind_dead /* uncommitted and dead */ + +} transaction_kind_t; + + +/*** Filesystem Transaction ***/ +typedef struct transaction_t +{ + /* kind of transaction. */ + transaction_kind_t kind; + + /* revision which this transaction was committed to create, or an + invalid revision number if this transaction was never committed. */ + svn_revnum_t revision; + + /* property list (const char * name, svn_string_t * value). + may be NULL if there are no properties. */ + apr_hash_t *proplist; + + /* node revision id of the root node. */ + const svn_fs_id_t *root_id; + + /* node revision id of the node which is the root of the revision + upon which this txn is base. (unfinished only) */ + const svn_fs_id_t *base_id; + + /* copies list (const char * copy_ids), or NULL if there have been + no copies in this transaction. */ + apr_array_header_t *copies; + +} transaction_t; + + +/*** Node-Revision ***/ +typedef struct node_revision_t +{ + /* node kind */ + svn_node_kind_t kind; + + /* predecessor node revision id, or NULL if there is no predecessor + for this node revision */ + const svn_fs_id_t *predecessor_id; + + /* number of predecessors this node revision has (recursively), or + -1 if not known (for backward compatibility). */ + int predecessor_count; + + /* representation key for this node's properties. may be NULL if + there are no properties. */ + const char *prop_key; + + /* representation key for this node's text data (files) or entries + list (dirs). may be NULL if there are no contents. */ + const char *data_key; + + /* data representation instance identifier. Sounds fancy, but is + really just a way to distinguish between "I use the same rep key + as another node because we share ancestry and haven't had our + text touched at all" and "I use the same rep key as another node + only because one or both of us decided to pick up a shared + representation after-the-fact." May be NULL (if this node + revision isn't using a shared rep, or isn't the original + "assignee" of a shared rep). */ + const char *data_key_uniquifier; + + /* representation key for this node's text-data-in-progess (files + only). NULL if no edits are currently in-progress. This field + is always NULL for kinds other than "file". */ + const char *edit_key; + + /* path at which this node first came into existence. */ + const char *created_path; + + /* does this node revision have the mergeinfo tracking property set + on it? (only valid for FS schema 3 and newer) */ + svn_boolean_t has_mergeinfo; + + /* number of children of this node which have the mergeinfo tracking + property set (0 for files; valid only for FS schema 3 and newer). */ + apr_int64_t mergeinfo_count; + +} node_revision_t; + + +/*** Representation Kind ***/ +typedef enum rep_kind_t +{ + rep_kind_fulltext = 1, /* fulltext */ + rep_kind_delta /* delta */ + +} rep_kind_t; + + +/*** "Delta" Offset/Window Chunk ***/ +typedef struct rep_delta_chunk_t +{ + /* diff format version number ### at this point, "svndiff" is the + only format used. */ + apr_byte_t version; + + /* starting offset of the data represented by this chunk */ + svn_filesize_t offset; + + /* string-key to which this representation points. */ + const char *string_key; + + /* size of the fulltext data represented by this delta window. */ + apr_size_t size; + + /* representation-key to use when needed source data for + undeltification. */ + const char *rep_key; + + /* apr_off_t rep_offset; ### not implemented */ + +} rep_delta_chunk_t; + + +/*** Representation ***/ +typedef struct representation_t +{ + /* representation kind */ + rep_kind_t kind; + + /* transaction ID under which representation was created (used as a + mutability flag when compared with a current editing + transaction). */ + const char *txn_id; + + /* Checksums for the contents produced by this representation. + These checksum is for the contents the rep shows to consumers, + regardless of how the rep stores the data under the hood. It is + independent of the storage (fulltext, delta, whatever). + + If this is NULL, then for compatibility behave as though + this checksum matches the expected checksum. */ + svn_checksum_t *md5_checksum; + svn_checksum_t *sha1_checksum; + + /* kind-specific stuff */ + union + { + /* fulltext stuff */ + struct + { + /* string-key which holds the fulltext data */ + const char *string_key; + + } fulltext; + + /* delta stuff */ + struct + { + /* an array of rep_delta_chunk_t * chunks of delta + information */ + apr_array_header_t *chunks; + + } delta; + } contents; +} representation_t; + + +/*** Copy Kind ***/ +typedef enum copy_kind_t +{ + copy_kind_real = 1, /* real copy */ + copy_kind_soft /* soft copy */ + +} copy_kind_t; + + +/*** Copy ***/ +typedef struct copy_t +{ + /* What kind of copy occurred. */ + copy_kind_t kind; + + /* Path of copy source. */ + const char *src_path; + + /* Transaction id of copy source. */ + const char *src_txn_id; + + /* Node-revision of copy destination. */ + const svn_fs_id_t *dst_noderev_id; + +} copy_t; + + +/*** Change ***/ +typedef struct change_t +{ + /* Path of the change. */ + const char *path; + + /* Node revision ID of the change. */ + const svn_fs_id_t *noderev_id; + + /* The kind of change. */ + svn_fs_path_change_kind_t kind; + + /* Text or property mods? */ + svn_boolean_t text_mod; + svn_boolean_t prop_mod; + +} change_t; + + +/*** Lock node ***/ +typedef struct lock_node_t +{ + /* entries list, maps (const char *) name --> (const char *) lock-node-id */ + apr_hash_t *entries; + + /* optional lock-token, might be NULL. */ + const char *lock_token; + +} lock_node_t; + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_BASE_H */ diff --git a/subversion/libsvn_fs_base/id.c b/subversion/libsvn_fs_base/id.c new file mode 100644 index 0000000..c063d02 --- /dev/null +++ b/subversion/libsvn_fs_base/id.c @@ -0,0 +1,208 @@ +/* id.c : operations on node-revision IDs + * + * ==================================================================== + * 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 <stdlib.h> + +#include "id.h" +#include "../libsvn_fs/fs-loader.h" + + + +typedef struct id_private_t { + const char *node_id; + const char *copy_id; + const char *txn_id; +} id_private_t; + + +/* Accessing ID Pieces. */ + +const char * +svn_fs_base__id_node_id(const svn_fs_id_t *id) +{ + id_private_t *pvt = id->fsap_data; + + return pvt->node_id; +} + + +const char * +svn_fs_base__id_copy_id(const svn_fs_id_t *id) +{ + id_private_t *pvt = id->fsap_data; + + return pvt->copy_id; +} + + +const char * +svn_fs_base__id_txn_id(const svn_fs_id_t *id) +{ + id_private_t *pvt = id->fsap_data; + + return pvt->txn_id; +} + + +svn_string_t * +svn_fs_base__id_unparse(const svn_fs_id_t *id, + apr_pool_t *pool) +{ + id_private_t *pvt = id->fsap_data; + + return svn_string_createf(pool, "%s.%s.%s", + pvt->node_id, pvt->copy_id, pvt->txn_id); +} + + +/*** Comparing node IDs ***/ + +svn_boolean_t +svn_fs_base__id_eq(const svn_fs_id_t *a, + const svn_fs_id_t *b) +{ + id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data; + + if (a == b) + return TRUE; + if (strcmp(pvta->node_id, pvtb->node_id) != 0) + return FALSE; + if (strcmp(pvta->copy_id, pvtb->copy_id) != 0) + return FALSE; + if (strcmp(pvta->txn_id, pvtb->txn_id) != 0) + return FALSE; + return TRUE; +} + + +svn_boolean_t +svn_fs_base__id_check_related(const svn_fs_id_t *a, + const svn_fs_id_t *b) +{ + id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data; + + if (a == b) + return TRUE; + + return (strcmp(pvta->node_id, pvtb->node_id) == 0); +} + + +int +svn_fs_base__id_compare(const svn_fs_id_t *a, + const svn_fs_id_t *b) +{ + if (svn_fs_base__id_eq(a, b)) + return 0; + return (svn_fs_base__id_check_related(a, b) ? 1 : -1); +} + + + +/* Creating ID's. */ + +static id_vtable_t id_vtable = { + svn_fs_base__id_unparse, + svn_fs_base__id_compare +}; + + +svn_fs_id_t * +svn_fs_base__id_create(const char *node_id, + const char *copy_id, + const char *txn_id, + apr_pool_t *pool) +{ + svn_fs_id_t *id = apr_palloc(pool, sizeof(*id)); + id_private_t *pvt = apr_palloc(pool, sizeof(*pvt)); + + pvt->node_id = apr_pstrdup(pool, node_id); + pvt->copy_id = apr_pstrdup(pool, copy_id); + pvt->txn_id = apr_pstrdup(pool, txn_id); + id->vtable = &id_vtable; + id->fsap_data = pvt; + return id; +} + + +svn_fs_id_t * +svn_fs_base__id_copy(const svn_fs_id_t *id, apr_pool_t *pool) +{ + svn_fs_id_t *new_id = apr_palloc(pool, sizeof(*new_id)); + id_private_t *new_pvt = apr_palloc(pool, sizeof(*new_pvt)); + id_private_t *pvt = id->fsap_data; + + new_pvt->node_id = apr_pstrdup(pool, pvt->node_id); + new_pvt->copy_id = apr_pstrdup(pool, pvt->copy_id); + new_pvt->txn_id = apr_pstrdup(pool, pvt->txn_id); + new_id->vtable = &id_vtable; + new_id->fsap_data = new_pvt; + return new_id; +} + + +svn_fs_id_t * +svn_fs_base__id_parse(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + svn_fs_id_t *id; + id_private_t *pvt; + char *data_copy, *str; + + /* Dup the ID data into POOL. Our returned ID will have references + into this memory. */ + data_copy = apr_pstrmemdup(pool, data, len); + + /* Alloc a new svn_fs_id_t structure. */ + id = apr_palloc(pool, sizeof(*id)); + pvt = apr_palloc(pool, sizeof(*pvt)); + id->vtable = &id_vtable; + id->fsap_data = pvt; + + /* Now, we basically just need to "split" this data on `.' + characters. We will use svn_cstring_tokenize, which will put + terminators where each of the '.'s used to be. Then our new + id field will reference string locations inside our duplicate + string.*/ + + /* Node Id */ + str = svn_cstring_tokenize(".", &data_copy); + if (str == NULL) + return NULL; + pvt->node_id = str; + + /* Copy Id */ + str = svn_cstring_tokenize(".", &data_copy); + if (str == NULL) + return NULL; + pvt->copy_id = str; + + /* Txn Id */ + str = svn_cstring_tokenize(".", &data_copy); + if (str == NULL) + return NULL; + pvt->txn_id = str; + + return id; +} diff --git a/subversion/libsvn_fs_base/id.h b/subversion/libsvn_fs_base/id.h new file mode 100644 index 0000000..4cdb45c --- /dev/null +++ b/subversion/libsvn_fs_base/id.h @@ -0,0 +1,81 @@ +/* id.h : interface to node ID functions, private to libsvn_fs_base + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_BASE_ID_H +#define SVN_LIBSVN_FS_BASE_ID_H + +#include "svn_fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** ID accessor functions. ***/ + +/* Get the "node id" portion of ID. */ +const char *svn_fs_base__id_node_id(const svn_fs_id_t *id); + +/* Get the "copy id" portion of ID. */ +const char *svn_fs_base__id_copy_id(const svn_fs_id_t *id); + +/* Get the "txn id" portion of ID. */ +const char *svn_fs_base__id_txn_id(const svn_fs_id_t *id); + +/* Convert ID into string form, allocated in POOL. */ +svn_string_t *svn_fs_base__id_unparse(const svn_fs_id_t *id, + apr_pool_t *pool); + +/* Return true if A and B are equal. */ +svn_boolean_t svn_fs_base__id_eq(const svn_fs_id_t *a, + const svn_fs_id_t *b); + +/* Return true if A and B are related. */ +svn_boolean_t svn_fs_base__id_check_related(const svn_fs_id_t *a, + const svn_fs_id_t *b); + +/* Return 0 if A and B are equal, 1 if they are related, -1 otherwise. */ +int svn_fs_base__id_compare(const svn_fs_id_t *a, + const svn_fs_id_t *b); + +/* Create an ID based on NODE_ID, COPY_ID, and TXN_ID, allocated in + POOL. */ +svn_fs_id_t *svn_fs_base__id_create(const char *node_id, + const char *copy_id, + const char *txn_id, + apr_pool_t *pool); + +/* Return a copy of ID, allocated from POOL. */ +svn_fs_id_t *svn_fs_base__id_copy(const svn_fs_id_t *id, + apr_pool_t *pool); + +/* Return an ID resulting from parsing the string DATA (with length + LEN), or NULL if DATA is an invalid ID string. */ +svn_fs_id_t *svn_fs_base__id_parse(const char *data, + apr_size_t len, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_ID_H */ diff --git a/subversion/libsvn_fs_base/key-gen.c b/subversion/libsvn_fs_base/key-gen.c new file mode 100644 index 0000000..411207d --- /dev/null +++ b/subversion/libsvn_fs_base/key-gen.c @@ -0,0 +1,131 @@ +/* key-gen.c --- manufacturing sequential keys for some db tables + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <assert.h> +#include <string.h> + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <stdlib.h> +#include <apr.h> + +#include "key-gen.h" + + + +/*** Keys for reps and strings. ***/ + + +void +svn_fs_base__next_key(const char *this, apr_size_t *len, char *next) +{ + apr_size_t olen = *len; /* remember the first length */ + int i = olen - 1; /* initial index; we work backwards */ + char c; /* current char */ + svn_boolean_t carry = TRUE; /* boolean: do we have a carry or not? + We start with a carry, because we're + incrementing the number, after all. */ + + /* Leading zeros are not allowed, except for the string "0". */ + if ((*len > 1) && (this[0] == '0')) + { + *len = 0; + return; + } + + for (i = (olen - 1); i >= 0; i--) + { + c = this[i]; + + /* Validate as we go. */ + if (! (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')))) + { + *len = 0; + return; + } + + if (carry) + { + if (c == 'z') + next[i] = '0'; + else + { + carry = FALSE; + + if (c == '9') + next[i] = 'a'; + else + next[i] = c + 1; + } + } + else + next[i] = c; + } + + /* The new length is OLEN, plus 1 if there's a carry out of the + leftmost digit. */ + *len = olen + (carry ? 1 : 0); + + /* Ensure that we haven't overrun the (ludicrous) bound on key length. + Note that MAX_KEY_SIZE is a bound on the size *including* + the trailing null byte. */ + assert(*len < MAX_KEY_SIZE); + + /* Now we know it's safe to add the null terminator. */ + next[*len] = '\0'; + + /* Handle any leftover carry. */ + if (carry) + { + memmove(next+1, next, olen); + next[0] = '1'; + } +} + + +int +svn_fs_base__key_compare(const char *a, const char *b) +{ + int a_len = strlen(a); + int b_len = strlen(b); + int cmp; + + if (a_len > b_len) + return 1; + if (b_len > a_len) + return -1; + cmp = strcmp(a, b); + return (cmp ? (cmp / abs(cmp)) : 0); +} + + +svn_boolean_t +svn_fs_base__same_keys(const char *a, const char *b) +{ + if (! (a || b)) + return TRUE; + if (a && (! b)) + return FALSE; + if ((! a) && b) + return FALSE; + return (strcmp(a, b) == 0); +} diff --git a/subversion/libsvn_fs_base/key-gen.h b/subversion/libsvn_fs_base/key-gen.h new file mode 100644 index 0000000..e1cd1aa --- /dev/null +++ b/subversion/libsvn_fs_base/key-gen.h @@ -0,0 +1,100 @@ +/* key-gen.c --- manufacturing sequential keys for some db tables + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_KEY_GEN_H +#define SVN_LIBSVN_FS_KEY_GEN_H + +#include <apr.h> + +#include "svn_types.h" +#include "private/svn_skel.h" /* ### for svn_fs_base__{get,put}size() */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* The alphanumeric keys passed in and out of svn_fs_base__next_key + are guaranteed never to be longer than this many bytes, + *including* the trailing null byte. It is therefore safe + to declare a key as "char key[MAX_KEY_SIZE]". + + Note that this limit will be a problem if the number of + keys in a table ever exceeds + + 18217977168218728251394687124089371267338971528174 + 76066745969754933395997209053270030282678007662838 + 67331479599455916367452421574456059646801054954062 + 15017704234999886990788594743994796171248406730973 + 80736524850563115569208508785942830080999927310762 + 50733948404739350551934565743979678824151197232629 + 947748581376, + + but that's a risk we'll live with for now. */ +#define MAX_KEY_SIZE 200 + +/* In many of the databases, the value at this key is the key to use + when storing a new record. */ +#define NEXT_KEY_KEY "next-key" + + +/* Generate the next key after a given alphanumeric key. + * + * The first *LEN bytes of THIS are an ascii representation of a + * number in base 36: digits 0-9 have their usual values, and a-z have + * values 10-35. + * + * The new key is stored in NEXT, null-terminated. NEXT must be at + * least *LEN + 2 bytes long -- one extra byte to hold a possible + * overflow column, and one for null termination. On return, *LEN + * will be set to the length of the new key, not counting the null + * terminator. In other words, the outgoing *LEN will be either equal + * to the incoming, or to the incoming + 1. + * + * If THIS contains anything other than digits and lower-case + * alphabetic characters, or if it starts with `0' but is not the + * string "0", then *LEN is set to zero and the effect on NEXT + * is undefined. + */ +void svn_fs_base__next_key(const char *this, apr_size_t *len, char *next); + + +/* Compare two strings A and B as base-36 alphanumeric keys. + * + * Return -1, 0, or 1 if A is less than, equal to, or greater than B, + * respectively. + */ +int svn_fs_base__key_compare(const char *a, const char *b); + +/* Compare two strings A and B as base-36 alphanumber keys. + * + * Return TRUE iff both keys are NULL or both keys have the same + * contents. + */ +svn_boolean_t svn_fs_base__same_keys(const char *a, const char *b); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_KEY_GEN_H */ diff --git a/subversion/libsvn_fs_base/lock.c b/subversion/libsvn_fs_base/lock.c new file mode 100644 index 0000000..79f72cc --- /dev/null +++ b/subversion/libsvn_fs_base/lock.c @@ -0,0 +1,594 @@ +/* lock.c : functions for manipulating filesystem locks. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_fs.h" +#include "svn_private_config.h" + +#include <apr_uuid.h> + +#include "lock.h" +#include "tree.h" +#include "err.h" +#include "bdb/locks-table.h" +#include "bdb/lock-tokens-table.h" +#include "util/fs_skels.h" +#include "../libsvn_fs/fs-loader.h" +#include "private/svn_fs_util.h" +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.h" + + +/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as + part of TRAIL. */ +static svn_error_t * +add_lock_and_token(svn_lock_t *lock, + const char *lock_token, + const char *path, + trail_t *trail) +{ + SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock, + trail, trail->pool)); + return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token, + trail, trail->pool); +} + + +/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH, + whose KIND is supplied), as part of TRAIL. */ +static svn_error_t * +delete_lock_and_token(const char *lock_token, + const char *path, + trail_t *trail) +{ + SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token, + trail, trail->pool)); + return svn_fs_bdb__lock_token_delete(trail->fs, path, + trail, trail->pool); +} + + +struct lock_args +{ + svn_lock_t **lock_p; + const char *path; + const char *token; + const char *comment; + svn_boolean_t is_dav_comment; + svn_boolean_t steal_lock; + apr_time_t expiration_date; + svn_revnum_t current_rev; +}; + + +static svn_error_t * +txn_body_lock(void *baton, trail_t *trail) +{ + struct lock_args *args = baton; + svn_node_kind_t kind = svn_node_file; + svn_lock_t *existing_lock; + svn_lock_t *lock; + + SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool)); + + /* Until we implement directory locks someday, we only allow locks + on files or non-existent paths. */ + if (kind == svn_node_dir) + return SVN_FS__ERR_NOT_FILE(trail->fs, args->path); + + /* While our locking implementation easily supports the locking of + nonexistent paths, we deliberately choose not to allow such madness. */ + if (kind == svn_node_none) + { + if (SVN_IS_VALID_REVNUM(args->current_rev)) + return svn_error_createf( + SVN_ERR_FS_OUT_OF_DATE, NULL, + _("Path '%s' doesn't exist in HEAD revision"), + args->path); + else + return svn_error_createf( + SVN_ERR_FS_NOT_FOUND, NULL, + _("Path '%s' doesn't exist in HEAD revision"), + args->path); + } + + /* There better be a username attached to the fs. */ + if (!trail->fs->access_ctx || !trail->fs->access_ctx->username) + return SVN_FS__ERR_NO_USER(trail->fs); + + /* Is the caller attempting to lock an out-of-date working file? */ + if (SVN_IS_VALID_REVNUM(args->current_rev)) + { + svn_revnum_t created_rev; + SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path, + trail, trail->pool)); + + /* SVN_INVALID_REVNUM means the path doesn't exist. So + apparently somebody is trying to lock something in their + working copy, but somebody else has deleted the thing + from HEAD. That counts as being 'out of date'. */ + if (! SVN_IS_VALID_REVNUM(created_rev)) + return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, + "Path '%s' doesn't exist in HEAD revision", + args->path); + + if (args->current_rev < created_rev) + return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, + "Lock failed: newer version of '%s' exists", + args->path); + } + + /* If the caller provided a TOKEN, we *really* need to see + if a lock already exists with that token, and if so, verify that + the lock's path matches PATH. Otherwise we run the risk of + breaking the 1-to-1 mapping of lock tokens to locked paths. */ + if (args->token) + { + svn_lock_t *lock_from_token; + svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs, + args->token, trail, + trail->pool); + if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) + { + svn_error_clear(err); + } + else + { + SVN_ERR(err); + if (strcmp(lock_from_token->path, args->path) != 0) + return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, + "Lock failed: token refers to existing " + "lock with non-matching path."); + } + } + + /* Is the path already locked? + + Note that this next function call will automatically ignore any + errors about {the path not existing as a key, the path's token + not existing as a key, the lock just having been expired}. And + that's totally fine. Any of these three errors are perfectly + acceptable to ignore; it means that the path is now free and + clear for locking, because the bdb funcs just cleared out both + of the tables for us. */ + SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path, + trail, trail->pool)); + if (existing_lock) + { + if (! args->steal_lock) + { + /* Sorry, the path is already locked. */ + return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs, + existing_lock); + } + else + { + /* ARGS->steal_lock is set, so fs_username is "stealing" the + lock from lock->owner. Destroy the existing lock. */ + SVN_ERR(delete_lock_and_token(existing_lock->token, + existing_lock->path, trail)); + } + } + + /* Create a new lock, and add it to the tables. */ + lock = svn_lock_create(trail->pool); + if (args->token) + lock->token = apr_pstrdup(trail->pool, args->token); + else + SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs, + trail->pool)); + lock->path = apr_pstrdup(trail->pool, args->path); + lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username); + lock->comment = apr_pstrdup(trail->pool, args->comment); + lock->is_dav_comment = args->is_dav_comment; + lock->creation_date = apr_time_now(); + lock->expiration_date = args->expiration_date; + SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail)); + *(args->lock_p) = lock; + + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_fs_base__lock(svn_lock_t **lock, + svn_fs_t *fs, + const char *path, + const char *token, + const char *comment, + svn_boolean_t is_dav_comment, + apr_time_t expiration_date, + svn_revnum_t current_rev, + svn_boolean_t steal_lock, + apr_pool_t *pool) +{ + struct lock_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.lock_p = lock; + args.path = svn_fs__canonicalize_abspath(path, pool); + args.token = token; + args.comment = comment; + args.is_dav_comment = is_dav_comment; + args.steal_lock = steal_lock; + args.expiration_date = expiration_date; + args.current_rev = current_rev; + + return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool); +} + + +svn_error_t * +svn_fs_base__generate_lock_token(const char **token, + svn_fs_t *fs, + apr_pool_t *pool) +{ + /* Notice that 'fs' is currently unused. But perhaps someday, we'll + want to use the fs UUID + some incremented number? For now, we + generate a URI that matches the DAV RFC. We could change this to + some other URI scheme someday, if we wish. */ + *token = apr_pstrcat(pool, "opaquelocktoken:", + svn_uuid_generate(pool), (char *)NULL); + return SVN_NO_ERROR; +} + + +struct unlock_args +{ + const char *path; + const char *token; + svn_boolean_t break_lock; +}; + + +static svn_error_t * +txn_body_unlock(void *baton, trail_t *trail) +{ + struct unlock_args *args = baton; + const char *lock_token; + svn_lock_t *lock; + + /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */ + SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path, + trail, trail->pool)); + + /* If not breaking the lock, we need to do some more checking. */ + if (!args->break_lock) + { + /* Sanity check: The lock token must exist, and must match. */ + if (args->token == NULL) + return svn_fs_base__err_no_lock_token(trail->fs, args->path); + else if (strcmp(lock_token, args->token) != 0) + return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path); + + SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token, + trail, trail->pool)); + + /* There better be a username attached to the fs. */ + if (!trail->fs->access_ctx || !trail->fs->access_ctx->username) + return SVN_FS__ERR_NO_USER(trail->fs); + + /* And that username better be the same as the lock's owner. */ + if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0) + return SVN_FS__ERR_LOCK_OWNER_MISMATCH( + trail->fs, + trail->fs->access_ctx->username, + lock->owner); + } + + /* Remove a row from each of the locking tables. */ + return delete_lock_and_token(lock_token, args->path, trail); +} + + +svn_error_t * +svn_fs_base__unlock(svn_fs_t *fs, + const char *path, + const char *token, + svn_boolean_t break_lock, + apr_pool_t *pool) +{ + struct unlock_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.path = svn_fs__canonicalize_abspath(path, pool); + args.token = token; + args.break_lock = break_lock; + return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool); +} + + +svn_error_t * +svn_fs_base__get_lock_helper(svn_lock_t **lock_p, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + const char *lock_token; + svn_error_t *err; + + err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path, + trail, pool); + + /* We've deliberately decided that this function doesn't tell the + caller *why* the lock is unavailable. */ + if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) + || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) + { + svn_error_clear(err); + *lock_p = NULL; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + /* Same situation here. */ + err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool); + if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED) + || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN))) + { + svn_error_clear(err); + *lock_p = NULL; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + return svn_error_trace(err); +} + + +struct lock_token_get_args +{ + svn_lock_t **lock_p; + const char *path; +}; + + +static svn_error_t * +txn_body_get_lock(void *baton, trail_t *trail) +{ + struct lock_token_get_args *args = baton; + return svn_fs_base__get_lock_helper(args->lock_p, args->path, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__get_lock(svn_lock_t **lock, + svn_fs_t *fs, + const char *path, + apr_pool_t *pool) +{ + struct lock_token_get_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.path = svn_fs__canonicalize_abspath(path, pool); + args.lock_p = lock; + return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool); +} + +/* Implements `svn_fs_get_locks_callback_t', spooling lock information + to a stream as the filesystem provides it. BATON is an 'svn_stream_t *' + object pointing to the stream. We'll write the spool stream with a + format like so: + + SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ... + + where each skel is a lock skel (the same format we use to store + locks in the `locks' table). */ +static svn_error_t * +spool_locks_info(void *baton, + svn_lock_t *lock, + apr_pool_t *pool) +{ + svn_skel_t *lock_skel; + svn_stream_t *stream = baton; + const char *skel_len; + svn_stringbuf_t *skel_buf; + apr_size_t len; + + SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool)); + skel_buf = svn_skel__unparse(lock_skel, pool); + skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len); + len = strlen(skel_len); + SVN_ERR(svn_stream_write(stream, skel_len, &len)); + len = skel_buf->len; + SVN_ERR(svn_stream_write(stream, skel_buf->data, &len)); + len = 1; + return svn_stream_write(stream, "\n", &len); +} + + +struct locks_get_args +{ + const char *path; + svn_depth_t depth; + svn_stream_t *stream; +}; + + +static svn_error_t * +txn_body_get_locks(void *baton, trail_t *trail) +{ + struct locks_get_args *args = baton; + return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth, + spool_locks_info, args->stream, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__get_locks(svn_fs_t *fs, + const char *path, + svn_depth_t depth, + svn_fs_get_locks_callback_t get_locks_func, + void *get_locks_baton, + apr_pool_t *pool) +{ + struct locks_get_args args; + svn_stream_t *stream; + svn_stringbuf_t *buf; + svn_boolean_t eof; + apr_pool_t *iterpool = svn_pool_create(pool); + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.path = svn_fs__canonicalize_abspath(path, pool); + args.depth = depth; + /* Enough for 100+ locks if the comments are small. */ + args.stream = svn_stream__from_spillbuf(4 * 1024 /* blocksize */, + 64 * 1024 /* maxsize */, + pool); + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool)); + + /* Read the stream calling GET_LOCKS_FUNC(). */ + stream = args.stream; + + while (1) + { + apr_size_t len, skel_len; + char c, *skel_buf; + svn_skel_t *lock_skel; + svn_lock_t *lock; + apr_uint64_t ui64; + svn_error_t *err; + + svn_pool_clear(iterpool); + + /* Read a skel length line and parse it for the skel's length. */ + SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool)); + if (eof) + break; + err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10); + if (err) + return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL); + skel_len = (apr_size_t)ui64; + + /* Now read that much into a buffer. */ + skel_buf = apr_palloc(pool, skel_len + 1); + SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len)); + skel_buf[skel_len] = '\0'; + + /* Read the extra newline that follows the skel. */ + len = 1; + SVN_ERR(svn_stream_read(stream, &c, &len)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); + + /* Parse the skel into a lock, and notify the caller. */ + lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool); + SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool)); + SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool)); + } + + SVN_ERR(svn_stream_close(stream)); + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + + +/* Utility function: verify that a lock can be used. + + If no username is attached to the FS, return SVN_ERR_FS_NO_USER. + + If the FS username doesn't match LOCK's owner, return + SVN_ERR_FS_LOCK_OWNER_MISMATCH. + + If FS hasn't been supplied with a matching lock-token for LOCK, + return SVN_ERR_FS_BAD_LOCK_TOKEN. + + Otherwise return SVN_NO_ERROR. + */ +static svn_error_t * +verify_lock(svn_fs_t *fs, + svn_lock_t *lock, + apr_pool_t *pool) +{ + if ((! fs->access_ctx) || (! fs->access_ctx->username)) + return svn_error_createf + (SVN_ERR_FS_NO_USER, NULL, + _("Cannot verify lock on path '%s'; no username available"), + lock->path); + + else if (strcmp(fs->access_ctx->username, lock->owner) != 0) + return svn_error_createf + (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, + _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), + fs->access_ctx->username, lock->path, lock->owner); + + else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) + return svn_error_createf + (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, + _("Cannot verify lock on path '%s'; no matching lock-token available"), + lock->path); + + return SVN_NO_ERROR; +} + + +/* This implements the svn_fs_get_locks_callback_t interface, where + BATON is just an svn_fs_t object. */ +static svn_error_t * +get_locks_callback(void *baton, + svn_lock_t *lock, + apr_pool_t *pool) +{ + return verify_lock(baton, lock, pool); +} + + +/* The main routine for lock enforcement, used throughout libsvn_fs_base. */ +svn_error_t * +svn_fs_base__allow_locked_operation(const char *path, + svn_boolean_t recurse, + trail_t *trail, + apr_pool_t *pool) +{ + if (recurse) + { + /* Discover all locks at or below the path. */ + SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity, + get_locks_callback, + trail->fs, trail, pool)); + } + else + { + svn_lock_t *lock; + + /* Discover any lock attached to the path. */ + SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool)); + if (lock) + SVN_ERR(verify_lock(trail->fs, lock, pool)); + } + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/lock.h b/subversion/libsvn_fs_base/lock.h new file mode 100644 index 0000000..603e78c --- /dev/null +++ b/subversion/libsvn_fs_base/lock.h @@ -0,0 +1,120 @@ +/* lock.h : internal interface to lock functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_LOCK_H +#define SVN_LIBSVN_FS_LOCK_H + +#include "trail.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* These functions implement part of the FS loader library's fs + vtables. See the public svn_fs.h for docstrings.*/ + +svn_error_t *svn_fs_base__lock(svn_lock_t **lock, + svn_fs_t *fs, + const char *path, + const char *token, + const char *comment, + svn_boolean_t is_dav_comment, + apr_time_t expiration_date, + svn_revnum_t current_rev, + svn_boolean_t steal_lock, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__generate_lock_token(const char **token, + svn_fs_t *fs, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__unlock(svn_fs_t *fs, + const char *path, + const char *token, + svn_boolean_t break_lock, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__get_lock(svn_lock_t **lock, + svn_fs_t *fs, + const char *path, + apr_pool_t *pool); + +svn_error_t * +svn_fs_base__get_locks(svn_fs_t *fs, + const char *path, + svn_depth_t depth, + svn_fs_get_locks_callback_t get_locks_func, + void *get_locks_baton, + apr_pool_t *pool); + + + +/* These next functions are 'helpers' for internal fs use: + if a fs function's txn_body needs to enforce existing locks, it + should use these routines: +*/ + + +/* Implements main logic of 'svn_fs_get_lock' (or in this + case, svn_fs_base__get_lock() above.) See svn_fs.h. */ +svn_error_t * +svn_fs_base__get_lock_helper(svn_lock_t **lock_p, + const char *path, + trail_t *trail, + apr_pool_t *pool); + + +/* Examine PATH for existing locks, and check whether they can be + used. Do all work in the context of TRAIL, using POOL for + temporary allocations. + + If no locks are present, return SVN_NO_ERROR. + + If PATH is locked (or contains locks "below" it, when RECURSE is + set), then verify that: + + 1. a username has been supplied to TRAIL->fs's access-context, + else return SVN_ERR_FS_NO_USER. + + 2. for every lock discovered, the current username in the access + context of TRAIL->fs matches the "owner" of the lock, else + return SVN_ERR_FS_LOCK_OWNER_MISMATCH. + + 3. for every lock discovered, a matching lock token has been + passed into TRAIL->fs's access-context, else return + SVN_ERR_FS_BAD_LOCK_TOKEN. + + If all three conditions are met, return SVN_NO_ERROR. +*/ +svn_error_t *svn_fs_base__allow_locked_operation(const char *path, + svn_boolean_t recurse, + trail_t *trail, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_LOCK_H */ diff --git a/subversion/libsvn_fs_base/node-rev.c b/subversion/libsvn_fs_base/node-rev.c new file mode 100644 index 0000000..949a24b --- /dev/null +++ b/subversion/libsvn_fs_base/node-rev.c @@ -0,0 +1,126 @@ +/* node-rev.c --- storing and retrieving NODE-REVISION skels + * + * ==================================================================== + * 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> + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" +#include "fs.h" +#include "err.h" +#include "node-rev.h" +#include "reps-strings.h" +#include "id.h" +#include "../libsvn_fs/fs-loader.h" + +#include "bdb/nodes-table.h" +#include "bdb/node-origins-table.h" + + +/* Creating completely new nodes. */ + + +svn_error_t * +svn_fs_base__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, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_id_t *id; + base_fs_data_t *bfd = fs->fsap_data; + + /* Find an unused ID for the node. */ + SVN_ERR(svn_fs_bdb__new_node_id(&id, fs, copy_id, txn_id, trail, pool)); + + /* Store its NODE-REVISION skel. */ + SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, noderev, trail, pool)); + + /* Add a record in the node origins index table if our format + supports it. */ + if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT) + { + SVN_ERR(svn_fs_bdb__set_node_origin(fs, svn_fs_base__id_node_id(id), + id, trail, pool)); + } + + *id_p = id; + return SVN_NO_ERROR; +} + + + +/* Creating new revisions of existing nodes. */ + +svn_error_t * +svn_fs_base__create_successor(const svn_fs_id_t **new_id_p, + svn_fs_t *fs, + const svn_fs_id_t *old_id, + node_revision_t *new_noderev, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_id_t *new_id; + + /* Choose an ID for the new node, and store it in the database. */ + SVN_ERR(svn_fs_bdb__new_successor_id(&new_id, fs, old_id, copy_id, + txn_id, trail, pool)); + + /* Store the new skel under that ID. */ + SVN_ERR(svn_fs_bdb__put_node_revision(fs, new_id, new_noderev, + trail, pool)); + + *new_id_p = new_id; + return SVN_NO_ERROR; +} + + + +/* Deleting a node revision. */ + +svn_error_t * +svn_fs_base__delete_node_revision(svn_fs_t *fs, + const svn_fs_id_t *id, + svn_boolean_t origin_also, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + + /* ### todo: here, we should adjust other nodes to compensate for + the missing node. */ + + /* Delete the node origin record, too, if asked to do so and our + format supports it. */ + if (origin_also && (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)) + { + SVN_ERR(svn_fs_bdb__delete_node_origin(fs, svn_fs_base__id_node_id(id), + trail, pool)); + } + + return svn_fs_bdb__delete_nodes_entry(fs, id, trail, pool); +} diff --git a/subversion/libsvn_fs_base/node-rev.h b/subversion/libsvn_fs_base/node-rev.h new file mode 100644 index 0000000..206be20 --- /dev/null +++ b/subversion/libsvn_fs_base/node-rev.h @@ -0,0 +1,101 @@ +/* node-rev.h : interface to node revision retrieval and storage + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_NODE_REV_H +#define SVN_LIBSVN_FS_NODE_REV_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" +#include "trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** Functions. ***/ + +/* Create an entirely new, mutable node in the filesystem FS, whose + NODE-REVISION is NODEREV, as part of TRAIL. Set *ID_P to the new + node revision's ID. Use POOL for any temporary allocation. + + COPY_ID is the copy_id to use in the node revision ID returned in + *ID_P. + + TXN_ID is the Subversion transaction under which this occurs. + + After this call, the node table manager assumes that the new node's + contents will change frequently. */ +svn_error_t *svn_fs_base__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, + trail_t *trail, + apr_pool_t *pool); + +/* Create a node revision in FS which is an immediate successor of + OLD_ID, whose contents are NEW_NR, as part of TRAIL. Set *NEW_ID_P + to the new node revision's ID. Use POOL for any temporary + allocation. + + COPY_ID, if non-NULL, is a key into the `copies' table, and + indicates that this new node is being created as the result of a + copy operation, and specifically which operation that was. + + TXN_ID is the Subversion transaction under which this occurs. + + After this call, the deltification code assumes that the new node's + contents will change frequently, and will avoid representing other + nodes as deltas against this node's contents. */ +svn_error_t *svn_fs_base__create_successor(const svn_fs_id_t **new_id_p, + svn_fs_t *fs, + const svn_fs_id_t *old_id, + node_revision_t *new_nr, + const char *copy_id, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete node revision ID from FS's `nodes' table, as part of TRAIL. + If ORIGIN_ALSO is set, also delete the record for this ID's node ID + from the `node-origins' index table (which is typically only done + if the caller thinks that ID points to the only node revision ID in + its line of history). + + WARNING: This does not check that the node revision is mutable! + Callers should do that check themselves. */ +svn_error_t *svn_fs_base__delete_node_revision(svn_fs_t *fs, + const svn_fs_id_t *id, + svn_boolean_t origin_also, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_NODE_REV_H */ diff --git a/subversion/libsvn_fs_base/notes/TODO b/subversion/libsvn_fs_base/notes/TODO new file mode 100644 index 0000000..c72af03 --- /dev/null +++ b/subversion/libsvn_fs_base/notes/TODO @@ -0,0 +1,137 @@ +What's happening now + +The filesystem needs some path validation stuffs independent of the +SVN path utilities. A filesystem path is a well-defined Thing that +should be held a safe distance away from future changes to SVN's +general path library. + + +Incorrectnesses + +We must ensure that node numbers are never reused. If we open a node, +svn_fs_delete it, and then create new nodes, what happens when the +original node structure suddenly comes to refer to an entirely +different node? Files become directories? + +We should convert filenames to some canonical Unicode form, for +comparison. + +Does everyone call svn_fs__check_fs who should? + +svn_fs_delete will actually delete non-empty directories, if they're +not cloned. This is inconsistent; should it be fixed? + +Does every operation on a deleted node or completed transaction fail +gracefully? + +Produce helpful error messages when filename paths contain null +characters. + + +Uglinesses + +Fix up comments in svn_fs.h for transactions. + +Add `public name' member to filesystem structure, to use to identify +the filesystem in error messages. When driven by DAV, this could be a +URL. + +When a dag function signals an error, it has no idea what the path of +the relevant node was. But node revision ID's are pretty useless to +the user. tree.c should probably rewrap some errors. + +svn_fs__getsize shouldn't rely on a maximum value for detecting +overflow. + +The use of svn_fs__getsize in svn_fs__parse_id is ugly --- what if +svn_vernum_t and apr_size_t aren't the same size? + +Consider some macros or accessory functions for referencing the pieces +of the NODE-REVISION skel (instead of seeing stuff like +node->children->next->next and such other unreadable rubbish) + + +Slownesses + +We don't store older node revisions as deltas yet. + +The delta algorithm walks the whole tree using a single pool, so the +memory used is proportional to the size of the target tree. Instead, +it should use a separate subpool every time it recurses into a new +directory, and free that subpool as soon as it's done processing that +subdirectory, so the memory used is proportional to the depth of the +tree. + +We should move as much real content out of the NODE-REVISION skel as +possible; the skels should be holding only small stuff (node kind, +flags). +- File contents and deltas should be moved out to a `contents' table. + The NODE-REVISION skel should simply contain a key into that table. +- Directory contents should be moved out to a `directories' table, + with a separate table entry for each directory entry. Keys into the + table should be of the form `NODE-ID ENTRY-NAME NODE-REVISION', and + values should be node revision ID's, or the word `deleted'; to look + up an entry named E in a directory whose node revision is N.R, + search for the entry `N E x', where x is the largest number present + <= R. +- Property lists should be moved out to a table `properties', indexed + similarly to the above. We could deltify property contents the + same way we do file contents. + + +Amenities + +Extend svn_fs_copy to handle mutable nodes. + +Long term ideas: + +- directory entry cache: + Create a cache mapping a node revision id X plus a filename component + N onto a new node revision id Y, meaning that X is a directory in + which the name N is bound to ID Y. If everything were in the cache, + this function could run with no I/O except for the final node. + + Since node revisions never change, we wouldn't have to worry about + invalidating the cache. Mutable node objects will need special + handling, of course. + +- fulltext cache: + If we've recently computed a node's fulltext, we might want to keep + that around in case we need to compute one of its nearby ancestors' + fulltext, too. This could be a waste, though --- the access + patterns are a mix of linear scan (backwards to reconstruct a given + revision) and random (who knows what node we'll hit next), so it's + not clear what cache policy would be effective. Best to record some + data on how many delta applications a given cache would avoid before + implementing it. + +- delta cache: + As people update, we're going to be recomputing text deltas for the + most recently changed files pretty often. It might be worthwhile to + cache the deltas for a little while. + +- Handle Unicode canonicalization for directory and property names + ourselves. People should be able to hand us any valid UTF-8 + sequence, perhaps with precomposed characters or non-spacing marks + in a non-canonical order, and find the appropriate matches, given + the rules defined by the Unicode standard. + +Keeping repositories alive in the long term: Berkeley DB is infamous +for changing its file format from one revision to the next. If someone +saves a Subversion 1.0 repository on a CD somewhere, and then tries to +read it seven years later, their chance of being able to read it with +the latest revision of Subversion is nil. The solution: + +- Define a simply XML repository dump format for the complete + repository data. This should be the same format we use for CVS + repository conversion. We'll have an import function. + +- Write a program that is simple and self-contained --- does not use + Berkeley DB, no fancy XML tools, uses nothing but POSIX read and + seek --- that can dump a Subversion repository in that format. + +- For each revision of Subversion, make a sample repository, and + archive a copy of it away as test data. + +- Write a test suite that verifies that the repository dump program + can handle all of the archived formats. diff --git a/subversion/libsvn_fs_base/notes/fs-history b/subversion/libsvn_fs_base/notes/fs-history new file mode 100644 index 0000000..e4f51a6 --- /dev/null +++ b/subversion/libsvn_fs_base/notes/fs-history @@ -0,0 +1,270 @@ + -*- text -*- + + Subversion Filesystem History + (a love song for libsvn_fs, by C. Michael Pilato) + + +The Subversion filesystem can be your best friend, or your worst +enemy, usually depending on which side of the public API you are +working on. Callers of the libsvn_fs interfaces do their work in a +world pleasantly addressed by roots (the name given to a revision or +transaction snapshot of the versioned directory tree) and paths under +those roots. But once you swim beneath the surface, you quickly +realize that there is a beast both beautiful and dangerous lying in +wait. What looks to the outside world as a sort of coordinate system +with axes for "Time" and "Location" is, in fact, a complicated DAG +subsystem, with nodes that represent revisions of versioned locations +all interconnected in various relationships with each other. + +The goal of this document is straightforward: to relay knowledge about +how to untangle that DAG subsystem -- knowledge which likely lives +only in the minds of a few developers -- so that the Few might become +the Many. + + + +Node-Revisions: The Nodes of the DAG + +When working outside the filesystem API, people generally talk about +their versioned resources in terms of the paths of those resources, +and the global revisions (or revisions-to-be) in which those paths +are located. But inside the filesystem, paths are broken down and +stored as a hierarchical linked-list of path components. Each of +these path components has its own historical lineage (because +Subversion versions directories, too!), and each revision of that +lineage is referred to as a "node-revision". It is these +node-revisions which are the nodes of the DAG subsystem, or "DAG +nodes". + +DAG nodes are identified by unique keys called "node-revision IDs", +and are inter-connected in a variety of ways. A DAG node that +represents a directory stores information about which other DAG nodes +represent the children of that directory. A DAG node contains +information about which other DAG node is its historical predecessor. +By tracing these links from node to node, we can effectively traverse +both space and time, both the geography and the chronology of the +filesystem landscape. + +For example, the path "/trunk/src/main.c" in revision 4 of the +filesystem consumes four DAG nodes: one for "/", one for "/trunk", one +for "/trunk/src", and one for "/trunk/src/main.c". The DAG node for +"/" contains a list of the names and node-revision IDs of its +children, among which is the node-revision ID for the child named +"trunk". Similar links are found in "/trunk" (for "src") and +"/trunk/src" (for "main.c"). Additionally, if these paths existed in +different forms in previous revisions of the filesystem, their DAG +nodes would store the node-revision IDs of their respective +predecessor nodes. + +Whenever someone uses the public API to query for information about a +versioned path under a particular root, the typical course of action +under-the-hood is as follows: + + 1. The root refers to a particular snapshot of the DAG node tree, + and from this we can learn the node-revision ID of the node + which represents the root directory ("/") as it appears in that + snapshot. Given this node-revision ID, it's all DAG from here. + + 2. The path is split into components and traversed, beginning with + the root node, and walking down toward the full path. Each + intermediate node-revision is read, its entries list parsed, and + the next component looked up in that entries list to find the + next node-revision ID along the traversal path. + + 3. Finally, we wind up with a node-revision ID for our original + path. We use it and its associated node-revision to answer the + query. + +Seems pretty easy, doesn't it? Keep reading. + + + +All About Node-Revision IDs + +As previously mentioned, each node-revision in the filesystem has a +unique key, referred to as the node-revision ID. This key is +typically represented as a string which looks like a period-separated +list of its three components: + + 1. node ID: This key is unique to the members of a single + historical lineage. Differing versions of the same versioned + resource, irrespective of the paths and revision in which those + versions are located, all share this ID. If two node-revisions + have different node IDs, their are historically unrelated. + + 2. copy ID: This key uniquely identifies a copy operation, and is + sometimes referred to (or at least thought of) as a "branch ID." + If two node-revisions have the same copy ID, they are said to be + on the same branch. The only exception to this is in the key + "0", a special key that means "not branched". + + 3. txn ID: This key uniquely identifies the Subversion transaction + in which this node-revision came into existence. + +Whenever someone uses the public API to *modify* a versioned resource, +these actions are much the same as those used when querying. But +there are important differences. + + 1. The path is traversed in the same manner is described in the + previous section. The result is an in-memory linked-list of + information about the node-revisions which comprise the + components of the path. + + 2. But before any changes can be made to a path, its node-revision + and those of its parent directories must first be cloned so that + changes to them don't affect previous incarnations of those + node-revisions. This process is called "making the path + mutable". If previous operations under this transaction caused + one or more of the parent directories to be made mutable + already, they are not again cloned. + + 3. Once the path and all its parents are mutable, the desired + changes can be made to the cloned node-revision, and they in no + way affect prior history. + +To clone a node-revision means to literally make a duplicate of it +which is granted its own unique node-revision ID. The new +node-revision ID consists of the same node ID as the node-revision +that was cloned (since this is just another point along the historical +lineage of this versioned resource), a copy ID (which will be +discussed later), and the txn ID in which this modification is +occuring. + +There are some cool things we can read between the lines above. Since +the only time a node-revision comes into existence is when it is brand +new or a fresh clone, and we never do cloning except during a +modification, then we can use the txn ID as a sort of mutability flag. +Mutability of a node-revision is determined by comparing the txn ID of +the node-revision with the ID of the Subversion transaction being used +to modify the filesystem -- if, and only if, they are the same, the node +is allowed to be changed inside that transaction. + +So, we know how txn IDs come into existence now. And the origin of +node IDs hardly warrants its own paragraph: brand new lines of history +(introduced with svn_fs_make_file() and svn_fs_make_dir()) get new +unique node IDs, and every other node-revision that is created simply +keeps the same node ID as the node-revision on which it is based. + +So what about those copy IDs? + +Copy IDs are assigned to nodes-revisions to denote on which "branch" +of a line of history that node-revision resides. (They are called +copy IDs for political reasons, really -- Subversion doesn't expose a +branching API per se, instead promoting the idea that branches are +just forks in the development of a line of history that can just as +easily be represented using path semantics.) New copy IDs are +allocated whenever a branching operation occurs. New node-revisions +can inherit the copy IDs of their predecessors (in the trivial cloning +case), inherit the copy-IDs of one of their parents (by nature of +their parent being copied), or inherit new copy-IDs. In the absence +of any branching, node-revisions are assigned the special copy ID "0". + + + +Copies and Copy IDs + +Currently there are two kinds of copy operation. The first is a +"real" copy, and is the direct result of a call to svn_fs_copy(). +When a real copy is made, the node-revision of the copy source is +cloned, and earns its own brand new unique node-revision ID. This +node-revision ID is constructed from the original node ID, a brand new +copy ID, and (as always) the txn ID of the transaction in which the +copy is being made. + +The Subversion filesystem uses a "cheap copy/lazy migration" model. +This means that when a directory node-revision is copied via +svn_fs_copy(), only the node-revision of the top of the copied "tree" +is cloned (again, earning a new copy ID), not every child of that +tree. This makes the svn_fs_copy() operation quite fast, at least +compared to the alternative. From that point, any children of the +copy target are lazily migrated. The first time they are themselves +modified after the original copy, they are cloned from their original +source location into the new target location. This lazy migration +procedure costs about the same as a regular cloning operation, which +keeps the "cheap copy" cheap, even the morning after. + +Copies of files behave no differently than copies of directories. But +files never have children, so effectively the "tree" being copied is +exactly one node-revision. This node-revision is explicitly cloned at +the time of the copy, and there is nothing to lazily migrate +afterwards. + +The second type of copy operation is a "soft" copy. These types of +copies are not explicitly triggered via the filesystem API, but are +implementation artifacts of other filesystem operations. A soft copy +happens whenever a node-revision exists in a different branch than +that of its parent, and the parent is again branched. Huh?! Let's +see if an example will help explain this a bit. + +Say you have a directory, "/trunk". Now, into "/trunk" you copy a +file "README" from some other part of the tree. You have now +effectively branched the original "README"'s history -- part of it +will live on in the original location, but part of it now thrives in +its new "/trunk/README" location. The copy operation assigned a brand +new copy ID to the new node-revision for "/trunk/README", which is +necessarily different from the copy ID assigned to the node-revision +for "/trunk". + +Later, you decide to copy "/trunk" to "/branches/mine". So the new +"/branches/mine" also gets a brand new copy ID, since it is now a +historical branch of "/trunk". But what happens when +"/branches/mine/README" is cloned later as part of some edits you are +making? What copy ID does the new clone get? Because "/trunk/README" +was on a different historical branch than "/trunk", our copy of +"/trunk" causes (in "README") a branch of a branch. So +"/branches/mine/README" gets a brand new copy ID, and the filesystem +remembers that the copy operation associated with that ID was a soft +copy. + + [### Right about here, C-Mike's memory starts getting fuzzy ###] + +The following is the copy ID inheritance algorithm, used to calculate +what copy ID a node revision will use when cloned for mutability. +Remember that a node revision is never cloned until its parent is +first cloned. + + 1. If the node revision is already mutable, its copy ID never + changes. + + 2. If the node revision has a copy ID of "0" (which is to say, it's + never been itself copied or cloned as a child of a copied + parent), then it inherits whatever copy ID its parent winds up + with. + + 3. If the node revision is on the same branch as its parent before + cloning, it will remain on the same branch as its parent after + cloning. A node revision can be said to be on the same branch + as its parent if: + + a) their copy IDs are the same, or + + b) the node revision is not a branch point (meaning, it was + not the node revision created by the copy associated with + its copy ID), or + + c) the node revision is a branch point which being accessed via + its copy destination path. + + 4. If, however, the node revision is *not* on the same branch as + its parent before cloning, it cannot be on the same branch as + its parent after cloning. This breaks down to two cases: + + a) If the node revision was the target of the copy operation + whose ID it holds, then it gets to keep its same copy ID. + + b) Otherwise, the node revision is the unedited child of some + parent that was copied, and wasn't on the same branch as + that parent before the copy. In this special case, the + cloned node revision will get a brand new copy ID which + points to one of those "soft copy" things we've been + talking about. + +The initial root directory's node revision, created when the +filesystem is initialized, begins life with a magical "0" copy ID. +Afterward, any new nodes (as in, freshly created files and +directories) begin life with the same copy ID as their parent. + + +Traversing History + + ### todo: put the history harvesting algorithm here diff --git a/subversion/libsvn_fs_base/notes/structure b/subversion/libsvn_fs_base/notes/structure new file mode 100644 index 0000000..8e2159f --- /dev/null +++ b/subversion/libsvn_fs_base/notes/structure @@ -0,0 +1,1086 @@ +Subversion on Berkeley DB -*- text -*- + +There are many different ways to implement the Subversion filesystem +interface. You could implement it directly using ordinary POSIX +filesystem operations; you could build it using an SQL server as a +back end; you could build it on RCS; and so on. + +This implementation of the Subversion filesystem interface is built on +top of Berkeley DB (http://www.sleepycat.com). Berkeley DB supports +transactions and recoverability, making it well-suited for Subversion. + + + +Nodes and Node Revisions + +In a Subversion filesystem, a `node' corresponds roughly to an +`inode' in a Unix filesystem: + + * A node is either a file or a directory. + + * A node's contents change over time. + + * When you change a node's contents, it's still the same node; it's + just been changed. So a node's identity isn't bound to a specific + set of contents. + + * If you rename a node, it's still the same node, just under a + different name. So a node's identity isn't bound to a particular + filename. + +A `node revision' refers to a node's contents at a specific point in +time. Changing a node's contents always creates a new revision of that +node. Once created, a node revision's contents never change. + +When we create a node, its initial contents are the initial revision of +the node. As users make changes to the node over time, we create new +revisions of that same node. When a user commits a change that deletes +a file from the filesystem, we don't delete the node, or any revision +of it --- those stick around to allow us to recreate prior revisions of +the filesystem. Instead, we just remove the reference to the node +from the directory. + + + +ID's + +Within the database, we refer to nodes and node revisions using a +string of three unique identifiers (the "node ID", the "copy ID", and +the "txn ID"), separated by periods. + + node_revision_id ::= node_id '.' copy_id '.' txn_id + +The node ID is unique to a particular node in the filesystem across +all of revision history. That is, two node revisions who share +revision history (perhaps because they are different revisions of the +same node, or because one is a copy of the other, e.g.) have the same +node ID, whereas two node revisions who have no common revision +history will not have the same node ID. + +The copy ID is a key into the `copies' table (see `Copies' below), and +identifies that a given node revision, or one of its ancestors, +resulted from a unique filesystem copy operation. + +The txn ID is just an identifier that is unique to a single filesystem +commit. All node revisions created as part of a commit share this txn +ID (which, incidentally, gets its name from the fact that this id is +the same id used as the primary key of Subversion transactions; see +`Transactions' below). + +A directory entry identifies the file or subdirectory it refers to +using a node revision ID --- not a node ID. This means that a change +to a file far down in a directory hierarchy requires the parent +directory of the changed node to be updated, to hold the new node +revision ID. Now, since that parent directory has changed, its parent +needs to be updated, and so on to the root. We call this process +"bubble-up". + +If a particular subtree was unaffected by a given commit, the node +revision ID that appears in its parent will be unchanged. When +doing an update, we can notice this, and ignore that entire +subtree. This makes it efficient to find localized changes in +large trees. + + + +A Word About Keys + +Some of the Subversion database tables use base-36 numbers as their +keys. Some debate exists about whether the use of base-36 (as opposed +to, say, regular decimal values) is either necessary or good. It is +outside the scope of this document to make a claim for or against this +usage. As such, the reader will please note that for the majority of +the document, the use of the term "number" when referring to keys of +database tables should be interpreted to mean "a monotonically +increasing unique key whose order with respect to other keys in the +table is irrelevant". :-) + +To determine the actual type currently in use for the keys of a given +table, you are invited to check out the "Appendix: Filesystem +structure summary" section of this document. + + + +NODE-REVISION: how we represent a node revision + +We represent a given revision of a file or directory node using a list +skel (see include/private/svn_skel.h for an explanation of skels). +A node revision skel has the form: + + (HEADER PROP-KEY KIND-SPECIFIC ...) + +where HEADER is a header skel, whose structure is common to all nodes, +PROP-KEY is the key of the representation that contains this node's +properties list, and the KIND-SPECIFIC elements carry data dependent +on what kind of node this is --- file, directory, etc. + +HEADER has the form: + + (KIND CREATED-PATH [PRED-ID [PRED-COUNT [HAS-MERGEINFO MERGEINFO-COUNT]]]) + +where: + + * KIND indicates what sort of node this is. It must be one of the + following: + - "file", indicating that the node is a file (see FILE below). + - "dir", indicating that the node is a directory (see DIR below). + + * CREATED-PATH is the canonicalized absolute filesystem path at + which this node was created. + + * PRED-ID, if present, indicates the node revision which is the + immediate ancestor of this node. + + * PRED-COUNT, if present, indicates the number of predecessors the + node revision has (recursively). + + * HAS-MERGEINFO and MERGEINFO-COUNT, if present, indicate ... + ### TODO + +Note that a node cannot change its kind from one revision to the next. +A directory node is always a directory; a file node is always a file; +etc. The fact that the node's kind is stored in each node revision, +rather than in some revision-independent place, might suggest that +it's possible for a node to change kinds from revision to revision, but +Subversion does not allow this. + +PROP-KEY is a key into the `representations' table (see REPRESENTATIONS +below), whose value is a representation pointing to a string +(see `strings' table) that is a PROPLIST skel. + +The KIND-SPECIFIC portions are discussed below. + + + +PROPLIST: a property list is a list skel of the form: + + (NAME1 VALUE1 NAME2 VALUE2 ...) + +where each NAMEi is the name of a property, and VALUEi is the value of +the property named NAMEi. Every valid property list has an even +number of elements. + + + +FILE: how files are represented. + +If a NODE-REVISION's header's KIND is "file", then the node-revision +skel represents a file, and has the form: + + (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY]) + +where + + DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID) + +and DATA-KEY identifies the representation for the file's current +contents, and EDIT-DATA-KEY identifies the representation currently +available for receiving new contents for the file. + +DATA-KEY-UNIQID ... +### TODO + +See discussion of representations later. + + + +DIR: how directories are represented. + +If the header's KIND is "dir", then the node-revision skel +represents a directory, and has the form: + + (HEADER PROP-KEY ENTRIES-KEY) + +where ENTRIES-KEY identifies the representation for the directory's +entries list (see discussion of representations later). An entries +list has the form + + (ENTRY ...) + +where each entry is + + (NAME ID) + +where: + + * NAME is the name of the directory entry, in UTF-8, and + + * ID is the ID of the node revision to which this entry refers + + + +REPRESENTATIONS: where and how Subversion stores your data. + +Some parts of a node revision are essentially constant-length: for +example, the KIND field and the REV. Other parts can have +arbitrarily varying length: property lists, file contents, and +directory entry lists. This variable-length data is often similar +from one revision to the next, so Subversion stores just the deltas +between them, instead of successive fulltexts. + +The HEADER portion of a node revision holds the constant-length stuff, +which is never deltified. The rest of a node revision just points to +data stored outside the node revision proper. This design makes the +repository code easier to maintain, because deltification and +undeltification are confined to a layer separate from node revisions, +and makes the code more efficient, because Subversion can retrieve +just the parts of a node it needs for a given operation. + +Deltifiable data is stored in the `strings' table, as mediated by the +`representations' table. Here's how it works: + +The `strings' table stores only raw bytes. A given string could be +any one of these: + + - a file's contents + - a delta that reconstructs file contents, or part of a file's contents + - a directory entry list skel + - a delta that reconstructs a dir entry list skel, or part of same + - a property list skel + - a delta that reconstructs a property list skel, or part of same + +There is no way to tell, from looking at a string, what kind of data +it is. A directory entry list skel is indistinguishable from file +contents that just happen to look exactly like the unparsed form of a +directory entry list skel. File contents that just happen to look +like svndiff data are indistinguishable from delta data. + +The code is able to interpret a given string because Subversion + + a) knows whether to be looking for a property list or some + kind-specific data, + + b) knows the `kind' of the node revision in question, + + c) always goes through the `representations' table to discover if + any undeltification or other transformation is needed. + +The `representations' table is an intermediary between node revisions +and strings. Node revisions never refer directly into the `strings' +table; instead, they always refer into the `representations' table, +which knows whether a given string is a fulltext or a delta, and if it +is a delta, what it is a delta against. That, combined with the +knowledge in (a) and (b) above, allows Subversion to retrieve the data +and parse it appropriately. A representation has the form: + + (HEADER KIND-SPECIFIC) + +where HEADER is + + (KIND TXN [MD5 [SHA1]]) + +The KIND is "fulltext" or "delta". TXN is the txn ID for the txn in +which this representation was created. MD5 is a checksum of the +representation's contents, that is, what the representation produces, +regardless of whether it is stored deltified or as fulltext. (For +compatibility with older versions of Subversion, MD5 may be +absent, in which case the filesystem behaves as though the checksum is +there and is correct.) An additional kind of checksum, SHA1, is present +in newer formats, starting with version ... +### TODO + +The TXN also serves as a kind of mutability flag: if txn T tries to +change a representation's contents, but the rep's TXN is not T, then +something has gone horribly wrong and T should leave the rep alone +(and probably error). Of course, "change a representation" here means +changing what the rep's consumer sees. Switching a representation's +storage strategy, for example from fulltext to deltified, wouldn't +count as a change, since that wouldn't affect what the rep produces. + +KIND-SPECIFIC varies considerably depending on the kind of +representation. Here are the two forms currently recognized: + + (("fulltext" TXN [MD5 [SHA1]]) STRING-KEY) + The data is at STRING-KEY in the `strings' table. + + (("delta" TXN [MD5 [SHA1]]) (OFFSET WINDOW) ...) + Each OFFSET indicates the point in the fulltext that this + element reconstructs, and WINDOW says how to reconstruct it: + + WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ; + DIFF ::= ("svndiff" VERSION STRING-KEY) + + Notice that a WINDOW holds only metadata. REP-KEY says what + the window should be applied against, or none if this is a + self-compressed delta; SIZE says how much data this window + reconstructs; VERSION says what version of the svndiff format + is being used (currently only version 0 is supported); and + STRING-KEY says which string contains the actual svndiff data + (there is no diff data held directly in the representations + table, of course). + + Note also that REP-KEY might refer to a representation that + itself requires undeltification. We use a delta combiner to + combine all the deltas needed to reproduce the fulltext from + some stored plaintext. + + Branko says this is what REP-OFFSET is for: + > The offsets embedded in the svndiff are stored in a string; + > these offsets would be in the representation. The point is that + > you get all the information you need to select the appropriate + > windows from the rep skel -- without touching a single + > string. This means a bit more space used in the repository, but + > lots less memory used on the server. + + We'll see if it turns out to be necessary. + +In the future, there may be other representations, for example +indicating that the text is stored elsewhere in the database, or +perhaps in an ordinary Unix file. + +Let's work through an example node revision: + + (("file" REV COUNT) PROP-KEY "2345") + +The entry for key "2345" in `representations' is: + + (("delta" TXN CHECKSUM) (0 (("svndiff" 0 "1729") 65 "2343"))) + +and the entry for key "2343" in `representations' is: + + (("fulltext" TXN CHECKSUM) "1001") + +while the entry for key "1729" in `strings' is: + + <some unprintable glob of svndiff data> + +which, when applied to the fulltext at key "1001" in strings, results +in this new fulltext: + + "((some text) (that looks) (deceptively like) (directory entries))" + +Et voila! Subversion knew enough, via the `representations' and +`strings' tables, to undeltify and get that fulltext; and knew enough, +because of the node revision's "file" type, to interpret the result as +file contents, not as a directory entry list. + +(Note that the `strings' table stores multiple DB values per key. +That is, although it's accurate to say there is one string per key, +the string may be divided into multiple consecutive blocks, all +sharing that key. You use a Berkeley DB cursor to find the desired +value[s], when retrieving a particular offset+len in a string.) + +Representations know nothing about ancestry -- the `representations' +table never refers to node revision id's, only to strings or to other +representations. In other words, while the `nodes' table allows +recovery of ancestry information, the `representations' and `strings' +tables together handle deltification and undeltification +*independently* of ancestry. At present, Subversion generally stores +the youngest strings in "fulltext" form, and older strings as "delta"s +against them (unless the delta would save no space compared to the +fulltext). However, there's nothing magic about that particular +arrangement. Other interesting alternatives: + + * We could store the N most recently accessed strings as fulltexts, + letting access patterns determine the most appropriate + representation for each revision. + + * We could occasionally store deltas against the N'th younger + revision, storing larger jumps with a frequency inverse to the + distance covered, yielding a tree-structured history. + +Since the filesystem interface doesn't expose these details, we can +change the representation pretty much as we please to optimize +whatever parameter we care about --- storage size, speed, robustness, +etc. + +Representations never share strings - every string is referred to by +exactly one representation. This is so that when we change a +representation to a different form (e.g. during deltification), we can +delete the strings containing the old form, and know that we're not +messing up any other reps by doing so. + + +Further Notes On Deltifying: +---------------------------- + +When a representation is deltified, it is changed in place. +New strings are created containing the new delta, the representation +is changed to refer to the new strings, and the original (usually +fulltext) string or strings are deleted. + +The node revisions referring to that representation will not be +changed; instead, the same rep key will now be associated with +different value. That way, we get reader locking for free: if someone +is reading a file while Subversion is deltifying that file, one of the +two sides will get a DB_DEADLOCK and svn_fs__retry_txn() will retry. + +### todo: add a note about cycle-checking here, too. + + + +The Berkeley DB "nodes" table + +The database contains a table called "nodes", which is a btree indexed +by node revision ID's, mapping them onto REPRESENTATION skels. Node 0 +is always the root directory, and node revision ID 0.0.0 is always the +empty directory. We use the value of the key 'next-key' to indicate +the next unused node ID. + +Assuming that we store the most recent revision on every branch as +fulltext, and all other revisions as deltas, we can retrieve any node +revision by searching for the last revision of the node, and then +walking backwards to specific revision we desire, applying deltas as +we go. + + + +REVISION: filesystem revisions, and the Berkeley DB "revisions" table + +We represent a filesystem revision using a skel of the form: + + ("revision" TXN) + +where TXN is the key into the `transactions' table (see 'Transactions' below) +whose value is the transaction that was committed to create this revision. + +The database contains a table called "revisions", which is a +record-number table mapping revision numbers onto REVISION skels. +Since Berkeley DB record numbers start with 1, whereas Subversion +filesystem revision numbers start at zero, revision V is stored as +record number V+1 in the `revisions' table. Filesystem revision zero +always has node revision 0.0.0 as its root directory; that node +revision is guaranteed to be an empty directory. + + + +Transactions + +Every transaction ends when it is either successfully committed, or +aborted. We call a transaction which has been either committed or +aborted "finished", and one which hasn't "unfinished". + +Transactions are identified by unique numbers, called transaction +ID's. Currently, transaction ID's are never reused, though this is +not mandated by the schema. In the database, we always represent a +transaction ID in its shortest ASCII form. + +The Berkeley DB `transactions' table records both unfinished and +committed transactions. Every key in this table is a transaction ID. +Unfinished transactions have values that are skels of one of the +following forms: + + ("transaction" ROOT-ID BASE-ID PROPLIST COPIES) + ("dead" ROOT-ID BASE-ID PROPLIST COPIES) + +where: + + * ROOT-ID is the node revision ID of the transaction's root + directory. This is of the form 0.0.THIS-TXN-ID. + + * BASE-ID is the node revision ID of the root of the transaction's + base revision. This is of the form 0.0.BASE-TXN-ID - the base + transaction is, of course, the transaction of the base revision. + + * PROPLIST is a skel giving the revision properties for the + transaction. + + * COPIES contains a list of keys into the `copies' table, + referencing all the filesystem copies created inside of this + transaction. If the transaction is aborted, these copies get + removed from the `copies' table. + + * A "dead" transaction is one that has been requested to be + destroyed, and should never, ever, be committed. + +Committed transaction, however, have values that are skels of the form: + + ("committed" ROOT-ID REV PROPLIST COPIES) + +where: + + * ROOT-ID is the node revision ID of the committed transaction's (or + revision's) root node. + + * REV represents the revision that was created when the + transaction was committed. + + * PROPLIST is a skel giving the revision properties for the + committed transaction. + + * COPIES contains a list of keys into the `copies' table, + referencing all the filesystem copies created by this committed + transaction. Nothing currently uses this information for + committed transactions, but it could be useful in the future. + +As the sole exception to the rule above, the `transactions' table +always has one entry whose key is `next-key', and whose value is the +lowest transaction ID that has never yet been used. We use this entry +to allocate ID's for new transactions. + +The `transactions' table is a btree, with no particular sort order. + + + +Changes + +As modifications are made (files and dirs added or removed, text and +properties changed, etc.) on Subversion transaction trees, the +filesystem tracks the basic change made in the Berkeley DB `changes' +table. + +The `changes' table is a btree with Berkeley's "duplicate keys" +functionality (and with no particular sort order), and maps the +one-to-many relationship of a transaction ID to a "change" item. +Change items are skels of the form: + + ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD) + +where: + + * PATH is the path that was operated on to enact this change. + + * ID is the node revision ID of the node changed. The precise + meaning varies based on the kind of the change: + - "add" or "modify": a new node revision created in the current + txn. + - "delete": a node revision from a previous txn. + - "replace": a replace operation actually acts on two node + revisions, one being deleted, one being added. Only the added + node-revision ID is recorded in the `changes' table - this is + a design flaw. + - "reset": no node revision applies. A zero atom is used as a + placeholder. + + * CHANGE-KIND is one of the following: + + - "add" : PATH/ID was added to the filesystem. + - "delete" : PATH/ID was removed from the filesystem. + - "replace" : PATH/ID was removed, then re-added to the filesystem. + - "modify" : PATH/ID was otherwise modified. + - "reset" : Ignore any previous changes for PATH/ID in this txn. + This kind is no longer created by Subversion 1.3.0 + and later, and can probably be removed at the next + schema bump. + + * TEXT-MOD is a bit specifying whether or not the contents of + this node was modified. + + * PROP-MOD is a bit specifying whether or not the properties of + this node where modified. + +In order to fully describe the changes made to any given path as part +of a single transaction, one must read all the change items associated +with the transaction's ID, and "collapse" multiple entries that refer +to that path. + + + +Copies + +Each time a filesystem copy operation is performed, Subversion records +meta-data about that copy. + +Copies are identified by unique numbers called copy ID's. Currently, +copy ID's are never reused, though this is not mandated by the schema. +In the database, we always represent a copy ID in its shortest ASCII +form. + +The Berkeley DB `copies' table records all filesystem copies. Every +key in this table is copy ID, and every value is a skel of one of the +following forms: + + ("copy" SRC-PATH SRC-TXN DST-NODE-ID) + ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID) + +where: + + * "copy" indicates an explicitly requested copy, and "soft-copy" + indicates a node that was cloned internally as part of an + explicitly requested copy of some parent directory. See the + section "Copies and Copy IDs" in the file <fs-history> for + details. + + * SRC-PATH and SRC-TXN are the canonicalized absolute path and + transaction ID, respectively, of the source of the copy. + + * DST-NODE-ID represents the new node revision created as a result + of the copy. + +As the sole exception to the rule above, the `copies' table always has +one entry whose key is `next-key', and whose value is the lowest copy ID +that has never yet been used. We use this entry to allocate new +copy ID's. + +The `copies' table is a btree, with no particular sort order. + + + +Locks + +When a caller locks a file -- reserving an exclusive right to modify +or delete it -- an lock object is created in a `locks' table. + +The `locks' table is a btree whose key is a UUID string known as +a "lock-token", and whose value is a skel representing a lock. The +fields in the skel mirror those of an svn_lock__t (see svn_types.h): + + ("lock" PATH TOKEN OWNER COMMENT XML-P CREATION-DATE EXPIRATION-DATE) + +where: + + * PATH is the absolute filesystem path reserved by the lock. + + * TOKEN is the universally unique identifier of the lock, known + as the lock-token. This is the same as the row's key. + + * OWNER is the authenticated username that "owns" the lock. + + * COMMENT is a string describing the lock. It may be empty, or it + might describe the rationale for locking. + + * XML-P is a boolean (either 0 or 1) indicating whether the COMMENT + field is wrapped in an XML tag. (This is something only used by + the DAV layer, for webdav interoperabliity.) + + * CREATION-DATE is a string representation of the date/time when + the lock was created. (see svn_time_to_cstring()) + + * EXPIRATION-DATE is a string representation of the date/time when + the lock will cease to be valid. (see svn_time_to_cstring()) + +In addition to creating a lock in the `locks' table, a new row is +created in a `lock-tokens' table. The `lock-tokens' table is a btree +whose key is an absolute path in the filesystem. The value of each +key is a lock-token (which is a key into the `locks' table.) + +To test if a path is locked, simply check if the path is a key in the +`lock-tokens' table. To see if a certain directory has any locked +children below, we ask BerkeleyDB to do a "greater or equal match" on +the directory path, and see if any results come back. If they do, +then at least one of the directory's children is locked, and thus the +directory cannot be deleted without further investigation. + +Locks are ephemeral things, not historied in any way. They are +potentially created and deleted quite often. When a lock is +destroyed, the appropriate row is removed from the `locks' table. +Additionally, the locked-path is removed from the `lock-tokens' table. + + + + + +Merge rules + +The Subversion filesystem must provide the following characteristics: + +- clients can submit arbitrary rearrangements of the tree, to be + performed as atomic changes to the filesystem tree +- multiple clients can submit non-overlapping changes at the same time, + without blocking +- readers must never block other readers or writers +- writers must never block readers +- writers may block writers + +Merging rules: + + The general principle: a series of changes can be merged iff the + final outcome is independent of the order you apply them in. + +Merging algorithm: + + For each entry NAME in the directory ANCESTOR: + + Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of + the name within ANCESTOR, SOURCE, and TARGET respectively. + (Possibly null if NAME does not exist in SOURCE or TARGET.) + + If ANCESTOR-ENTRY == SOURCE-ENTRY, then: + No changes were made to this entry while the transaction was in + progress, so do nothing to the target. + + Else if ANCESTOR-ENTRY == TARGET-ENTRY, then: + A change was made to this entry while the transaction was in + process, but the transaction did not touch this entry. Replace + TARGET-ENTRY with SOURCE-ENTRY. + + Else: + Changes were made to this entry both within the transaction and + to the repository while the transaction was in progress. They + must be merged or declared to be in conflict. + + If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a + double delete; if one of them is null, that's a delete versus + a modification. In any of these cases, flag a conflict. + + If any of the three entries is of type file, declare a conflict. + + If either SOURCE-ENTRY or TARGET-ENTRY is not a direct + modification of ANCESTOR-ENTRY (determine by comparing the + node-id fields), declare a conflict. A replacement is + incompatible with a modification or other replacement--even + an identical replacement. + + Direct modifications were made to the directory ANCESTOR-ENTRY + in both SOURCE and TARGET. Recursively merge these + modifications. + + For each leftover entry NAME in the directory SOURCE: + + If NAME exists in TARGET, declare a conflict. Even if SOURCE and + TARGET are adding exactly the same thing, two additions are not + auto-mergeable with each other. + + Add NAME to TARGET with the entry from SOURCE. + + Now that we are done merging the changes from SOURCE into the + directory TARGET, update TARGET's predecessor to be SOURCE. + +The following algorithm was used when the Subversion filesystem was +initially written, but has been replaced with the simpler and more +performant algorithm above: + + Merging two nodes, A and B, with respect to a common ancestor + ANCESTOR: + + - First, the merge fails unless A, B, and ANCESTOR are all the same + kind of node. + - If A and B are text files: + - If A is an ancestor of B, then B is the merged result. + - If A is identical to B, then B (arbitrarily) is the merged + result. + - Otherwise, the merge fails. + - If A and B are both directories: + - For every directory entry E in either A, B, or ANCESTOR, here + are the cases: + - E exists in neither ANCESTOR nor A. + - E doesn't exist in ANCESTOR, and has been added to A. + - E exists in ANCESTOR, but has been deleted from A. + - E exists in both ANCESTOR and A ... + - but refers to different nodes. + - but refers to different revisions of the same node. + - and refers to the same node revision. + + The same set of possible relationships with ANCESTOR holds for B, + so there are thirty-six combinations. The matrix is symmetrical + with A and B reversed, so we only have to describe one triangular + half, including the diagonal --- 21 combinations. + + - (6) E exists in neither ANCESTOR nor A: + - (1) E exists in neither ANCESTOR nor B. Can't occur, by + assumption that E exists in either A, B, or ancestor. + - (1) E has been added to B. Add E in the merged result. *** + - (1) E has been deleted from B. Can't occur, by assumption + that E doesn't exist in ANCESTOR. + - (3) E exists in both ANCESTOR and B. Can't occur, by + assumption that E doesn't exist in ancestor. + - (5) E doesn't exist in ANCESTOR, and has been added to A. + - (1) E doesn't exist in ANCESTOR, and has been added to B. + Conflict. + - (1) E exists in ANCESTOR, but has been deleted from B. + Can't occur, by assumption that E doesn't exist in + ANCESTOR. + - (3) E exists in both ANCESTOR and B. Can't occur, by + assumption that E doesn't exist in ANCESTOR. + - (4) E exists in ANCESTOR, but has been deleted from A. + - (1) E exists in ANCESTOR, but has been deleted from B. If + neither delete was a result of a rename, then omit E from + the merged tree. *** Otherwise, conflict. + - E exists in both ANCESTOR and B ... + - (1) but refers to different nodes. Conflict. + - (1) but refers to different revisions of the same node. + Conflict. + - (1) and refers to the same node revision. Omit E from + the merged tree. *** + - (3) E exists in both ANCESTOR and A, but refers to different + nodes. + - (1) E exists in both ANCESTOR and B, but refers to + different nodes. Conflict. + - (1) E exists in both ANCESTOR and B, but refers to + different revisions of the same node. Conflict. + - (1) E exists in both ANCESTOR and B, and refers to the same + node revision. Replace E with A's node revision. *** + - (2) E exists in both ANCESTOR and A, but refers to different + revisions of the same node. + - (1) E exists in both ANCESTOR and B, but refers to + different revisions of the same node. Try to merge A/E and + B/E, recursively. *** + - (1) E exists in both ANCESTOR and B, and refers to the same + node revision. Replace E with A's node revision. *** + - (1) E exists in both ANCESTOR and A, and refers to the same + node revision. + - (1) E exists in both ANCESTOR and B, and refers to the same + node revision. Nothing has happened to ANCESTOR/E, so no + change is necessary. + + *** == something actually happens + + +Non-Historical Properties + +[[Yes, do tell.]] + + +UUIDs: Universally Unique Identifiers + +Every filesystem has a UUID. This is represented as record #1 in the +`uuids' table. + + +Layers + +In previous structurings of the code, I had trouble keeping track of +exactly who has implemented which promises, based on which other +promises from whom. + +I hope the arrangement below will help me keep things straight, and +make the code more reliable. The files are arranged in order from +low-level to high-level: each file depends only on services provided +by the files before it. + +skel.c, id.c, dbt.c, convert-size.c + + Low-level utility functions. + +fs_skels.c Routines for marshaling between skels and native FS types. + +fs.c Creating and destroying filesystem objects. + +err.c Error handling. + +nodes-table.c, txn-table.c, rev-table.c, reps-table.c, strings-table.c + + Create and open particular database tables. + Responsible for intra-record consistency. + +node-rev.c Creating, reading, and writing node revisions. + Responsible for deciding what gets deltified when. + +reps-strings.c + Retrieval and storage of represented strings. + This will handle delta-based storage, + +dag.c Operations on the DAG filesystem. "DAG" because the + interface exposes the filesystem's sharing structure. + Enforce inter-record consistency. + +tree.c Operations on the tree filesystem. This layer is + built on top of dag.c, but transparently distinguishes + virtual copies, making the underlying DAG look like a + real tree. This makes incomplete transactions behave + like ordinary mutable filesystems. + +delta.c Computing deltas. + + + +Appendix: Filesystem structure summary +====================================== + +Berkeley DB tables +------------------ + + "nodes" : btree(ID -> NODE-REVISION, "next-key" -> NODE-ID) + "revisions" : recno(REVISION) + "transactions" : btree(TXN -> TRANSACTION, "next-key" -> TXN) + "changes" : btree(TXN -> CHANGE) + "copies" : btree(CPY -> COPY, "next-key" -> CPY) + "strings" : btree(STR -> STRING, "next-key" -> STR) + "representations" : btree(REP -> REPRESENTATION, "next-key" -> REP) + "uuids" : recno(UUID) + "locks" : btree(TOKEN -> LOCK) + "lock-tokens" : btree(PATH -> TOKEN) + "node-origins" : btree(NODE-ID -> ID) + "checksum-reps" : btree(SHA1SUM -> REP, "next-key" -> number-36) + "miscellaneous" : btree(STRING -> STRING) + +Syntactic elements +------------------ + +Table keys: + + ID ::= NODE-REV-ID ; + TXN ::= number-36 ; + CPY ::= number-36 ; + STR ::= number-36 ; + REP ::= number-36 ; + TOKEN ::= uuid ; + +Property lists: + + PROPLIST ::= (PROP ...) ; + PROP ::= atom atom ; + + +Filesystem revisions: + + REVISION ::= ("revision" TXN) ; + + +Transactions: + + TRANSACTION ::= UNFINISHED-TXN | COMMITTED-TXN | DEAD-TXN + UNFINISHED-TXN ::= ("transaction" ROOT-ID BASE-ID PROPLIST COPIES) ; + COMMITTED-TXN ::= ("committed" ROOT-ID REV PROPLIST COPIES) ; + DEAD-TXN ::= ("dead" ROOT-ID BASE-ID PROPLIST COPIES) ; + ROOT-ID ::= NODE-REV-ID ; + BASE-ID ::= NODE-REV-ID ; + COPIES ::= (CPY ...) ; + REV ::= number ; + + +Changes: + + CHANGE ::= ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD) ; + CHANGE-KIND ::= "add" | "delete" | "replace" | "modify" | "reset"; + TEXT-MOD ::= atom ; + PROP-MOD ::= atom ; + + +Copies: + + COPY ::= REAL-COPY | SOFT-COPY + REAL-COPY ::= ("copy" SRC-PATH SRC-TXN DST-NODE-ID) + SOFT-COPY ::= ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID) + SRC-PATH ::= atom ; + SRC-TXN ::= TXN ; + DST-NODE-ID ::= NODE-REV-ID ; + + +Entries lists: + + ENTRIES ::= (ENTRY ...) ; + ENTRY ::= (NAME ID) ; + NAME ::= atom ; + + +Node revisions: + + NODE-REVISION ::= FILE | DIR ; + FILE ::= (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY]) ; + DIR ::= (HEADER PROP-KEY ENTRIES-KEY) ; + HEADER ::= (KIND CREATED-PATH + [PRED-ID [PRED-COUNT + [HAS-MERGEINFO MERGEINFO-COUNT]]]) ; + KIND ::= "file" | "dir" ; + PRED-ID ::= NODE-REV-ID | ""; + PRED-COUNT ::= number | "" ; + CREATED-PATH ::= atom ; + PROP-KEY ::= atom ; + DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID) + DATA-KEY ::= atom ; + DATA-KEY-UNIQID ::= atom ; + EDIT-DATA-KEY ::= atom ; + HAS-MERGEINFO ::= "0" | "1" ; + MERGEINFO-COUNT ::= number ; + + +Representations: + + REPRESENTATION ::= FULLTEXT | DELTA ; + FULLTEXT ::= (HEADER STRING-KEY) ; + DELTA ::= (HEADER (OFFSET WINDOW) ...) ; + WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ; + DIFF ::= ("svndiff" VERSION STRING-KEY) ; + VERSION ::= number ; + REP-KEY ::= atom ; + STRING-KEY ::= atom ; + OFFSET ::= number ; + REP-OFFSET ::= number ; + + HEADER ::= (KIND TXN [MD5 [SHA1]]) ; + KIND ::= "fulltext" | "delta" ; + + SIZE ::= number ; + MD5 ::= ("md5" MD5SUM) ; + SHA1 ::= ("sha1" SHA1SUM) ; + MD5SUM ::= atom ; + SHA1SUM ::= atom ; + + +Strings: + + STRING ::= RAWTEXT | LISTTEXT | DIFFTEXT + RAWTEXT ::= /{anything.class}*/ ; + LISTTEXT ::= list ; + DIFFTEXT ::= /{anything.class}*/ ; + + +Node revision IDs: + + NODE-REV-ID ::= NODE-ID '.' CPY '.' TXN ; + NODE-ID ::= number ; + +UUIDs: + UUID ::= uuid ; + + +Locks: + + LOCK ::= ("lock" PATH TOKEN OWNER + COMMENT XML-P CR-DATE [X-DATE]); + PATH ::= atom ; + OWNER ::= atom ; + COMMENT ::= atom ; + XML-P ::= "0" | "1" ; + CR-DATE ::= atom ; + X-DATE ::= atom ; + +Lock tokens: + + (the value is just a lock-token, which is a uuid) + + +Node origins: + + NODE-ID ::= NODE-REV-ID ; + + +Lexical elements +---------------- + +UUIDs: + + uuid ::= hexits-32 '-' hexits-16 '-' hexits-16 '-' + hexits-16 '-' hexits-48 ; + +Numbers: + + number ::= /{digit.class}+/ ; + number-36 ::= /{base36.class}+/ ; + hexits-32 ::= /{base16.class}{8}/ ; + hexits-16 ::= /{base16.class}{4}/ ; + hexits-48 ::= /{base16.class}{12}/ ; + +(Note: the following are described in skel.h) +Skels: + + skel ::= atom | list; + list ::= list.head list.body.opt list.tail ; + atom ::= atom.imp-len | atom.exp-len ; + + list.head ::= '(' spaces.opt ; + list.tail ::= spaces.opt ')' ; + list.body.opt ::= | list.body ; + list.body ::= skel | list.body spaces.opt skel ; + + atom.imp-len ::= /{name.class}[^\(\){ws.class}]*/ ; + atom.exp-len ::= /({digit.class}+){ws.class}.{\1}/ ; + + spaces.opt ::= /{ws.class}*/ ; + + +Character classes: + + ws.class ::= [\t\n\f\r\ ] ; + digit.class ::= [0-9] ; + name.class ::= [A-Za-z] ; + base16.class ::= [0-9a-f] + base36.class ::= [a-z0-9] + anything.class ::= anything at all ; + + + +Appendix: 'miscellaneous' table contents +====================================== + +The 'miscellaneous' table contains string keys mapped to string +values. Here is a table of the supported keys, the descriptions of +their values, and the filesystem format version in which they were +introduced. + + Fmt Key Value + --- ------------------ ------------------------------------ + 4 forward-delta-rev Youngest revision in the repository as of + the moment when it was upgraded to support + forward deltas. diff --git a/subversion/libsvn_fs_base/reps-strings.c b/subversion/libsvn_fs_base/reps-strings.c new file mode 100644 index 0000000..553075d --- /dev/null +++ b/subversion/libsvn_fs_base/reps-strings.c @@ -0,0 +1,1617 @@ +/* reps-strings.c : intepreting representations with respect to strings + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <assert.h> + +#include "svn_fs.h" +#include "svn_pools.h" + +#include "fs.h" +#include "err.h" +#include "trail.h" +#include "reps-strings.h" + +#include "bdb/reps-table.h" +#include "bdb/strings-table.h" + +#include "../libsvn_fs/fs-loader.h" +#define SVN_WANT_BDB +#include "svn_private_config.h" + + +/*** Helper Functions ***/ + + +/* Return non-zero iff REP is mutable under transaction TXN_ID. */ +static svn_boolean_t rep_is_mutable(representation_t *rep, + const char *txn_id) +{ + if ((! rep->txn_id) || (strcmp(rep->txn_id, txn_id) != 0)) + return FALSE; + return TRUE; +} + +/* Helper macro that evaluates to an error message indicating that + the representation referred to by X has an unknown node kind. */ +#define UNKNOWN_NODE_KIND(x) \ + svn_error_createf \ + (SVN_ERR_FS_CORRUPT, NULL, \ + _("Unknown node kind for representation '%s'"), x) + +/* Return a `fulltext' representation, allocated in POOL, which + * references the string STR_KEY. + * + * If TXN_ID is non-zero and non-NULL, make the representation mutable + * under that TXN_ID. + * + * If STR_KEY is non-null, copy it into an allocation from POOL. + * + * If MD5_CHECKSUM is non-null, use it as the MD5 checksum for the new + * rep; else initialize the rep with an all-zero (i.e., always + * successful) MD5 checksum. + * + * If SHA1_CHECKSUM is non-null, use it as the SHA1 checksum for the new + * rep; else initialize the rep with an all-zero (i.e., always + * successful) SHA1 checksum. + */ +static representation_t * +make_fulltext_rep(const char *str_key, + const char *txn_id, + svn_checksum_t *md5_checksum, + svn_checksum_t *sha1_checksum, + apr_pool_t *pool) + +{ + representation_t *rep = apr_pcalloc(pool, sizeof(*rep)); + if (txn_id && *txn_id) + rep->txn_id = apr_pstrdup(pool, txn_id); + rep->kind = rep_kind_fulltext; + rep->md5_checksum = svn_checksum_dup(md5_checksum, pool); + rep->sha1_checksum = svn_checksum_dup(sha1_checksum, pool); + rep->contents.fulltext.string_key + = str_key ? apr_pstrdup(pool, str_key) : NULL; + return rep; +} + + +/* Set *KEYS to an array of string keys gleaned from `delta' + representation REP. Allocate *KEYS in POOL. */ +static svn_error_t * +delta_string_keys(apr_array_header_t **keys, + const representation_t *rep, + apr_pool_t *pool) +{ + const char *key; + int i; + apr_array_header_t *chunks; + + if (rep->kind != rep_kind_delta) + return svn_error_create + (SVN_ERR_FS_GENERAL, NULL, + _("Representation is not of type 'delta'")); + + /* Set up a convenience variable. */ + chunks = rep->contents.delta.chunks; + + /* Initialize *KEYS to an empty array. */ + *keys = apr_array_make(pool, chunks->nelts, sizeof(key)); + if (! chunks->nelts) + return SVN_NO_ERROR; + + /* Now, push the string keys for each window into *KEYS */ + for (i = 0; i < chunks->nelts; i++) + { + rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i, rep_delta_chunk_t *); + + key = apr_pstrdup(pool, chunk->string_key); + APR_ARRAY_PUSH(*keys, const char *) = key; + } + + return SVN_NO_ERROR; +} + + +/* Delete the strings associated with array KEYS in FS as part of TRAIL. */ +static svn_error_t * +delete_strings(const apr_array_header_t *keys, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + int i; + const char *str_key; + apr_pool_t *subpool = svn_pool_create(pool); + + for (i = 0; i < keys->nelts; i++) + { + svn_pool_clear(subpool); + str_key = APR_ARRAY_IDX(keys, i, const char *); + SVN_ERR(svn_fs_bdb__string_delete(fs, str_key, trail, subpool)); + } + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + + +/*** Reading the contents from a representation. ***/ + +struct compose_handler_baton +{ + /* The combined window, and the pool it's allocated from. */ + svn_txdelta_window_t *window; + apr_pool_t *window_pool; + + /* If the incoming window was self-compressed, and the combined WINDOW + exists from previous iterations, SOURCE_BUF will point to the + expanded self-compressed window. */ + char *source_buf; + + /* The trail for this operation. WINDOW_POOL will be a child of + TRAIL->pool. No allocations will be made from TRAIL->pool itself. */ + trail_t *trail; + + /* TRUE when no more windows have to be read/combined. */ + svn_boolean_t done; + + /* TRUE if we've just started reading a new window. We need this + because the svndiff handler will push a NULL window at the end of + the stream, and we have to ignore that; but we must also know + when it's appropriate to push a NULL window at the combiner. */ + svn_boolean_t init; +}; + + +/* Handle one window. If BATON is emtpy, copy the WINDOW into it; + otherwise, combine WINDOW with the one in BATON, unless WINDOW + is self-compressed (i.e., does not copy from the source view), + in which case expand. */ + +static svn_error_t * +compose_handler(svn_txdelta_window_t *window, void *baton) +{ + struct compose_handler_baton *cb = baton; + SVN_ERR_ASSERT(!cb->done || window == NULL); + SVN_ERR_ASSERT(cb->trail && cb->trail->pool); + + if (!cb->init && !window) + return SVN_NO_ERROR; + + /* We should never get here if we've already expanded a + self-compressed window. */ + SVN_ERR_ASSERT(!cb->source_buf); + + if (cb->window) + { + if (window && (window->sview_len == 0 || window->src_ops == 0)) + { + /* This is a self-compressed window. Don't combine it with + the others, because the combiner may go quadratic. Instead, + expand it here and signal that the combination has + ended. */ + apr_size_t source_len = window->tview_len; + SVN_ERR_ASSERT(cb->window->sview_len == source_len); + cb->source_buf = apr_palloc(cb->window_pool, source_len); + svn_txdelta_apply_instructions(window, NULL, + cb->source_buf, &source_len); + cb->done = TRUE; + } + else + { + /* Combine the incoming window with whatever's in the baton. */ + apr_pool_t *composite_pool = svn_pool_create(cb->trail->pool); + svn_txdelta_window_t *composite; + + composite = svn_txdelta_compose_windows(window, cb->window, + composite_pool); + svn_pool_destroy(cb->window_pool); + cb->window = composite; + cb->window_pool = composite_pool; + cb->done = (composite->sview_len == 0 || composite->src_ops == 0); + } + } + else if (window) + { + /* Copy the (first) window into the baton. */ + apr_pool_t *window_pool = svn_pool_create(cb->trail->pool); + SVN_ERR_ASSERT(cb->window_pool == NULL); + cb->window = svn_txdelta_window_dup(window, window_pool); + cb->window_pool = window_pool; + cb->done = (window->sview_len == 0 || window->src_ops == 0); + } + else + cb->done = TRUE; + + cb->init = FALSE; + return SVN_NO_ERROR; +} + + + +/* Read one delta window from REP[CUR_CHUNK] and push it at the + composition handler. */ + +static svn_error_t * +get_one_window(struct compose_handler_baton *cb, + svn_fs_t *fs, + representation_t *rep, + int cur_chunk) +{ + svn_stream_t *wstream; + char diffdata[4096]; /* hunk of svndiff data */ + svn_filesize_t off; /* offset into svndiff data */ + apr_size_t amt; /* how much svndiff data to/was read */ + const char *str_key; + + apr_array_header_t *chunks = rep->contents.delta.chunks; + rep_delta_chunk_t *this_chunk, *first_chunk; + + cb->init = TRUE; + if (chunks->nelts <= cur_chunk) + return compose_handler(NULL, cb); + + /* Set up a window handling stream for the svndiff data. */ + wstream = svn_txdelta_parse_svndiff(compose_handler, cb, TRUE, + cb->trail->pool); + + /* First things first: send the "SVN"{version} header through the + stream. ### For now, we will just use the version specified + in the first chunk, and then verify that no chunks have a + different version number than the one used. In the future, + we might simply convert chunks that use a different version + of the diff format -- or, heck, a different format + altogether -- to the format/version of the first chunk. */ + first_chunk = APR_ARRAY_IDX(chunks, 0, rep_delta_chunk_t*); + diffdata[0] = 'S'; + diffdata[1] = 'V'; + diffdata[2] = 'N'; + diffdata[3] = (char) (first_chunk->version); + amt = 4; + SVN_ERR(svn_stream_write(wstream, diffdata, &amt)); + /* FIXME: The stream write handler is borked; assert (amt == 4); */ + + /* Get this string key which holds this window's data. + ### todo: make sure this is an `svndiff' DIFF skel here. */ + this_chunk = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*); + str_key = this_chunk->string_key; + + /* Run through the svndiff data, at least as far as necessary. */ + off = 0; + do + { + amt = sizeof(diffdata); + SVN_ERR(svn_fs_bdb__string_read(fs, str_key, diffdata, + off, &amt, cb->trail, + cb->trail->pool)); + off += amt; + SVN_ERR(svn_stream_write(wstream, diffdata, &amt)); + } + while (amt != 0); + SVN_ERR(svn_stream_close(wstream)); + + SVN_ERR_ASSERT(!cb->init); + SVN_ERR_ASSERT(cb->window != NULL); + SVN_ERR_ASSERT(cb->window_pool != NULL); + return SVN_NO_ERROR; +} + + +/* Undeltify a range of data. DELTAS is the set of delta windows to + combine, FULLTEXT is the source text, CUR_CHUNK is the index of the + delta chunk we're starting from. OFFSET is the relative offset of + the requested data within the chunk; BUF and LEN are what we're + undeltifying to. */ + +static svn_error_t * +rep_undeltify_range(svn_fs_t *fs, + const apr_array_header_t *deltas, + representation_t *fulltext, + int cur_chunk, + char *buf, + apr_size_t offset, + apr_size_t *len, + trail_t *trail, + apr_pool_t *pool) +{ + apr_size_t len_read = 0; + + do + { + struct compose_handler_baton cb = { 0 }; + char *source_buf, *target_buf; + apr_size_t target_len; + int cur_rep; + + cb.trail = trail; + cb.done = FALSE; + for (cur_rep = 0; !cb.done && cur_rep < deltas->nelts; ++cur_rep) + { + representation_t *const rep = + APR_ARRAY_IDX(deltas, cur_rep, representation_t*); + SVN_ERR(get_one_window(&cb, fs, rep, cur_chunk)); + } + + if (!cb.window) + /* That's it, no more source data is available. */ + break; + + /* The source view length should not be 0 if there are source + copy ops in the window. */ + SVN_ERR_ASSERT(cb.window->sview_len > 0 || cb.window->src_ops == 0); + + /* cb.window is the combined delta window. Read the source text + into a buffer. */ + if (cb.source_buf) + { + /* The combiner already created the source text from a + self-compressed window. */ + source_buf = cb.source_buf; + } + else if (fulltext && cb.window->sview_len > 0 && cb.window->src_ops > 0) + { + apr_size_t source_len = cb.window->sview_len; + source_buf = apr_palloc(cb.window_pool, source_len); + SVN_ERR(svn_fs_bdb__string_read + (fs, fulltext->contents.fulltext.string_key, + source_buf, cb.window->sview_offset, &source_len, + trail, pool)); + if (source_len != cb.window->sview_len) + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Svndiff source length inconsistency")); + } + else + { + source_buf = NULL; /* Won't read anything from here. */ + } + + if (offset > 0) + { + target_len = *len - len_read + offset; + target_buf = apr_palloc(cb.window_pool, target_len); + } + else + { + target_len = *len - len_read; + target_buf = buf; + } + + svn_txdelta_apply_instructions(cb.window, source_buf, + target_buf, &target_len); + if (offset > 0) + { + SVN_ERR_ASSERT(target_len > offset); + target_len -= offset; + memcpy(buf, target_buf + offset, target_len); + offset = 0; /* Read from the beginning of the next chunk. */ + } + /* Don't need this window any more. */ + svn_pool_destroy(cb.window_pool); + + len_read += target_len; + buf += target_len; + ++cur_chunk; + } + while (len_read < *len); + + *len = len_read; + return SVN_NO_ERROR; +} + + + +/* Calculate the index of the chunk in REP that contains REP_OFFSET, + and find the relative CHUNK_OFFSET within the chunk. + Return -1 if offset is beyond the end of the represented data. + ### The basic assumption is that all delta windows are the same size + and aligned at the same offset, so this number is the same in all + dependent deltas. Oh, and the chunks in REP must be ordered. */ + +static int +get_chunk_offset(representation_t *rep, + svn_filesize_t rep_offset, + apr_size_t *chunk_offset) +{ + const apr_array_header_t *chunks = rep->contents.delta.chunks; + int cur_chunk; + assert(chunks->nelts); + + /* ### Yes, this is a linear search. I'll change this to bisection + the very second we notice it's slowing us down. */ + for (cur_chunk = 0; cur_chunk < chunks->nelts; ++cur_chunk) + { + const rep_delta_chunk_t *const this_chunk + = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*); + + if ((this_chunk->offset + this_chunk->size) > rep_offset) + { + assert(this_chunk->offset <= rep_offset); + assert(rep_offset - this_chunk->offset < SVN_MAX_OBJECT_SIZE); + *chunk_offset = (apr_size_t) (rep_offset - this_chunk->offset); + return cur_chunk; + } + } + + return -1; +} + +/* Copy into BUF *LEN bytes starting at OFFSET from the string + represented via REP_KEY in FS, as part of TRAIL. + The number of bytes actually copied is stored in *LEN. */ +static svn_error_t * +rep_read_range(svn_fs_t *fs, + const char *rep_key, + svn_filesize_t offset, + char *buf, + apr_size_t *len, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + apr_size_t chunk_offset; + + /* Read in our REP. */ + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + if (rep->kind == rep_kind_fulltext) + { + SVN_ERR(svn_fs_bdb__string_read(fs, rep->contents.fulltext.string_key, + buf, offset, len, trail, pool)); + } + else if (rep->kind == rep_kind_delta) + { + const int cur_chunk = get_chunk_offset(rep, offset, &chunk_offset); + if (cur_chunk < 0) + *len = 0; + else + { + svn_error_t *err; + /* Preserve for potential use in error message. */ + const char *first_rep_key = rep_key; + /* Make a list of all the rep's we need to undeltify this range. + We'll have to read them within this trail anyway, so we might + as well do it once and up front. */ + apr_array_header_t *reps = apr_array_make(pool, 30, sizeof(rep)); + do + { + const rep_delta_chunk_t *const first_chunk + = APR_ARRAY_IDX(rep->contents.delta.chunks, + 0, rep_delta_chunk_t*); + const rep_delta_chunk_t *const chunk + = APR_ARRAY_IDX(rep->contents.delta.chunks, + cur_chunk, rep_delta_chunk_t*); + + /* Verify that this chunk is of the same version as the first. */ + if (first_chunk->version != chunk->version) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, NULL, + _("Diff version inconsistencies in representation '%s'"), + rep_key); + + rep_key = chunk->rep_key; + APR_ARRAY_PUSH(reps, representation_t *) = rep; + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, + trail, pool)); + } + while (rep->kind == rep_kind_delta + && rep->contents.delta.chunks->nelts > cur_chunk); + + /* Right. We've either just read the fulltext rep, or a rep that's + too short, in which case we'll undeltify without source data.*/ + if (rep->kind != rep_kind_delta && rep->kind != rep_kind_fulltext) + return UNKNOWN_NODE_KIND(rep_key); + + if (rep->kind == rep_kind_delta) + rep = NULL; /* Don't use source data */ + + err = rep_undeltify_range(fs, reps, rep, cur_chunk, buf, + chunk_offset, len, trail, pool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_CORRUPT) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, err, + _("Corruption detected whilst reading delta chain from " + "representation '%s' to '%s'"), first_rep_key, rep_key); + else + return svn_error_trace(err); + } + } + } + else /* unknown kind */ + return UNKNOWN_NODE_KIND(rep_key); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__get_mutable_rep(const char **new_rep_key, + const char *rep_key, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep = NULL; + const char *new_str = NULL; + + /* We were passed an existing REP_KEY, so examine it. If it is + mutable already, then just return REP_KEY as the mutable result + key. */ + if (rep_key && (rep_key[0] != '\0')) + { + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + if (rep_is_mutable(rep, txn_id)) + { + *new_rep_key = rep_key; + return SVN_NO_ERROR; + } + } + + /* Either we weren't provided a base key to examine, or the base key + we were provided was not mutable. So, let's make a new + representation and return its key to the caller. */ + SVN_ERR(svn_fs_bdb__string_append(fs, &new_str, 0, NULL, trail, pool)); + rep = make_fulltext_rep(new_str, txn_id, + svn_checksum_empty_checksum(svn_checksum_md5, + pool), + svn_checksum_empty_checksum(svn_checksum_sha1, + pool), + pool); + return svn_fs_bdb__write_new_rep(new_rep_key, fs, rep, trail, pool); +} + + +svn_error_t * +svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + if (! rep_is_mutable(rep, txn_id)) + return SVN_NO_ERROR; + + if (rep->kind == rep_kind_fulltext) + { + SVN_ERR(svn_fs_bdb__string_delete(fs, + rep->contents.fulltext.string_key, + trail, pool)); + } + else if (rep->kind == rep_kind_delta) + { + apr_array_header_t *keys; + SVN_ERR(delta_string_keys(&keys, rep, pool)); + SVN_ERR(delete_strings(keys, fs, trail, pool)); + } + else /* unknown kind */ + return UNKNOWN_NODE_KIND(rep_key); + + return svn_fs_bdb__delete_rep(fs, rep_key, trail, pool); +} + + + +/*** Reading and writing data via representations. ***/ + +/** Reading. **/ + +struct rep_read_baton +{ + /* The FS from which we're reading. */ + svn_fs_t *fs; + + /* The representation skel whose contents we want to read. If this + is NULL, the rep has never had any contents, so all reads fetch 0 + bytes. + + Formerly, we cached the entire rep skel here, not just the key. + That way we didn't have to fetch the rep from the db every time + we want to read a little bit more of the file. Unfortunately, + this has a problem: if, say, a file's representation changes + while we're reading (changes from fulltext to delta, for + example), we'll never know it. So for correctness, we now + refetch the representation skel every time we want to read + another chunk. */ + const char *rep_key; + + /* How many bytes have been read already. */ + svn_filesize_t offset; + + /* If present, the read will be done as part of this trail, and the + trail's pool will be used. Otherwise, see `pool' below. */ + trail_t *trail; + + /* MD5 checksum context. Initialized when the baton is created, updated as + we read data, and finalized when the stream is closed. */ + svn_checksum_ctx_t *md5_checksum_ctx; + + /* Final resting place of the checksum created by md5_checksum_cxt. */ + svn_checksum_t *md5_checksum; + + /* SHA1 checksum context. Initialized when the baton is created, updated as + we read data, and finalized when the stream is closed. */ + svn_checksum_ctx_t *sha1_checksum_ctx; + + /* Final resting place of the checksum created by sha1_checksum_cxt. */ + svn_checksum_t *sha1_checksum; + + /* The length of the rep's contents (as fulltext, that is, + independent of how the rep actually stores the data.) This is + retrieved when the baton is created, and used to determine when + we have read the last byte, at which point we compare checksums. + + Getting this at baton creation time makes interleaved reads and + writes on the same rep in the same trail impossible. But we're + not doing that, and probably no one ever should. And anyway if + they do, they should see problems immediately. */ + svn_filesize_t size; + + /* Set to FALSE when the baton is created, TRUE when the checksum_ctx + is digestified. */ + svn_boolean_t checksum_finalized; + + /* Used for temporary allocations. This pool is cleared at the + start of each invocation of the relevant stream read function -- + see rep_read_contents(). */ + apr_pool_t *scratch_pool; + +}; + + +static svn_error_t * +rep_read_get_baton(struct rep_read_baton **rb_p, + svn_fs_t *fs, + const char *rep_key, + svn_boolean_t use_trail_for_reads, + trail_t *trail, + apr_pool_t *pool) +{ + struct rep_read_baton *b; + + b = apr_pcalloc(pool, sizeof(*b)); + b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); + b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); + + if (rep_key) + SVN_ERR(svn_fs_base__rep_contents_size(&(b->size), fs, rep_key, + trail, pool)); + else + b->size = 0; + + b->checksum_finalized = FALSE; + b->fs = fs; + b->trail = use_trail_for_reads ? trail : NULL; + b->scratch_pool = svn_pool_create(pool); + b->rep_key = rep_key; + b->offset = 0; + + *rb_p = b; + + return SVN_NO_ERROR; +} + + + +/*** Retrieving data. ***/ + +svn_error_t * +svn_fs_base__rep_contents_size(svn_filesize_t *size_p, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + + if (rep->kind == rep_kind_fulltext) + { + /* Get the size by asking Berkeley for the string's length. */ + SVN_ERR(svn_fs_bdb__string_size(size_p, fs, + rep->contents.fulltext.string_key, + trail, pool)); + } + else if (rep->kind == rep_kind_delta) + { + /* Get the size by finding the last window pkg in the delta and + adding its offset to its size. This way, we won't even be + messed up by overlapping windows, as long as the window pkgs + are still ordered. */ + apr_array_header_t *chunks = rep->contents.delta.chunks; + rep_delta_chunk_t *last_chunk; + + SVN_ERR_ASSERT(chunks->nelts); + + last_chunk = APR_ARRAY_IDX(chunks, chunks->nelts - 1, + rep_delta_chunk_t *); + *size_p = last_chunk->offset + last_chunk->size; + } + else /* unknown kind */ + return UNKNOWN_NODE_KIND(rep_key); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum, + svn_checksum_t **sha1_checksum, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + if (md5_checksum) + *md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); + if (sha1_checksum) + *sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__rep_contents(svn_string_t *str, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool) +{ + svn_filesize_t contents_size; + apr_size_t len; + char *data; + + SVN_ERR(svn_fs_base__rep_contents_size(&contents_size, fs, rep_key, + trail, pool)); + + /* What if the contents are larger than we can handle? */ + if (contents_size > SVN_MAX_OBJECT_SIZE) + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, + _("Rep contents are too large: " + "got %s, limit is %s"), + apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, contents_size), + apr_psprintf(pool, "%" APR_SIZE_T_FMT, SVN_MAX_OBJECT_SIZE)); + else + str->len = (apr_size_t) contents_size; + + data = apr_palloc(pool, str->len); + str->data = data; + len = str->len; + SVN_ERR(rep_read_range(fs, rep_key, 0, data, &len, trail, pool)); + + /* Paranoia. */ + if (len != str->len) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, NULL, + _("Failure reading representation '%s'"), rep_key); + + /* Just the standard paranoia. */ + { + representation_t *rep; + svn_checksum_t *checksum, *rep_checksum; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + rep_checksum = rep->sha1_checksum ? rep->sha1_checksum : rep->md5_checksum; + SVN_ERR(svn_checksum(&checksum, rep_checksum->kind, str->data, str->len, + pool)); + + if (! svn_checksum_match(checksum, rep_checksum)) + return svn_error_create(SVN_ERR_FS_CORRUPT, + svn_checksum_mismatch_err(rep_checksum, checksum, pool, + _("Checksum mismatch on representation '%s'"), + rep_key), + NULL); + } + + return SVN_NO_ERROR; +} + + +struct read_rep_args +{ + struct rep_read_baton *rb; /* The data source. */ + char *buf; /* Where to put what we read. */ + apr_size_t *len; /* How much to read / was read. */ +}; + + +/* BATON is of type `read_rep_args': + + Read into BATON->rb->buf the *(BATON->len) bytes starting at + BATON->rb->offset from the data represented at BATON->rb->rep_key + in BATON->rb->fs, as part of TRAIL. + + Afterwards, *(BATON->len) is the number of bytes actually read, and + BATON->rb->offset is incremented by that amount. + + If BATON->rb->rep_key is null, this is assumed to mean the file's + contents have no representation, i.e., the file has no contents. + In that case, if BATON->rb->offset > 0, return the error + SVN_ERR_FS_FILE_CONTENTS_CHANGED, else just set *(BATON->len) to + zero and return. */ +static svn_error_t * +txn_body_read_rep(void *baton, trail_t *trail) +{ + struct read_rep_args *args = baton; + + if (args->rb->rep_key) + { + SVN_ERR(rep_read_range(args->rb->fs, + args->rb->rep_key, + args->rb->offset, + args->buf, + args->len, + trail, + args->rb->scratch_pool)); + + args->rb->offset += *(args->len); + + /* We calculate the checksum just once, the moment we see the + * last byte of data. But we can't assume there was a short + * read. The caller may have known the length of the data and + * requested exactly that amount, so there would never be a + * short read. (That's why the read baton has to know the + * length of the data in advance.) + * + * On the other hand, some callers invoke the stream reader in a + * loop whose termination condition is that the read returned + * zero bytes of data -- which usually results in the read + * function being called one more time *after* the call that got + * a short read (indicating end-of-stream). + * + * The conditions below ensure that we compare checksums even + * when there is no short read associated with the last byte of + * data, while also ensuring that it's harmless to repeatedly + * read 0 bytes from the stream. + */ + if (! args->rb->checksum_finalized) + { + SVN_ERR(svn_checksum_update(args->rb->md5_checksum_ctx, args->buf, + *(args->len))); + SVN_ERR(svn_checksum_update(args->rb->sha1_checksum_ctx, args->buf, + *(args->len))); + + if (args->rb->offset == args->rb->size) + { + representation_t *rep; + + SVN_ERR(svn_checksum_final(&args->rb->md5_checksum, + args->rb->md5_checksum_ctx, + trail->pool)); + SVN_ERR(svn_checksum_final(&args->rb->sha1_checksum, + args->rb->sha1_checksum_ctx, + trail->pool)); + args->rb->checksum_finalized = TRUE; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, args->rb->fs, + args->rb->rep_key, + trail, trail->pool)); + + if (rep->md5_checksum + && (! svn_checksum_match(rep->md5_checksum, + args->rb->md5_checksum))) + return svn_error_create(SVN_ERR_FS_CORRUPT, + svn_checksum_mismatch_err(rep->md5_checksum, + args->rb->sha1_checksum, trail->pool, + _("MD5 checksum mismatch on representation '%s'"), + args->rb->rep_key), + NULL); + + if (rep->sha1_checksum + && (! svn_checksum_match(rep->sha1_checksum, + args->rb->sha1_checksum))) + return svn_error_createf(SVN_ERR_FS_CORRUPT, + svn_checksum_mismatch_err(rep->sha1_checksum, + args->rb->sha1_checksum, trail->pool, + _("SHA1 checksum mismatch on representation '%s'"), + args->rb->rep_key), + NULL); + } + } + } + else if (args->rb->offset > 0) + { + return + svn_error_create + (SVN_ERR_FS_REP_CHANGED, NULL, + _("Null rep, but offset past zero already")); + } + else + *(args->len) = 0; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +rep_read_contents(void *baton, char *buf, apr_size_t *len) +{ + struct rep_read_baton *rb = baton; + struct read_rep_args args; + + /* Clear the scratch pool of the results of previous invocations. */ + svn_pool_clear(rb->scratch_pool); + + args.rb = rb; + args.buf = buf; + args.len = len; + + /* If we got a trail, use it; else make one. */ + if (rb->trail) + SVN_ERR(txn_body_read_rep(&args, rb->trail)); + else + { + /* In the case of reading from the db, any returned data should + live in our pre-allocated buffer, so the whole operation can + happen within a single malloc/free cycle. This prevents us + from creating millions of unnecessary trail subpools when + reading a big file. */ + SVN_ERR(svn_fs_base__retry_txn(rb->fs, + txn_body_read_rep, + &args, + TRUE, + rb->scratch_pool)); + } + return SVN_NO_ERROR; +} + + +/** Writing. **/ + + +struct rep_write_baton +{ + /* The FS in which we're writing. */ + svn_fs_t *fs; + + /* The representation skel whose contents we want to write. */ + const char *rep_key; + + /* The transaction id under which this write action will take + place. */ + const char *txn_id; + + /* If present, do the write as part of this trail, and use trail's + pool. Otherwise, see `pool' below. */ + trail_t *trail; + + /* SHA1 and MD5 checksums. Initialized when the baton is created, + updated as we write data, and finalized and stored when the + stream is closed. */ + svn_checksum_ctx_t *md5_checksum_ctx; + svn_checksum_t *md5_checksum; + svn_checksum_ctx_t *sha1_checksum_ctx; + svn_checksum_t *sha1_checksum; + svn_boolean_t finalized; + + /* Used for temporary allocations, iff `trail' (above) is null. */ + apr_pool_t *pool; + +}; + + +static struct rep_write_baton * +rep_write_get_baton(svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + struct rep_write_baton *b; + + b = apr_pcalloc(pool, sizeof(*b)); + b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); + b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); + b->fs = fs; + b->trail = trail; + b->pool = pool; + b->rep_key = rep_key; + b->txn_id = txn_id; + return b; +} + + + +/* Write LEN bytes from BUF into the end of the string represented via + REP_KEY in FS, as part of TRAIL. If the representation is not + mutable, return the error SVN_FS_REP_NOT_MUTABLE. */ +static svn_error_t * +rep_write(svn_fs_t *fs, + const char *rep_key, + const char *buf, + apr_size_t len, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + + if (! rep_is_mutable(rep, txn_id)) + return svn_error_createf + (SVN_ERR_FS_REP_NOT_MUTABLE, NULL, + _("Rep '%s' is not mutable"), rep_key); + + if (rep->kind == rep_kind_fulltext) + { + SVN_ERR(svn_fs_bdb__string_append + (fs, &(rep->contents.fulltext.string_key), len, buf, + trail, pool)); + } + else if (rep->kind == rep_kind_delta) + { + /* There should never be a case when we have a mutable + non-fulltext rep. The only code that creates mutable reps is + in this file, and it creates them fulltext. */ + return svn_error_createf + (SVN_ERR_FS_CORRUPT, NULL, + _("Rep '%s' both mutable and non-fulltext"), rep_key); + } + else /* unknown kind */ + return UNKNOWN_NODE_KIND(rep_key); + + return SVN_NO_ERROR; +} + + +struct write_rep_args +{ + struct rep_write_baton *wb; /* Destination. */ + const char *buf; /* Data. */ + apr_size_t len; /* How much to write. */ +}; + + +/* BATON is of type `write_rep_args': + Append onto BATON->wb->rep_key's contents BATON->len bytes of + data from BATON->wb->buf, in BATON->rb->fs, as part of TRAIL. + + If the representation is not mutable, return the error + SVN_FS_REP_NOT_MUTABLE. */ +static svn_error_t * +txn_body_write_rep(void *baton, trail_t *trail) +{ + struct write_rep_args *args = baton; + + SVN_ERR(rep_write(args->wb->fs, + args->wb->rep_key, + args->buf, + args->len, + args->wb->txn_id, + trail, + trail->pool)); + SVN_ERR(svn_checksum_update(args->wb->md5_checksum_ctx, + args->buf, args->len)); + SVN_ERR(svn_checksum_update(args->wb->sha1_checksum_ctx, + args->buf, args->len)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +rep_write_contents(void *baton, + const char *buf, + apr_size_t *len) +{ + struct rep_write_baton *wb = baton; + struct write_rep_args args; + + /* We toss LEN's indirectness because if not all the bytes are + written, it's an error, so we wouldn't be reporting anything back + through *LEN anyway. */ + args.wb = wb; + args.buf = buf; + args.len = *len; + + /* If we got a trail, use it; else make one. */ + if (wb->trail) + SVN_ERR(txn_body_write_rep(&args, wb->trail)); + else + { + /* In the case of simply writing the rep to the db, we're + *certain* that there's no data coming back to us that needs + to be preserved... so the whole operation can happen within a + single malloc/free cycle. This prevents us from creating + millions of unnecessary trail subpools when writing a big + file. */ + SVN_ERR(svn_fs_base__retry_txn(wb->fs, + txn_body_write_rep, + &args, + TRUE, + wb->pool)); + } + + return SVN_NO_ERROR; +} + + +/* Helper for rep_write_close_contents(); see that doc string for + more. BATON is of type `struct rep_write_baton'. */ +static svn_error_t * +txn_body_write_close_rep(void *baton, trail_t *trail) +{ + struct rep_write_baton *wb = baton; + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, wb->fs, wb->rep_key, + trail, trail->pool)); + rep->md5_checksum = svn_checksum_dup(wb->md5_checksum, trail->pool); + rep->sha1_checksum = svn_checksum_dup(wb->sha1_checksum, trail->pool); + return svn_fs_bdb__write_rep(wb->fs, wb->rep_key, rep, + trail, trail->pool); +} + + +/* BATON is of type `struct rep_write_baton'. + * + * Finalize BATON->md5_context and store the resulting digest under + * BATON->rep_key. + */ +static svn_error_t * +rep_write_close_contents(void *baton) +{ + struct rep_write_baton *wb = baton; + + /* ### Thought: if we fixed apr-util MD5 contexts to allow repeated + digestification, then we wouldn't need a stream close function at + all -- instead, we could update the stored checksum each time a + write occurred, which would have the added advantage of making + interleaving reads and writes work. Currently, they'd fail with + a checksum mismatch, it just happens that our code never tries to + do that anyway. */ + + if (! wb->finalized) + { + SVN_ERR(svn_checksum_final(&wb->md5_checksum, wb->md5_checksum_ctx, + wb->pool)); + SVN_ERR(svn_checksum_final(&wb->sha1_checksum, wb->sha1_checksum_ctx, + wb->pool)); + wb->finalized = TRUE; + } + + /* If we got a trail, use it; else make one. */ + if (wb->trail) + return txn_body_write_close_rep(wb, wb->trail); + else + /* We need to keep our trail pool around this time so the + checksums we've calculated survive. */ + return svn_fs_base__retry_txn(wb->fs, txn_body_write_close_rep, + wb, FALSE, wb->pool); +} + + +/** Public read and write stream constructors. **/ + +svn_error_t * +svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p, + svn_fs_t *fs, + const char *rep_key, + svn_boolean_t use_trail_for_reads, + trail_t *trail, + apr_pool_t *pool) +{ + struct rep_read_baton *rb; + + SVN_ERR(rep_read_get_baton(&rb, fs, rep_key, use_trail_for_reads, + trail, pool)); + *rs_p = svn_stream_create(rb, pool); + svn_stream_set_read(*rs_p, rep_read_contents); + + return SVN_NO_ERROR; +} + + +/* Clear the contents of REP_KEY, so that it represents the empty + string, as part of TRAIL. TXN_ID is the id of the Subversion + transaction under which this occurs. If REP_KEY is not mutable, + return the error SVN_ERR_FS_REP_NOT_MUTABLE. */ +static svn_error_t * +rep_contents_clear(svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + const char *str_key; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool)); + + /* Make sure it's mutable. */ + if (! rep_is_mutable(rep, txn_id)) + return svn_error_createf + (SVN_ERR_FS_REP_NOT_MUTABLE, NULL, + _("Rep '%s' is not mutable"), rep_key); + + SVN_ERR_ASSERT(rep->kind == rep_kind_fulltext); + + /* If rep has no string, just return success. Else, clear the + underlying string. */ + str_key = rep->contents.fulltext.string_key; + if (str_key && *str_key) + { + SVN_ERR(svn_fs_bdb__string_clear(fs, str_key, trail, pool)); + rep->md5_checksum = NULL; + rep->sha1_checksum = NULL; + SVN_ERR(svn_fs_bdb__write_rep(fs, rep_key, rep, trail, pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p, + svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + svn_boolean_t use_trail_for_writes, + trail_t *trail, + apr_pool_t *pool) +{ + struct rep_write_baton *wb; + + /* Clear the current rep contents (free mutability check!). */ + SVN_ERR(rep_contents_clear(fs, rep_key, txn_id, trail, pool)); + + /* Now, generate the write baton and stream. */ + wb = rep_write_get_baton(fs, rep_key, txn_id, + use_trail_for_writes ? trail : NULL, pool); + *ws_p = svn_stream_create(wb, pool); + svn_stream_set_write(*ws_p, rep_write_contents); + svn_stream_set_close(*ws_p, rep_write_close_contents); + + return SVN_NO_ERROR; +} + + + +/*** Deltified storage. ***/ + +/* Baton for svn_write_fn_t write_string_set(). */ +struct write_svndiff_strings_baton +{ + /* The fs where lives the string we're writing. */ + svn_fs_t *fs; + + /* The key of the string we're writing to. Typically this is + initialized to NULL, so svn_fs_base__string_append() can fill in a + value. */ + const char *key; + + /* The amount of txdelta data written to the current + string-in-progress. */ + apr_size_t size; + + /* The amount of svndiff header information we've written thus far + to the strings table. */ + apr_size_t header_read; + + /* The version number of the svndiff data written. ### You'd better + not count on this being populated after the first chunk is sent + through the interface, since it lives at the 4th byte of the + stream. */ + apr_byte_t version; + + /* The trail we're writing in. */ + trail_t *trail; + +}; + + +/* Function of type `svn_write_fn_t', for writing to a collection of + strings; BATON is `struct write_svndiff_strings_baton *'. + + On the first call, BATON->key is null. A new string key in + BATON->fs is chosen and stored in BATON->key; each call appends + *LEN bytes from DATA onto the string. *LEN is never changed; if + the write fails to write all *LEN bytes, an error is returned. + BATON->size is used to track the total amount of data written via + this handler, and must be reset by the caller to 0 when appropriate. */ +static svn_error_t * +write_svndiff_strings(void *baton, const char *data, apr_size_t *len) +{ + struct write_svndiff_strings_baton *wb = baton; + const char *buf = data; + apr_size_t nheader = 0; + + /* If we haven't stripped all the header information from this + stream yet, keep stripping. If someone sends a first window + through here that's shorter than 4 bytes long, this will probably + cause a nuclear reactor meltdown somewhere in the American + midwest. */ + if (wb->header_read < 4) + { + nheader = 4 - wb->header_read; + *len -= nheader; + buf += nheader; + wb->header_read += nheader; + + /* If we have *now* read the full 4-byte header, check that + least byte for the version number of the svndiff format. */ + if (wb->header_read == 4) + wb->version = *(buf - 1); + } + + /* Append to the current string we're writing (or create a new one + if WB->key is NULL). */ + SVN_ERR(svn_fs_bdb__string_append(wb->fs, &(wb->key), *len, + buf, wb->trail, wb->trail->pool)); + + /* Make sure we (still) have a key. */ + if (wb->key == NULL) + return svn_error_create(SVN_ERR_FS_GENERAL, NULL, + _("Failed to get new string key")); + + /* Restore *LEN to the value it *would* have been were it not for + header stripping. */ + *len += nheader; + + /* Increment our running total of bytes written to this string. */ + wb->size += *len; + + return SVN_NO_ERROR; +} + + +typedef struct window_write_t +{ + const char *key; /* string key for this window */ + apr_size_t svndiff_len; /* amount of svndiff data written to the string */ + svn_filesize_t text_off; /* offset of fulltext represented by this window */ + apr_size_t text_len; /* amount of fulltext data represented by this window */ + +} window_write_t; + + +svn_error_t * +svn_fs_base__rep_deltify(svn_fs_t *fs, + const char *target, + const char *source, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + svn_stream_t *source_stream; /* stream to read the source */ + svn_stream_t *target_stream; /* stream to read the target */ + svn_txdelta_stream_t *txdelta_stream; /* stream to read delta windows */ + + /* window-y things, and an array to track them */ + window_write_t *ww; + apr_array_header_t *windows; + + /* stream to write new (deltified) target data and its baton */ + svn_stream_t *new_target_stream; + struct write_svndiff_strings_baton new_target_baton; + + /* window handler/baton for writing to above stream */ + svn_txdelta_window_handler_t new_target_handler; + void *new_target_handler_baton; + + /* yes, we do windows */ + svn_txdelta_window_t *window; + + /* The current offset into the fulltext that our window is about to + write. This doubles, after all windows are written, as the + total size of the svndiff data for the deltification process. */ + svn_filesize_t tview_off = 0; + + /* The total amount of diff data written while deltifying. */ + svn_filesize_t diffsize = 0; + + /* TARGET's original string keys */ + apr_array_header_t *orig_str_keys; + + /* The checksums for the representation's fulltext contents. */ + svn_checksum_t *rep_md5_checksum; + svn_checksum_t *rep_sha1_checksum; + + /* MD5 digest */ + const unsigned char *digest; + + /* pool for holding the windows */ + apr_pool_t *wpool; + + /* Paranoia: never allow a rep to be deltified against itself, + because then there would be no fulltext reachable in the delta + chain, and badness would ensue. */ + if (strcmp(target, source) == 0) + return svn_error_createf + (SVN_ERR_FS_CORRUPT, NULL, + _("Attempt to deltify '%s' against itself"), + target); + + /* Set up a handler for the svndiff data, which will write each + window to its own string in the `strings' table. */ + new_target_baton.fs = fs; + new_target_baton.trail = trail; + new_target_baton.header_read = FALSE; + new_target_stream = svn_stream_create(&new_target_baton, pool); + svn_stream_set_write(new_target_stream, write_svndiff_strings); + + /* Get streams to our source and target text data. */ + SVN_ERR(svn_fs_base__rep_contents_read_stream(&source_stream, fs, source, + TRUE, trail, pool)); + SVN_ERR(svn_fs_base__rep_contents_read_stream(&target_stream, fs, target, + TRUE, trail, pool)); + + /* Setup a stream to convert the textdelta data into svndiff windows. */ + svn_txdelta2(&txdelta_stream, source_stream, target_stream, TRUE, pool); + + if (bfd->format >= SVN_FS_BASE__MIN_SVNDIFF1_FORMAT) + svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton, + new_target_stream, 1, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + else + svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton, + new_target_stream, 0, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + + /* subpool for the windows */ + wpool = svn_pool_create(pool); + + /* Now, loop, manufacturing and dispatching windows of svndiff data. */ + windows = apr_array_make(pool, 1, sizeof(ww)); + do + { + /* Reset some baton variables. */ + new_target_baton.size = 0; + new_target_baton.key = NULL; + + /* Free the window. */ + svn_pool_clear(wpool); + + /* Fetch the next window of txdelta data. */ + SVN_ERR(svn_txdelta_next_window(&window, txdelta_stream, wpool)); + + /* Send off this package to be written as svndiff data. */ + SVN_ERR(new_target_handler(window, new_target_handler_baton)); + if (window) + { + /* Add a new window description to our array. */ + ww = apr_pcalloc(pool, sizeof(*ww)); + ww->key = new_target_baton.key; + ww->svndiff_len = new_target_baton.size; + ww->text_off = tview_off; + ww->text_len = window->tview_len; + APR_ARRAY_PUSH(windows, window_write_t *) = ww; + + /* Update our recordkeeping variables. */ + tview_off += window->tview_len; + diffsize += ww->svndiff_len; + } + + } while (window); + + svn_pool_destroy(wpool); + + /* Having processed all the windows, we can query the MD5 digest + from the stream. */ + digest = svn_txdelta_md5_digest(txdelta_stream); + if (! digest) + return svn_error_createf + (SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT, NULL, + _("Failed to calculate MD5 digest for '%s'"), + source); + + /* Construct a list of the strings used by the old representation so + that we can delete them later. While we are here, if the old + representation was a fulltext, check to make sure the delta we're + replacing it with is actually smaller. (Don't perform this check + if we're replacing a delta; in that case, we're going for a time + optimization, not a space optimization.) */ + { + representation_t *old_rep; + const char *str_key; + + SVN_ERR(svn_fs_bdb__read_rep(&old_rep, fs, target, trail, pool)); + if (old_rep->kind == rep_kind_fulltext) + { + svn_filesize_t old_size = 0; + + str_key = old_rep->contents.fulltext.string_key; + SVN_ERR(svn_fs_bdb__string_size(&old_size, fs, str_key, + trail, pool)); + orig_str_keys = apr_array_make(pool, 1, sizeof(str_key)); + APR_ARRAY_PUSH(orig_str_keys, const char *) = str_key; + + /* If the new data is NOT an space optimization, destroy the + string(s) we created, and get outta here. */ + if (diffsize >= old_size) + { + int i; + for (i = 0; i < windows->nelts; i++) + { + ww = APR_ARRAY_IDX(windows, i, window_write_t *); + SVN_ERR(svn_fs_bdb__string_delete(fs, ww->key, trail, pool)); + } + return SVN_NO_ERROR; + } + } + else if (old_rep->kind == rep_kind_delta) + SVN_ERR(delta_string_keys(&orig_str_keys, old_rep, pool)); + else /* unknown kind */ + return UNKNOWN_NODE_KIND(target); + + /* Save the checksums, since the new rep needs them. */ + rep_md5_checksum = svn_checksum_dup(old_rep->md5_checksum, pool); + rep_sha1_checksum = svn_checksum_dup(old_rep->sha1_checksum, pool); + } + + /* Hook the new strings we wrote into the rest of the filesystem by + building a new representation to replace our old one. */ + { + representation_t new_rep; + rep_delta_chunk_t *chunk; + apr_array_header_t *chunks; + int i; + + new_rep.kind = rep_kind_delta; + new_rep.txn_id = NULL; + + /* Migrate the old rep's checksums to the new rep. */ + new_rep.md5_checksum = svn_checksum_dup(rep_md5_checksum, pool); + new_rep.sha1_checksum = svn_checksum_dup(rep_sha1_checksum, pool); + + chunks = apr_array_make(pool, windows->nelts, sizeof(chunk)); + + /* Loop through the windows we wrote, creating and adding new + chunks to the representation. */ + for (i = 0; i < windows->nelts; i++) + { + ww = APR_ARRAY_IDX(windows, i, window_write_t *); + + /* Allocate a chunk and its window */ + chunk = apr_palloc(pool, sizeof(*chunk)); + chunk->offset = ww->text_off; + + /* Populate the window */ + chunk->version = new_target_baton.version; + chunk->string_key = ww->key; + chunk->size = ww->text_len; + chunk->rep_key = source; + + /* Add this chunk to the array. */ + APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk; + } + + /* Put the chunks array into the representation. */ + new_rep.contents.delta.chunks = chunks; + + /* Write out the new representation. */ + SVN_ERR(svn_fs_bdb__write_rep(fs, target, &new_rep, trail, pool)); + + /* Delete the original pre-deltified strings. */ + SVN_ERR(delete_strings(orig_str_keys, fs, trail, pool)); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/reps-strings.h b/subversion/libsvn_fs_base/reps-strings.h new file mode 100644 index 0000000..475af0c --- /dev/null +++ b/subversion/libsvn_fs_base/reps-strings.h @@ -0,0 +1,176 @@ +/* reps-strings.h : interpreting representations with respect to strings + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_REPS_STRINGS_H +#define SVN_LIBSVN_FS_REPS_STRINGS_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_io.h" +#include "svn_fs.h" + +#include "trail.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* Get or create a mutable representation in FS, and set *NEW_REP_KEY to its + key. + + TXN_ID is the id of the Subversion transaction under which this occurs. + + If REP_KEY is not null and is already a mutable representation, set + *NEW_REP_KEY to REP_KEY, else create a brand new rep and set *NEW_REP_KEY + to its key, allocated in POOL. */ +svn_error_t *svn_fs_base__get_mutable_rep(const char **new_rep_key, + const char *rep_key, + svn_fs_t *fs, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Delete REP_KEY from FS if REP_KEY is mutable, as part of trail, or + do nothing if REP_KEY is immutable. If a mutable rep is deleted, + the string it refers to is deleted as well. TXN_ID is the id of + the Subversion transaction under which this occurs. + + If no such rep, return SVN_ERR_FS_NO_SUCH_REPRESENTATION. */ +svn_error_t *svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool); + + + + +/*** Reading and writing rep contents. ***/ + +/* Set *SIZE_P to the size of REP_KEY's contents in FS, as part of TRAIL. + Note: this is the fulltext size, no matter how the contents are + represented in storage. */ +svn_error_t *svn_fs_base__rep_contents_size(svn_filesize_t *size_p, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool); + + +/* If MD5_CHECKSUM is non-NULL, set *MD5_CHECKSUM to the MD5 checksum + for REP_KEY in FS, as part of TRAIL. + + If SHA1_CHECKSUM is non-NULL, set *SHA1_CHECKSUM to the SHA1 + checksum for REP_KEY in FS, as part of TRAIL. + + These are the prerecorded checksums for the rep's contents' + fulltext. If one or both of the checksums is not stored, do not + calculate one dynamically, just put NULL into the respective return + value. (By convention, the NULL checksum is considered to match + any checksum.) */ +svn_error_t * +svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum, + svn_checksum_t **sha1_checksum, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool); + + +/* Set STR->data to the contents of REP_KEY in FS, and STR->len to the + contents' length, as part of TRAIL. The data is allocated in + POOL. If an error occurs, the effect on STR->data and + STR->len is undefined. + + Note: this is the fulltext contents, no matter how the contents are + represented in storage. */ +svn_error_t *svn_fs_base__rep_contents(svn_string_t *str, + svn_fs_t *fs, + const char *rep_key, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *RS_P to a stream to read the contents of REP_KEY in FS. + Allocate the stream in POOL. + + REP_KEY may be null, in which case reads just return 0 bytes. + + If USE_TRAIL_FOR_READS is TRUE, the stream's reads are part + of TRAIL; otherwise, each read happens in an internal, one-off + trail (though TRAIL is still required). POOL may be TRAIL->pool. */ +svn_error_t * +svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p, + svn_fs_t *fs, + const char *rep_key, + svn_boolean_t use_trail_for_reads, + trail_t *trail, + apr_pool_t *pool); + + +/* Set *WS_P to a stream to write the contents of REP_KEY. Allocate + the stream in POOL. TXN_ID is the id of the Subversion transaction + under which this occurs. + + If USE_TRAIL_FOR_WRITES is TRUE, the stream's writes are part + of TRAIL; otherwise, each write happens in an internal, one-off + trail (though TRAIL is still required). POOL may be TRAIL->pool. + + If REP_KEY is not mutable, writes to *WS_P will return the + error SVN_ERR_FS_REP_NOT_MUTABLE. */ +svn_error_t * +svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p, + svn_fs_t *fs, + const char *rep_key, + const char *txn_id, + svn_boolean_t use_trail_for_writes, + trail_t *trail, + apr_pool_t *pool); + + + +/*** Deltified storage. ***/ + +/* Offer TARGET the chance to store its contents as a delta against + SOURCE, in FS, as part of TRAIL. TARGET and SOURCE are both + representation keys. + + This usually results in TARGET's data being stored as a diff + against SOURCE; but it might not, if it turns out to be more + efficient to store the contents some other way. */ +svn_error_t *svn_fs_base__rep_deltify(svn_fs_t *fs, + const char *target, + const char *source, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_REPS_STRINGS_H */ diff --git a/subversion/libsvn_fs_base/revs-txns.c b/subversion/libsvn_fs_base/revs-txns.c new file mode 100644 index 0000000..d218843 --- /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; +} diff --git a/subversion/libsvn_fs_base/revs-txns.h b/subversion/libsvn_fs_base/revs-txns.h new file mode 100644 index 0000000..558a90c --- /dev/null +++ b/subversion/libsvn_fs_base/revs-txns.h @@ -0,0 +1,231 @@ +/* revs-txns.h : internal interface to revision and transactions operations + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_REVS_TXNS_H +#define SVN_LIBSVN_FS_REVS_TXNS_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include "svn_fs.h" + +#include "fs.h" +#include "trail.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/*** Revisions ***/ + +/* Set *ROOT_ID_P to the ID of the root directory of revision REV in FS, + as part of TRAIL. Allocate the ID in POOL. */ +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); + + +/* Set *TXN_ID_P to the ID of the transaction that was committed to + create REV in FS, as part of TRAIL. Allocate the ID in POOL. */ +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); + + +/* Set property NAME to VALUE on REV in FS, as part of TRAIL. */ +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); + + + +/*** Transactions ***/ + +/* Convert the unfinished transaction in FS named TXN_NAME to a + committed transaction that refers to REVISION as part of TRAIL. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +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); + + +/* Set *REVISION to the revision which was created when FS transaction + TXN_NAME was committed, or to SVN_INVALID_REVNUM if the transaction + has not been committed. Do all of this as part of TRAIL. */ +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); + + +/* Retrieve information about the Subversion transaction TXN_NAME from + the `transactions' table of FS, as part of TRAIL. + Set *ROOT_ID_P to the ID of the transaction's root directory. + Set *BASE_ROOT_ID_P to the ID of the root directory of the + transaction's base revision. + + If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is + the error returned. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. + + Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */ +svn_error_t *svn_fs_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); + + +/* Set the root directory of the Subversion transaction TXN_NAME in FS + to ROOT_ID, as part of TRAIL. Do any necessary temporary + allocation in POOL. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +svn_error_t *svn_fs_base__set_txn_root(svn_fs_t *fs, + const char *txn_name, + const svn_fs_id_t *root_id, + trail_t *trail, + apr_pool_t *pool); + + +/* Add COPY_ID to the list of copies made under the Subversion + transaction TXN_NAME in FS as part of TRAIL. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +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); + + +/* Set the base root directory of TXN_NAME in FS to NEW_ID, as part of + TRAIL. Do any necessary temporary allocation in POOL. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +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); + + +/* Set a property NAME to VALUE on transaction TXN_NAME in FS as part + of TRAIL. Use POOL for any necessary allocations. + + Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a + transaction that has already been committed. */ +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); + + +/* These functions implement some of the calls in the FS loader + library's fs and txn vtables. */ + +svn_error_t *svn_fs_base__youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs, + apr_pool_t *pool); + +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); + +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); + +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); + +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_error_t *svn_fs_base__open_txn(svn_fs_txn_t **txn, svn_fs_t *fs, + const char *name, apr_pool_t *pool); + +svn_error_t *svn_fs_base__purge_txn(svn_fs_t *fs, const char *txn_id, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__list_transactions(apr_array_header_t **names_p, + svn_fs_t *fs, apr_pool_t *pool); + +svn_error_t *svn_fs_base__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool); + +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); + +svn_error_t *svn_fs_base__txn_proplist(apr_hash_t **table_p, + svn_fs_txn_t *txn, + apr_pool_t *pool); + +/* Helper func: variant of __txn_proplist that uses an existing TRAIL. + * TXN_ID identifies the transaction. + * *TABLE_P will be non-null upon success. + */ +svn_error_t *svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p, + const char *txn_id, + trail_t *trail); + +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); + +svn_error_t *svn_fs_base__change_txn_props(svn_fs_txn_t *txn, + const apr_array_header_t *props, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_REVS_TXNS_H */ diff --git a/subversion/libsvn_fs_base/trail.c b/subversion/libsvn_fs_base/trail.c new file mode 100644 index 0000000..8fdf9be --- /dev/null +++ b/subversion/libsvn_fs_base/trail.c @@ -0,0 +1,292 @@ +/* trail.c : backing out of aborted Berkeley DB 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. + * ==================================================================== + */ + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include <apr_pools.h> +#include "svn_pools.h" +#include "svn_fs.h" +#include "fs.h" +#include "err.h" +#include "bdb/bdb-err.h" +#include "bdb/bdb_compat.h" +#include "trail.h" +#include "../libsvn_fs/fs-loader.h" + + +#if defined(SVN_FS__TRAIL_DEBUG) + +struct trail_debug_t +{ + struct trail_debug_t *prev; + const char *table; + const char *op; +}; + +void +svn_fs_base__trail_debug(trail_t *trail, const char *table, const char *op) +{ + struct trail_debug_t *trail_debug; + + trail_debug = apr_palloc(trail->pool, sizeof(*trail_debug)); + trail_debug->prev = trail->trail_debug; + trail_debug->table = table; + trail_debug->op = op; + trail->trail_debug = trail_debug; +} + +static void +print_trail_debug(trail_t *trail, + const char *txn_body_fn_name, + const char *filename, int line) +{ + struct trail_debug_t *trail_debug; + + fprintf(stderr, "(%s, %s, %u, %u): ", + txn_body_fn_name, filename, line, trail->db_txn ? 1 : 0); + + trail_debug = trail->trail_debug; + while (trail_debug) + { + fprintf(stderr, "(%s, %s) ", trail_debug->table, trail_debug->op); + trail_debug = trail_debug->prev; + } + fprintf(stderr, "\n"); +} +#else +#define print_trail_debug(trail, txn_body_fn_name, filename, line) +#endif /* defined(SVN_FS__TRAIL_DEBUG) */ + + +static svn_error_t * +begin_trail(trail_t **trail_p, + svn_fs_t *fs, + svn_boolean_t use_txn, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + trail_t *trail = apr_pcalloc(pool, sizeof(*trail)); + + trail->pool = svn_pool_create(pool); + trail->fs = fs; + if (use_txn) + { + /* [*] + If we're already inside a trail operation, abort() -- this is + a coding problem (and will likely hang the repository anyway). */ + SVN_ERR_ASSERT(! bfd->in_txn_trail); + + SVN_ERR(BDB_WRAP(fs, N_("beginning Berkeley DB transaction"), + bfd->bdb->env->txn_begin(bfd->bdb->env, 0, + &trail->db_txn, 0))); + bfd->in_txn_trail = TRUE; + } + else + { + trail->db_txn = NULL; + } + + *trail_p = trail; + return SVN_NO_ERROR; +} + + +static svn_error_t * +abort_trail(trail_t *trail) +{ + svn_fs_t *fs = trail->fs; + base_fs_data_t *bfd = fs->fsap_data; + + if (trail->db_txn) + { + /* [**] + We have to reset the in_txn_trail flag *before* calling + DB_TXN->abort(). If we did it the other way around, the next + call to begin_trail() (e.g., as part of a txn retry) would + cause an abort, even though there's strictly speaking no + programming error involved (see comment [*] above). + + In any case, if aborting the txn fails, restarting it will + most likely fail for the same reason, and so it's better to + see the returned error than to abort. An obvious example is + when DB_TXN->abort() returns DB_RUNRECOVERY. */ + bfd->in_txn_trail = FALSE; + SVN_ERR(BDB_WRAP(fs, N_("aborting Berkeley DB transaction"), + trail->db_txn->abort(trail->db_txn))); + } + svn_pool_destroy(trail->pool); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +commit_trail(trail_t *trail) +{ + int db_err; + svn_fs_t *fs = trail->fs; + base_fs_data_t *bfd = fs->fsap_data; + + /* According to the example in the Berkeley DB manual, txn_commit + doesn't return DB_LOCK_DEADLOCK --- all deadlocks are reported + earlier. */ + if (trail->db_txn) + { + /* See comment [**] in abort_trail() above. + An error during txn commit will abort the transaction anyway. */ + bfd->in_txn_trail = FALSE; + SVN_ERR(BDB_WRAP(fs, N_("committing Berkeley DB transaction"), + trail->db_txn->commit(trail->db_txn, 0))); + } + + /* Do a checkpoint here, if enough has gone on. + The checkpoint parameters below are pretty arbitrary. Perhaps + there should be an svn_fs_berkeley_mumble function to set them. */ + db_err = bfd->bdb->env->txn_checkpoint(bfd->bdb->env, 1024, 5, 0); + + /* Pre-4.1 Berkeley documentation says: + + The DB_ENV->txn_checkpoint function returns a non-zero error + value on failure, 0 on success, and returns DB_INCOMPLETE if + there were pages that needed to be written to complete the + checkpoint but that DB_ENV->memp_sync was unable to write + immediately. + + It's safe to ignore DB_INCOMPLETE if we get it while + checkpointing. (Post-4.1 Berkeley doesn't have DB_INCOMPLETE + anymore, so it's not an issue there.) */ + if (db_err) + { +#if SVN_BDB_HAS_DB_INCOMPLETE + if (db_err != DB_INCOMPLETE) +#endif /* SVN_BDB_HAS_DB_INCOMPLETE */ + { + return svn_fs_bdb__wrap_db + (fs, "checkpointing after Berkeley DB transaction", db_err); + } + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +do_retry(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, trail_t *trail), + void *baton, + svn_boolean_t use_txn, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool, + const char *txn_body_fn_name, + const char *filename, + int line) +{ + for (;;) + { + trail_t *trail; + svn_error_t *svn_err, *err; + svn_boolean_t deadlocked = FALSE; + + SVN_ERR(begin_trail(&trail, fs, use_txn, pool)); + + /* Do the body of the transaction. */ + svn_err = (*txn_body)(baton, trail); + + if (! svn_err) + { + /* The transaction succeeded! Commit it. */ + SVN_ERR(commit_trail(trail)); + + if (use_txn) + print_trail_debug(trail, txn_body_fn_name, filename, line); + + /* If our caller doesn't want us to keep trail memory + around, destroy our subpool. */ + if (destroy_trail_pool) + svn_pool_destroy(trail->pool); + + return SVN_NO_ERROR; + } + + /* Search for a deadlock error on the stack. */ + for (err = svn_err; err; err = err->child) + if (err->apr_err == SVN_ERR_FS_BERKELEY_DB_DEADLOCK) + deadlocked = TRUE; + + /* Is this a real error, or do we just need to retry? */ + if (! deadlocked) + { + /* Ignore any error returns. The first error is more valuable. */ + svn_error_clear(abort_trail(trail)); + return svn_err; + } + + svn_error_clear(svn_err); + + /* We deadlocked. Abort the transaction, and try again. */ + SVN_ERR(abort_trail(trail)); + } +} + + +svn_error_t * +svn_fs_base__retry_debug(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool, + const char *txn_body_fn_name, + const char *filename, + int line) +{ + return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool, + txn_body_fn_name, filename, line); +} + + +#if defined(SVN_FS__TRAIL_DEBUG) +#undef svn_fs_base__retry_txn +#endif + +svn_error_t * +svn_fs_base__retry_txn(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool) +{ + return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool, + "unknown", "", 0); +} + + +svn_error_t * +svn_fs_base__retry(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool) +{ + return do_retry(fs, txn_body, baton, FALSE, destroy_trail_pool, pool, + NULL, NULL, 0); +} diff --git a/subversion/libsvn_fs_base/trail.h b/subversion/libsvn_fs_base/trail.h new file mode 100644 index 0000000..87fc313 --- /dev/null +++ b/subversion/libsvn_fs_base/trail.h @@ -0,0 +1,239 @@ +/* trail.h : internal interface to backing out of aborted Berkeley DB txns + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_TRAIL_H +#define SVN_LIBSVN_FS_TRAIL_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include <apr_pools.h> +#include "svn_fs.h" +#include "fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* "How do I get a trail object? All these functions in the + filesystem expect them, and I can't find a function that returns + one." + + Well, there isn't a function that returns a trail. All trails come + from svn_fs_base__retry_txn. Here's how to use that: + + When using Berkeley DB transactions to protect the integrity of a + database, there are several things you need to keep in mind: + + - Any Berkeley DB operation you perform as part of a Berkeley DB + transaction may return DB_LOCK_DEADLOCK, meaning that your + operation interferes with some other transaction in progress. + When this happens, you must abort the transaction, which undoes + all the changes you've made so far, and try it again. So every + piece of code you ever write to bang on the DB needs to be + wrapped up in a retry loop. + + - If, while you're doing your database operations, you also change + some in-memory data structures, then you may want to revert those + changes if the transaction deadlocks and needs to be retried. + + - If you get a `real' error (i.e., something other than + DB_LOCK_DEADLOCK), you must abort your DB transaction, to release + its locks and return the database to its previous state. + Similarly, you may want to unroll some changes you've made to + in-memory data structures. + + - Since a transaction insulates you from database changes made by + other processes, it's often possible to cache information about + database contents while the transaction lasts. However, this + cache may become stale once your transaction is over. So you may + need to clear your cache once the transaction completes, either + successfully or unsuccessfully. + + The `svn_fs_base__retry_txn' function and its friends help you manage + some of that, in one nice package. + + To use it, write your code in a function like this: + + static svn_error_t * + txn_body_do_my_thing (void *baton, + trail_t *trail) + { + ... + Do everything which needs to be protected by a Berkeley DB + transaction here. Use TRAIL->db_txn as your Berkeley DB + transaction, and do your allocation in TRAIL->pool. Pass + TRAIL on through to any functions which require one. + + If a Berkeley DB operation returns DB_LOCK_DEADLOCK, just + return that using the normal Subversion error mechanism + (using DB_ERR, for example); don't write a retry loop. If you + encounter some other kind of error, return it in the normal + fashion. + ... + } + + Now, call svn_fs_base__retry_txn, passing a pointer to your function as + an argument: + + err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool); + + This will simply invoke your function `txn_body_do_my_thing', + passing BATON through unchanged, and providing a fresh TRAIL + object, containing a pointer to the filesystem object, a Berkeley + DB transaction and an APR pool -- a subpool of POOL -- you should + use. + + If your function returns a Subversion error wrapping a Berkeley DB + DB_LOCK_DEADLOCK error, `svn_fs_base__retry_txn' will abort the trail's + Berkeley DB transaction for you (thus undoing any database changes + you've made), free the trail's subpool (thus undoing any allocation + you may have done), and try the whole thing again with a new trail, + containing a new Berkeley DB transaction and pool. + + If your function returns any other kind of Subversion error, + `svn_fs_base__retry_txn' will abort the trail's Berkeley DB transaction, + free the subpool, and return your error to its caller. + + If, heavens forbid, your function actually succeeds, returning + SVN_NO_ERROR, `svn_fs_base__retry_txn' commits the trail's Berkeley DB + transaction, thus making your DB changes permanent, leaves the + trail's pool alone so all the objects it contains are still + around (unless you request otherwise), and returns SVN_NO_ERROR. + + + Keep the amount of work done in a trail small. C-Mike Pilato said to me: + + I want to draw your attention to something that you may or may not realize + about designing for the BDB backend. The 'trail' objects are (generally) + representative of Berkeley DB transactions -- that part I'm sure you know. + But you might not realize the value of keeping transactions as small as + possible. Berkeley DB will accumulate locks (which I believe are + page-level, not as tight as row-level like you might hope) over the course + of a transaction, releasing those locks only at transaction commit/abort. + Berkeley DB backends are configured to have a maximum number of locks and + lockers allowed, and it's easier than you might think to hit the max-locks + thresholds (especially under high concurrency) and see an error (typically a + "Cannot allocate memory") result from that. + + For example, in [a loop] you are writing a bunch of rows to the + `changes' table. Could be 10. Could be 100,000. 100,000 writes and + associated locks might be a problem or it might not. But I use it as a way + to encourage you to think about reducing the amount of work you spend in any + one trail [...]. +*/ + +struct trail_t +{ + /* A Berkeley DB transaction. */ + DB_TXN *db_txn; + + /* The filesystem object with which this trail is associated. */ + svn_fs_t *fs; + + /* A pool to allocate things in as part of that transaction --- a + subpool of the one passed to `begin_trail'. We destroy this pool + if we abort the transaction, and leave it around otherwise. */ + apr_pool_t *pool; + +#if defined(SVN_FS__TRAIL_DEBUG) + struct trail_debug_t *trail_debug; +#endif +}; +typedef struct trail_t trail_t; + + +/* Try a Berkeley DB transaction repeatedly until it doesn't deadlock. + + That is: + - Begin a new Berkeley DB transaction, DB_TXN, in the filesystem FS. + - Allocate a subpool of POOL, TXN_POOL. + - Start a new trail, TRAIL, pointing to DB_TXN and TXN_POOL. + - Apply TXN_BODY to BATON and TRAIL. TXN_BODY should try to do + some series of DB operations which needs to be atomic, using + TRAIL->db_txn as the transaction, and TRAIL->pool for allocation. + If a DB operation deadlocks, or if any other kind of error + happens, TXN_BODY should simply return with an appropriate + svn_error_t, E. + - If TXN_BODY returns SVN_NO_ERROR, then commit the transaction, + run any completion functions, and return SVN_NO_ERROR. Do *not* + free TXN_POOL (unless DESTROY_TRAIL_POOL is set). + - If E is a Berkeley DB error indicating that a deadlock occurred, + abort the DB transaction and free TXN_POOL. Then retry the whole + thing from the top. + - If E is any other kind of error, free TXN_POOL and return E. + + One benefit of using this function is that it makes it easy to + ensure that whatever transactions a filesystem function starts, it + either aborts or commits before it returns. If we don't somehow + complete all our transactions, later operations could deadlock. */ +svn_error_t * +svn_fs_base__retry_txn(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, + trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool); + +svn_error_t * +svn_fs_base__retry_debug(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, + trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool, + const char *txn_body_fn_name, + const char *filename, + int line); + +#if defined(SVN_FS__TRAIL_DEBUG) +#define svn_fs_base__retry_txn(fs, txn_body, baton, destroy, pool) \ + svn_fs_base__retry_debug(fs, txn_body, baton, destroy, pool, \ + #txn_body, __FILE__, __LINE__) +#endif + + +/* Try an action repeatedly until it doesn't deadlock. This is + exactly like svn_fs_base__retry_txn() (whose documentation you really + should read) except that no Berkeley DB transaction is created. */ +svn_error_t *svn_fs_base__retry(svn_fs_t *fs, + svn_error_t *(*txn_body)(void *baton, + trail_t *trail), + void *baton, + svn_boolean_t destroy_trail_pool, + apr_pool_t *pool); + + +/* Record that OPeration is being done on TABLE in the TRAIL. */ +#if defined(SVN_FS__TRAIL_DEBUG) +void svn_fs_base__trail_debug(trail_t *trail, const char *table, + const char *op); +#else +#define svn_fs_base__trail_debug(trail, table, operation) +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_TRAIL_H */ diff --git a/subversion/libsvn_fs_base/tree.c b/subversion/libsvn_fs_base/tree.c new file mode 100644 index 0000000..e603af4 --- /dev/null +++ b/subversion/libsvn_fs_base/tree.c @@ -0,0 +1,5451 @@ +/* tree.c : tree-like filesystem, built on DAG filesystem + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + +/* The job of this layer is to take a filesystem with lots of node + sharing going on --- the real DAG filesystem as it appears in the + database --- and make it look and act like an ordinary tree + filesystem, with no sharing. + + We do just-in-time cloning: you can walk from some unfinished + transaction's root down into directories and files shared with + committed revisions; as soon as you try to change something, the + appropriate nodes get cloned (and parent directory entries updated) + invisibly, behind your back. Any other references you have to + nodes that have been cloned by other changes, even made by other + processes, are automatically updated to point to the right clones. */ + + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_path.h" +#include "svn_mergeinfo.h" +#include "svn_fs.h" +#include "svn_sorts.h" +#include "svn_checksum.h" +#include "fs.h" +#include "err.h" +#include "trail.h" +#include "node-rev.h" +#include "key-gen.h" +#include "dag.h" +#include "tree.h" +#include "lock.h" +#include "revs-txns.h" +#include "id.h" +#include "bdb/txn-table.h" +#include "bdb/rev-table.h" +#include "bdb/nodes-table.h" +#include "bdb/changes-table.h" +#include "bdb/copies-table.h" +#include "bdb/node-origins-table.h" +#include "bdb/miscellaneous-table.h" +#include "../libsvn_fs/fs-loader.h" +#include "private/svn_fspath.h" +#include "private/svn_fs_util.h" +#include "private/svn_mergeinfo_private.h" + + +/* ### I believe this constant will become internal to reps-strings.c. + ### see the comment in window_consumer() for more information. */ + +/* ### the comment also seems to need tweaking: the log file stuff + ### is no longer an issue... */ +/* Data written to the filesystem through the svn_fs_apply_textdelta() + interface is cached in memory until the end of the data stream, or + until a size trigger is hit. Define that trigger here (in bytes). + Setting the value to 0 will result in no filesystem buffering at + all. The value only really matters when dealing with file contents + bigger than the value itself. Above that point, large values here + allow the filesystem to buffer more data in memory before flushing + to the database, which increases memory usage but greatly decreases + the amount of disk access (and log-file generation) in database. + Smaller values will limit your overall memory consumption, but can + drastically hurt throughput by necessitating more write operations + to the database (which also generates more log-files). */ +#define WRITE_BUFFER_SIZE 512000 + +/* The maximum number of cache items to maintain in the node cache. */ +#define NODE_CACHE_MAX_KEYS 32 + + + +/* The root structure. */ + +/* Structure for svn_fs_root_t's node_cache hash values. */ +struct dag_node_cache_t +{ + dag_node_t *node; /* NODE to be cached. */ + int idx; /* Index into the keys array for this cache item's key. */ + apr_pool_t *pool; /* Pool in which NODE is allocated. */ +}; + + +typedef struct base_root_data_t +{ + + /* For revision roots, this is a dag node for the revision's root + directory. For transaction roots, we open the root directory + afresh every time, since the root may have been cloned, or + the transaction may have disappeared altogether. */ + dag_node_t *root_dir; + + /* Cache structures, for mapping const char * PATH to const + struct dag_node_cache_t * structures. + + ### Currently this is only used for revision roots. To be safe + for transaction roots, you must have the guarantee that there is + never more than a single transaction root per Subversion + transaction ever open at a given time -- having two roots open to + the same Subversion transaction would be a request for pain. + Also, you have to ensure that if a 'make_path_mutable()' fails for + any reason, you don't leave cached nodes for the portion of that + function that succeeded. In other words, this cache must never, + ever, lie. */ + apr_hash_t *node_cache; + const char *node_cache_keys[NODE_CACHE_MAX_KEYS]; + int node_cache_idx; +} base_root_data_t; + + +static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev, + dag_node_t *root_dir, + apr_pool_t *pool); + +static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn, + svn_revnum_t base_rev, apr_uint32_t flags, + apr_pool_t *pool); + + +/*** Node Caching in the Roots. ***/ + +/* Return NODE for PATH from ROOT's node cache, or NULL if the node + isn't cached. */ +static dag_node_t * +dag_node_cache_get(svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + base_root_data_t *brd = root->fsap_data; + struct dag_node_cache_t *cache_item; + + /* Assert valid input. */ + assert(*path == '/'); + + /* Only allow revision roots. */ + if (root->is_txn_root) + return NULL; + + /* Look in the cache for our desired item. */ + cache_item = svn_hash_gets(brd->node_cache, path); + if (cache_item) + return svn_fs_base__dag_dup(cache_item->node, pool); + + return NULL; +} + + +/* Add the NODE for PATH to ROOT's node cache. Callers should *NOT* + call this unless they are adding a currently un-cached item to the + cache, or are replacing the NODE for PATH with a new (different) + one. */ +static void +dag_node_cache_set(svn_fs_root_t *root, + const char *path, + dag_node_t *node) +{ + base_root_data_t *brd = root->fsap_data; + const char *cache_path; + apr_pool_t *cache_pool; + struct dag_node_cache_t *cache_item; + int num_keys = apr_hash_count(brd->node_cache); + + /* What? No POOL passed to this function? + + To ensure that our cache values live as long as the svn_fs_root_t + in which they are ultimately stored, and to allow us to free() + them individually without harming the rest, they are each + allocated from a subpool of ROOT's pool. We'll keep one subpool + around for each cache slot -- as we start expiring stuff + to make room for more entries, we'll re-use the expired thing's + pool. */ + + /* Assert valid input and state. */ + assert(*path == '/'); + assert((brd->node_cache_idx <= num_keys) + && (num_keys <= NODE_CACHE_MAX_KEYS)); + + /* Only allow revision roots. */ + if (root->is_txn_root) + return; + + /* Special case: the caller wants us to replace an existing cached + node with a new one. If the callers aren't mindless, this should + only happen when a node is made mutable under a transaction + root, and that only happens once under that root. So, we'll be a + little bit sloppy here, and count on callers doing the right + thing. */ + cache_item = svn_hash_gets(brd->node_cache, path); + if (cache_item) + { + /* ### This section is somehow broken. I don't know how, but it + ### is. And I don't want to spend any more time on it. So, + ### callers, use only revision root and don't try to update + ### an already-cached thing. -- cmpilato */ + SVN_ERR_MALFUNCTION_NO_RETURN(); + +#if 0 + int cache_index = cache_item->idx; + cache_path = brd->node_cache_keys[cache_index]; + cache_pool = cache_item->pool; + cache_item->node = svn_fs_base__dag_dup(node, cache_pool); + + /* Now, move the cache key reference to the end of the keys in + the keys array (unless it's already at the end). ### Yes, + it's a memmove(), but we're not talking about pages of memory + here. */ + if (cache_index != (num_keys - 1)) + { + int move_num = NODE_CACHE_MAX_KEYS - cache_index - 1; + memmove(brd->node_cache_keys + cache_index, + brd->node_cache_keys + cache_index + 1, + move_num * sizeof(const char *)); + cache_index = num_keys - 1; + brd->node_cache_keys[cache_index] = cache_path; + } + + /* Advance the cache pointers. */ + cache_item->idx = cache_index; + brd->node_cache_idx = (cache_index + 1) % NODE_CACHE_MAX_KEYS; + return; +#endif + } + + /* We're adding a new cache item. First, see if we have room for it + (otherwise, make some room). */ + if (apr_hash_count(brd->node_cache) == NODE_CACHE_MAX_KEYS) + { + /* No room. Expire the oldest thing. */ + cache_path = brd->node_cache_keys[brd->node_cache_idx]; + cache_item = svn_hash_gets(brd->node_cache, cache_path); + svn_hash_sets(brd->node_cache, cache_path, NULL); + cache_pool = cache_item->pool; + svn_pool_clear(cache_pool); + } + else + { + cache_pool = svn_pool_create(root->pool); + } + + /* Make the cache item, allocated in its own pool. */ + cache_item = apr_palloc(cache_pool, sizeof(*cache_item)); + cache_item->node = svn_fs_base__dag_dup(node, cache_pool); + cache_item->idx = brd->node_cache_idx; + cache_item->pool = cache_pool; + + /* Now add it to the cache. */ + cache_path = apr_pstrdup(cache_pool, path); + svn_hash_sets(brd->node_cache, cache_path, cache_item); + brd->node_cache_keys[brd->node_cache_idx] = cache_path; + + /* Advance the cache pointer. */ + brd->node_cache_idx = (brd->node_cache_idx + 1) % NODE_CACHE_MAX_KEYS; +} + + + + +/* Creating transaction and revision root nodes. */ + +struct txn_root_args +{ + svn_fs_root_t **root_p; + svn_fs_txn_t *txn; +}; + + +static svn_error_t * +txn_body_txn_root(void *baton, + trail_t *trail) +{ + struct txn_root_args *args = baton; + svn_fs_root_t **root_p = args->root_p; + svn_fs_txn_t *txn = args->txn; + svn_fs_t *fs = txn->fs; + const char *svn_txn_id = txn->id; + const svn_fs_id_t *root_id, *base_root_id; + svn_fs_root_t *root; + apr_hash_t *txnprops; + apr_uint32_t flags = 0; + + /* Verify that the transaction actually exists. */ + SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, + svn_txn_id, trail, trail->pool)); + + /* Look for special txn props that represent the 'flags' behavior of + the transaction. */ + SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, svn_txn_id, trail)); + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) + flags |= SVN_FS_TXN_CHECK_OOD; + + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) + flags |= SVN_FS_TXN_CHECK_LOCKS; + + root = make_txn_root(fs, svn_txn_id, txn->base_rev, flags, trail->pool); + + *root_p = root; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__txn_root(svn_fs_root_t **root_p, + svn_fs_txn_t *txn, + apr_pool_t *pool) +{ + svn_fs_root_t *root; + struct txn_root_args args; + + args.root_p = &root; + args.txn = txn; + SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_txn_root, &args, + FALSE, pool)); + + *root_p = root; + return SVN_NO_ERROR; +} + + +struct revision_root_args +{ + svn_fs_root_t **root_p; + svn_revnum_t rev; +}; + + +static svn_error_t * +txn_body_revision_root(void *baton, + trail_t *trail) +{ + struct revision_root_args *args = baton; + dag_node_t *root_dir; + svn_fs_root_t *root; + + SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, args->rev, + trail, trail->pool)); + root = make_revision_root(trail->fs, args->rev, root_dir, trail->pool); + + *args->root_p = root; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__revision_root(svn_fs_root_t **root_p, + svn_fs_t *fs, + svn_revnum_t rev, + apr_pool_t *pool) +{ + struct revision_root_args args; + svn_fs_root_t *root; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + args.root_p = &root; + args.rev = rev; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_root, &args, + FALSE, pool)); + + *root_p = root; + return SVN_NO_ERROR; +} + + + +/* Getting dag nodes for roots. */ + + +/* Set *NODE_P to a freshly opened dag node referring to the root + directory of ROOT, as part of TRAIL. */ +static svn_error_t * +root_node(dag_node_t **node_p, + svn_fs_root_t *root, + trail_t *trail, + apr_pool_t *pool) +{ + base_root_data_t *brd = root->fsap_data; + + if (! root->is_txn_root) + { + /* It's a revision root, so we already have its root directory + opened. */ + *node_p = svn_fs_base__dag_dup(brd->root_dir, pool); + return SVN_NO_ERROR; + } + else + { + /* It's a transaction root. Open a fresh copy. */ + return svn_fs_base__dag_txn_root(node_p, root->fs, root->txn, + trail, pool); + } +} + + +/* Set *NODE_P to a mutable root directory for ROOT, cloning if + necessary, as part of TRAIL. ROOT must be a transaction root. Use + ERROR_PATH in error messages. */ +static svn_error_t * +mutable_root_node(dag_node_t **node_p, + svn_fs_root_t *root, + const char *error_path, + trail_t *trail, + apr_pool_t *pool) +{ + if (root->is_txn_root) + return svn_fs_base__dag_clone_root(node_p, root->fs, root->txn, + trail, pool); + else + /* If it's not a transaction root, we can't change its contents. */ + return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path); +} + + + +/* Traversing directory paths. */ + +typedef enum copy_id_inherit_t +{ + copy_id_inherit_unknown = 0, + copy_id_inherit_self, + copy_id_inherit_parent, + copy_id_inherit_new + +} copy_id_inherit_t; + +/* A linked list representing the path from a node up to a root + directory. We use this for cloning, and for operations that need + to deal with both a node and its parent directory. For example, a + `delete' operation needs to know that the node actually exists, but + also needs to change the parent directory. */ +typedef struct parent_path_t +{ + + /* A node along the path. This could be the final node, one of its + parents, or the root. Every parent path ends with an element for + the root directory. */ + dag_node_t *node; + + /* The name NODE has in its parent directory. This is zero for the + root directory, which (obviously) has no name in its parent. */ + char *entry; + + /* The parent of NODE, or zero if NODE is the root directory. */ + struct parent_path_t *parent; + + /* The copy ID inheritance style. */ + copy_id_inherit_t copy_inherit; + + /* If copy ID inheritance style is copy_id_inherit_new, this is the + path which should be implicitly copied; otherwise, this is NULL. */ + const char *copy_src_path; + +} parent_path_t; + + +/* Return the FS path for the parent path chain object PARENT_PATH, + allocated in POOL. */ +static const char * +parent_path_path(parent_path_t *parent_path, + apr_pool_t *pool) +{ + const char *path_so_far = "/"; + if (parent_path->parent) + path_so_far = parent_path_path(parent_path->parent, pool); + return parent_path->entry + ? svn_fspath__join(path_so_far, parent_path->entry, pool) + : path_so_far; +} + + +/* Return the FS path for the parent path chain object CHILD relative + to its ANCESTOR in the same chain, allocated in POOL. */ +static const char * +parent_path_relpath(parent_path_t *child, + parent_path_t *ancestor, + apr_pool_t *pool) +{ + const char *path_so_far = ""; + parent_path_t *this_node = child; + while (this_node != ancestor) + { + assert(this_node != NULL); + path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool); + this_node = this_node->parent; + } + return path_so_far; +} + + +/* Choose a copy ID inheritance method *INHERIT_P to be used in the + event that immutable node CHILD in FS needs to be made mutable. If + the inheritance method is copy_id_inherit_new, also return a + *COPY_SRC_PATH on which to base the new copy ID (else return NULL + for that path). CHILD must have a parent (it cannot be the root + node). TXN_ID is the transaction in which these items might be + mutable. */ +static svn_error_t * +get_copy_inheritance(copy_id_inherit_t *inherit_p, + const char **copy_src_path, + svn_fs_t *fs, + parent_path_t *child, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *child_id, *parent_id; + const char *child_copy_id, *parent_copy_id; + const char *id_path = NULL; + + SVN_ERR_ASSERT(child && child->parent && txn_id); + + /* Initialize our return variables (default: self-inheritance). */ + *inherit_p = copy_id_inherit_self; + *copy_src_path = NULL; + + /* Initialize some convenience variables. */ + child_id = svn_fs_base__dag_get_id(child->node); + parent_id = svn_fs_base__dag_get_id(child->parent->node); + child_copy_id = svn_fs_base__id_copy_id(child_id); + parent_copy_id = svn_fs_base__id_copy_id(parent_id); + + /* Easy out: if this child is already mutable, we have nothing to do. */ + if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(child_id), txn_id) == 0) + return SVN_NO_ERROR; + + /* If the child and its parent are on the same branch, then the + child will inherit the copy ID of its parent when made mutable. + This is trivially detectable when the child and its parent have + the same copy ID. But that's not the sole indicator of + same-branchness. It might be the case that the parent was the + result of a copy, but the child has not yet been cloned for + mutability since that copy. Detection of this latter case + basically means making sure the copy IDs don't differ for some + other reason, such as that the child was the direct target of the + copy whose ID it has. There is a special case here, too -- if + the child's copy ID is the special ID "0", it can't have been the + target of any copy, and therefore must be on the same branch as + its parent. */ + if ((strcmp(child_copy_id, "0") == 0) + || (svn_fs_base__key_compare(child_copy_id, parent_copy_id) == 0)) + { + *inherit_p = copy_id_inherit_parent; + return SVN_NO_ERROR; + } + else + { + copy_t *copy; + SVN_ERR(svn_fs_bdb__get_copy(©, fs, child_copy_id, trail, pool)); + if (svn_fs_base__id_compare(copy->dst_noderev_id, child_id) == -1) + { + *inherit_p = copy_id_inherit_parent; + return SVN_NO_ERROR; + } + } + + /* If we get here, the child and its parent are not on speaking + terms -- there will be no parental inheritance handed down in + *this* generation. */ + + /* If the child was created at a different path than the one we are + expecting its clone to live, one of its parents must have been + created via a copy since the child was created. The child isn't + on the same branch as its parent (we caught those cases early); + it can't keep its current copy ID because there's been an + affecting copy (its clone won't be on the same branch as the + child is). That leaves only one course of action -- to assign + the child a brand new "soft" copy ID. */ + id_path = svn_fs_base__dag_get_created_path(child->node); + if (strcmp(id_path, parent_path_path(child, pool)) != 0) + { + *inherit_p = copy_id_inherit_new; + *copy_src_path = id_path; + return SVN_NO_ERROR; + } + + /* The node gets to keep its own ID. */ + return SVN_NO_ERROR; +} + + +/* Allocate a new parent_path_t node from POOL, referring to NODE, + ENTRY, PARENT, and COPY_ID. */ +static parent_path_t * +make_parent_path(dag_node_t *node, + char *entry, + parent_path_t *parent, + apr_pool_t *pool) +{ + parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path)); + parent_path->node = node; + parent_path->entry = entry; + parent_path->parent = parent; + parent_path->copy_inherit = copy_id_inherit_unknown; + parent_path->copy_src_path = NULL; + return parent_path; +} + + +/* Flags for open_path. */ +typedef enum open_path_flags_t { + + /* The last component of the PATH need not exist. (All parent + directories must exist, as usual.) If the last component doesn't + exist, simply leave the `node' member of the bottom parent_path + component zero. */ + open_path_last_optional = 1 + +} open_path_flags_t; + + +/* Open the node identified by PATH in ROOT, as part of TRAIL. Set + *PARENT_PATH_P to a path from the node up to ROOT, allocated in + TRAIL->pool. The resulting *PARENT_PATH_P value is guaranteed to + contain at least one element, for the root directory. + + If resulting *PARENT_PATH_P will eventually be made mutable and + modified, or if copy ID inheritance information is otherwise + needed, TXN_ID should be the ID of the mutability transaction. If + TXN_ID is NULL, no copy ID in heritance information will be + calculated for the *PARENT_PATH_P chain. + + If FLAGS & open_path_last_optional is zero, return the error + SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If + non-zero, require all the parent directories to exist as normal, + but if the final path component doesn't exist, simply return a path + whose bottom `node' member is zero. This option is useful for + callers that create new nodes --- we find the parent directory for + them, and tell them whether the entry exists already. + + NOTE: Public interfaces which only *read* from the filesystem + should not call this function directly, but should instead use + get_dag(). +*/ +static svn_error_t * +open_path(parent_path_t **parent_path_p, + svn_fs_root_t *root, + const char *path, + int flags, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + svn_fs_t *fs = root->fs; + dag_node_t *here; /* The directory we're currently looking at. */ + parent_path_t *parent_path; /* The path from HERE up to the root. */ + const char *rest; /* The portion of PATH we haven't traversed yet. */ + const char *canon_path = svn_fs__canonicalize_abspath(path, pool); + const char *path_so_far = "/"; + + /* Make a parent_path item for the root node, using its own current + copy id. */ + SVN_ERR(root_node(&here, root, trail, pool)); + parent_path = make_parent_path(here, 0, 0, pool); + parent_path->copy_inherit = copy_id_inherit_self; + + rest = canon_path + 1; /* skip the leading '/', it saves in iteration */ + + /* Whenever we are at the top of this loop: + - HERE is our current directory, + - ID is the node revision ID of HERE, + - REST is the path we're going to find in HERE, and + - PARENT_PATH includes HERE and all its parents. */ + for (;;) + { + const char *next; + char *entry; + dag_node_t *child; + + /* Parse out the next entry from the path. */ + entry = svn_fs__next_entry_name(&next, rest, pool); + + /* Calculate the path traversed thus far. */ + path_so_far = svn_fspath__join(path_so_far, entry, pool); + + if (*entry == '\0') + { + /* Given the behavior of svn_fs__next_entry_name(), this + happens when the path either starts or ends with a slash. + In either case, we stay put: the current directory stays + the same, and we add nothing to the parent path. */ + child = here; + } + else + { + copy_id_inherit_t inherit; + const char *copy_path = NULL; + svn_error_t *err = SVN_NO_ERROR; + dag_node_t *cached_node; + + /* If we found a directory entry, follow it. First, we + check our node cache, and, failing that, we hit the DAG + layer. */ + cached_node = dag_node_cache_get(root, path_so_far, pool); + if (cached_node) + child = cached_node; + else + err = svn_fs_base__dag_open(&child, here, entry, trail, pool); + + /* "file not found" requires special handling. */ + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + /* If this was the last path component, and the caller + said it was optional, then don't return an error; + just put a NULL node pointer in the path. */ + + svn_error_clear(err); + + if ((flags & open_path_last_optional) + && (! next || *next == '\0')) + { + parent_path = make_parent_path(NULL, entry, parent_path, + pool); + break; + } + else + { + /* Build a better error message than svn_fs_base__dag_open + can provide, giving the root and full path name. */ + return SVN_FS__NOT_FOUND(root, path); + } + } + + /* Other errors we return normally. */ + SVN_ERR(err); + + /* Now, make a parent_path item for CHILD. */ + parent_path = make_parent_path(child, entry, parent_path, pool); + if (txn_id) + { + SVN_ERR(get_copy_inheritance(&inherit, ©_path, + fs, parent_path, txn_id, + trail, pool)); + parent_path->copy_inherit = inherit; + parent_path->copy_src_path = apr_pstrdup(pool, copy_path); + } + + /* Cache the node we found (if it wasn't already cached). */ + if (! cached_node) + dag_node_cache_set(root, path_so_far, child); + } + + /* Are we finished traversing the path? */ + if (! next) + break; + + /* The path isn't finished yet; we'd better be in a directory. */ + if (svn_fs_base__dag_node_kind(child) != svn_node_dir) + SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far), + apr_psprintf(pool, _("Failure opening '%s'"), path)); + + rest = next; + here = child; + } + + *parent_path_p = parent_path; + return SVN_NO_ERROR; +} + + +/* Make the node referred to by PARENT_PATH mutable, if it isn't + already, as part of TRAIL. ROOT must be the root from which + PARENT_PATH descends. Clone any parent directories as needed. + Adjust the dag nodes in PARENT_PATH to refer to the clones. Use + ERROR_PATH in error messages. */ +static svn_error_t * +make_path_mutable(svn_fs_root_t *root, + parent_path_t *parent_path, + const char *error_path, + trail_t *trail, + apr_pool_t *pool) +{ + dag_node_t *cloned_node; + const char *txn_id = root->txn; + svn_fs_t *fs = root->fs; + + /* Is the node mutable already? */ + if (svn_fs_base__dag_check_mutable(parent_path->node, txn_id)) + return SVN_NO_ERROR; + + /* Are we trying to clone the root, or somebody's child node? */ + if (parent_path->parent) + { + const svn_fs_id_t *parent_id; + const svn_fs_id_t *node_id = svn_fs_base__dag_get_id(parent_path->node); + const char *copy_id = NULL; + const char *copy_src_path = parent_path->copy_src_path; + copy_id_inherit_t inherit = parent_path->copy_inherit; + const char *clone_path; + + /* We're trying to clone somebody's child. Make sure our parent + is mutable. */ + SVN_ERR(make_path_mutable(root, parent_path->parent, + error_path, trail, pool)); + + switch (inherit) + { + case copy_id_inherit_parent: + parent_id = svn_fs_base__dag_get_id(parent_path->parent->node); + copy_id = svn_fs_base__id_copy_id(parent_id); + break; + + case copy_id_inherit_new: + SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool)); + break; + + case copy_id_inherit_self: + copy_id = NULL; + break; + + case copy_id_inherit_unknown: + default: + SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID + inheritance data. */ + } + + /* Now make this node mutable. */ + clone_path = parent_path_path(parent_path->parent, pool); + SVN_ERR(svn_fs_base__dag_clone_child(&cloned_node, + parent_path->parent->node, + clone_path, + parent_path->entry, + copy_id, txn_id, + trail, pool)); + + /* If we just created a brand new copy ID, we need to store a + `copies' table entry for it, as well as a notation in the + transaction that should this transaction be terminated, our + new copy needs to be removed. */ + if (inherit == copy_id_inherit_new) + { + const svn_fs_id_t *new_node_id = + svn_fs_base__dag_get_id(cloned_node); + SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, copy_src_path, + svn_fs_base__id_txn_id(node_id), + new_node_id, + copy_kind_soft, trail, pool)); + SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, + trail, pool)); + } + } + else + { + /* We're trying to clone the root directory. */ + SVN_ERR(mutable_root_node(&cloned_node, root, error_path, trail, pool)); + } + + /* Update the PARENT_PATH link to refer to the clone. */ + parent_path->node = cloned_node; + + return SVN_NO_ERROR; +} + + +/* Walk up PARENT_PATH to the root of the tree, adjusting each node's + mergeinfo count by COUNT_DELTA as part of Subversion transaction + TXN_ID and TRAIL. Use POOL for allocations. */ +static svn_error_t * +adjust_parent_mergeinfo_counts(parent_path_t *parent_path, + apr_int64_t count_delta, + const char *txn_id, + trail_t *trail, + apr_pool_t *pool) +{ + apr_pool_t *iterpool; + parent_path_t *pp = parent_path; + + if (count_delta == 0) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(pool); + + while (pp) + { + svn_pool_clear(iterpool); + SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(pp->node, count_delta, + txn_id, trail, + iterpool)); + pp = pp->parent; + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Open the node identified by PATH in ROOT, as part of TRAIL. Set + *DAG_NODE_P to the node we find, allocated in TRAIL->pool. Return + the error SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */ +static svn_error_t * +get_dag(dag_node_t **dag_node_p, + svn_fs_root_t *root, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + parent_path_t *parent_path; + dag_node_t *node = NULL; + + /* Canonicalize the input PATH. */ + path = svn_fs__canonicalize_abspath(path, pool); + + /* If ROOT is a revision root, we'll look for the DAG in our cache. */ + node = dag_node_cache_get(root, path, pool); + if (! node) + { + /* Call open_path with no flags, as we want this to return an error + if the node for which we are searching doesn't exist. */ + SVN_ERR(open_path(&parent_path, root, path, 0, NULL, trail, pool)); + node = parent_path->node; + + /* No need to cache our find -- open_path() will do that for us. */ + } + + *dag_node_p = node; + return SVN_NO_ERROR; +} + + + +/* Populating the `changes' table. */ + +/* Add a change to the changes table in FS, keyed on transaction id + TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on + PATH (whose node revision id is--or was, in the case of a + deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs + occurred. Do all this as part of TRAIL. */ +static svn_error_t * +add_change(svn_fs_t *fs, + const char *txn_id, + const char *path, + const svn_fs_id_t *noderev_id, + svn_fs_path_change_kind_t change_kind, + svn_boolean_t text_mod, + svn_boolean_t prop_mod, + trail_t *trail, + apr_pool_t *pool) +{ + change_t change; + change.path = svn_fs__canonicalize_abspath(path, pool); + change.noderev_id = noderev_id; + change.kind = change_kind; + change.text_mod = text_mod; + change.prop_mod = prop_mod; + return svn_fs_bdb__changes_add(fs, txn_id, &change, trail, pool); +} + + + +/* Generic node operations. */ + + +struct node_id_args { + const svn_fs_id_t **id_p; + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_node_id(void *baton, trail_t *trail) +{ + struct node_id_args *args = baton; + dag_node_t *node; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + *args->id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(node), + trail->pool); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_node_id(const svn_fs_id_t **id_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + base_root_data_t *brd = root->fsap_data; + + if (! root->is_txn_root + && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0')))) + { + /* Optimize the case where we don't need any db access at all. + The root directory ("" or "/") node is stored in the + svn_fs_root_t object, and never changes when it's a revision + root, so we can just reach in and grab it directly. */ + *id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(brd->root_dir), + pool); + } + else + { + const svn_fs_id_t *id; + struct node_id_args args; + + args.id_p = &id; + args.root = root; + args.path = path; + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_id, &args, + FALSE, pool)); + *id_p = id; + } + return SVN_NO_ERROR; +} + + +struct node_created_rev_args { + svn_revnum_t revision; + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_node_created_rev(void *baton, trail_t *trail) +{ + struct node_created_rev_args *args = baton; + dag_node_t *node; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + return svn_fs_base__dag_get_revision(&(args->revision), node, + trail, trail->pool); +} + + +static svn_error_t * +base_node_created_rev(svn_revnum_t *revision, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct node_created_rev_args args; + + args.revision = SVN_INVALID_REVNUM; + args.root = root; + args.path = path; + SVN_ERR(svn_fs_base__retry_txn + (root->fs, txn_body_node_created_rev, &args, TRUE, pool)); + *revision = args.revision; + return SVN_NO_ERROR; +} + + +struct node_created_path_args { + const char **created_path; + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_node_created_path(void *baton, trail_t *trail) +{ + struct node_created_path_args *args = baton; + dag_node_t *node; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + *args->created_path = svn_fs_base__dag_get_created_path(node); + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_node_created_path(const char **created_path, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct node_created_path_args args; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + args.created_path = created_path; + args.root = root; + args.path = path; + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_created_path, &args, + FALSE, scratch_pool)); + if (*created_path) + *created_path = apr_pstrdup(pool, *created_path); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + + +struct node_kind_args { + const svn_fs_id_t *id; + svn_node_kind_t kind; /* OUT parameter */ +}; + + +static svn_error_t * +txn_body_node_kind(void *baton, trail_t *trail) +{ + struct node_kind_args *args = baton; + dag_node_t *node; + + SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id, + trail, trail->pool)); + args->kind = svn_fs_base__dag_node_kind(node); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +node_kind(svn_node_kind_t *kind_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct node_kind_args args; + const svn_fs_id_t *node_id; + + /* Get the node id. */ + SVN_ERR(base_node_id(&node_id, root, path, pool)); + + /* Use the node id to get the real kind. */ + args.id = node_id; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_kind, &args, + TRUE, pool)); + + *kind_p = args.kind; + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_check_path(svn_node_kind_t *kind_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + svn_error_t *err = node_kind(kind_p, root, path, pool); + if (err && + ((err->apr_err == SVN_ERR_FS_NOT_FOUND) + || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + *kind_p = svn_node_none; + } + + return svn_error_trace(err); +} + + +struct node_prop_args +{ + svn_string_t **value_p; + svn_fs_root_t *root; + const char *path; + const char *propname; +}; + + +static svn_error_t * +txn_body_node_prop(void *baton, + trail_t *trail) +{ + struct node_prop_args *args = baton; + dag_node_t *node; + apr_hash_t *proplist; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, + trail, trail->pool)); + *(args->value_p) = NULL; + if (proplist) + *(args->value_p) = svn_hash_gets(proplist, args->propname); + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_node_prop(svn_string_t **value_p, + svn_fs_root_t *root, + const char *path, + const char *propname, + apr_pool_t *pool) +{ + struct node_prop_args args; + svn_string_t *value; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + args.value_p = &value; + args.root = root; + args.path = path; + args.propname = propname; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_prop, &args, + FALSE, scratch_pool)); + *value_p = value ? svn_string_dup(value, pool) : NULL; + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + + +struct node_proplist_args { + apr_hash_t **table_p; + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_node_proplist(void *baton, trail_t *trail) +{ + struct node_proplist_args *args = baton; + dag_node_t *node; + apr_hash_t *proplist; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, + trail, trail->pool)); + *args->table_p = proplist ? proplist : apr_hash_make(trail->pool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_node_proplist(apr_hash_t **table_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + apr_hash_t *table; + struct node_proplist_args args; + + args.table_p = &table; + args.root = root; + args.path = path; + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_proplist, &args, + FALSE, pool)); + + *table_p = table; + return SVN_NO_ERROR; +} + + +struct change_node_prop_args { + svn_fs_root_t *root; + const char *path; + const char *name; + const svn_string_t *value; +}; + + +static svn_error_t * +txn_body_change_node_prop(void *baton, + trail_t *trail) +{ + struct change_node_prop_args *args = baton; + parent_path_t *parent_path; + apr_hash_t *proplist; + const char *txn_id = args->root->txn; + base_fs_data_t *bfd = trail->fs->fsap_data; + + SVN_ERR(open_path(&parent_path, args->root, args->path, 0, txn_id, + trail, trail->pool)); + + /* Check to see if path is locked; if so, check that we can use it. + Notice that we're doing this non-recursively, regardless of node kind. */ + if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + SVN_ERR(svn_fs_base__allow_locked_operation + (args->path, FALSE, trail, trail->pool)); + + SVN_ERR(make_path_mutable(args->root, parent_path, args->path, + trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, parent_path->node, + trail, trail->pool)); + + /* If there's no proplist, but we're just deleting a property, exit now. */ + if ((! proplist) && (! args->value)) + return SVN_NO_ERROR; + + /* Now, if there's no proplist, we know we need to make one. */ + if (! proplist) + proplist = apr_hash_make(trail->pool); + + /* Set the property. */ + svn_hash_sets(proplist, args->name, args->value); + + /* Overwrite the node's proplist. */ + SVN_ERR(svn_fs_base__dag_set_proplist(parent_path->node, proplist, + txn_id, trail, trail->pool)); + + /* If this was a change to the mergeinfo property, and our version + of the filesystem cares, we have some extra recording to do. + + ### If the format *doesn't* support mergeinfo recording, should + ### we fuss about attempts to change the svn:mergeinfo property + ### in any way save to delete it? */ + if ((bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT) + && (strcmp(args->name, SVN_PROP_MERGEINFO) == 0)) + { + svn_boolean_t had_mergeinfo, has_mergeinfo = args->value != NULL; + + /* First, note on our node that it has mergeinfo. */ + SVN_ERR(svn_fs_base__dag_set_has_mergeinfo(parent_path->node, + has_mergeinfo, + &had_mergeinfo, txn_id, + trail, trail->pool)); + + /* If this is a change from the old state, we need to update our + node's parents' mergeinfo counts by a factor of 1. */ + if (parent_path->parent && ((! had_mergeinfo) != (! has_mergeinfo))) + SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent, + has_mergeinfo ? 1 : -1, + txn_id, trail, trail->pool)); + } + + /* Make a record of this modification in the changes table. */ + return add_change(args->root->fs, txn_id, + args->path, svn_fs_base__dag_get_id(parent_path->node), + svn_fs_path_change_modify, FALSE, TRUE, trail, + trail->pool); +} + + +static svn_error_t * +base_change_node_prop(svn_fs_root_t *root, + const char *path, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct change_node_prop_args args; + + if (! root->is_txn_root) + return SVN_FS__NOT_TXN(root); + + args.root = root; + args.path = path; + args.name = name; + args.value = value; + return svn_fs_base__retry_txn(root->fs, txn_body_change_node_prop, &args, + TRUE, pool); +} + + +struct things_changed_args +{ + svn_boolean_t *changed_p; + svn_fs_root_t *root1; + svn_fs_root_t *root2; + const char *path1; + const char *path2; + apr_pool_t *pool; +}; + + +static svn_error_t * +txn_body_props_changed(void *baton, trail_t *trail) +{ + struct things_changed_args *args = baton; + dag_node_t *node1, *node2; + + SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool)); + SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool)); + return svn_fs_base__things_different(args->changed_p, NULL, + node1, node2, trail, trail->pool); +} + + +static svn_error_t * +base_props_changed(svn_boolean_t *changed_p, + svn_fs_root_t *root1, + const char *path1, + svn_fs_root_t *root2, + const char *path2, + apr_pool_t *pool) +{ + struct things_changed_args args; + + /* Check that roots are in the same fs. */ + if (root1->fs != root2->fs) + return svn_error_create + (SVN_ERR_FS_GENERAL, NULL, + _("Cannot compare property value between two different filesystems")); + + args.root1 = root1; + args.root2 = root2; + args.path1 = path1; + args.path2 = path2; + args.changed_p = changed_p; + args.pool = pool; + + return svn_fs_base__retry_txn(root1->fs, txn_body_props_changed, &args, + TRUE, pool); +} + + + +/* Miscellaneous table handling */ + +struct miscellaneous_set_args +{ + const char *key; + const char *val; +}; + +static svn_error_t * +txn_body_miscellaneous_set(void *baton, trail_t *trail) +{ + struct miscellaneous_set_args *msa = baton; + + return svn_fs_bdb__miscellaneous_set(trail->fs, msa->key, msa->val, trail, + trail->pool); +} + +svn_error_t * +svn_fs_base__miscellaneous_set(svn_fs_t *fs, + const char *key, + const char *val, + apr_pool_t *pool) +{ + struct miscellaneous_set_args msa; + msa.key = key; + msa.val = val; + + return svn_fs_base__retry_txn(fs, txn_body_miscellaneous_set, &msa, + TRUE, pool); +} + +struct miscellaneous_get_args +{ + const char *key; + const char **val; +}; + +static svn_error_t * +txn_body_miscellaneous_get(void *baton, trail_t *trail) +{ + struct miscellaneous_get_args *mga = baton; + return svn_fs_bdb__miscellaneous_get(mga->val, trail->fs, mga->key, trail, + trail->pool); +} + +svn_error_t * +svn_fs_base__miscellaneous_get(const char **val, + svn_fs_t *fs, + const char *key, + apr_pool_t *pool) +{ + struct miscellaneous_get_args mga; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + mga.key = key; + mga.val = val; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_miscellaneous_get, &mga, + FALSE, scratch_pool)); + if (*val) + *val = apr_pstrdup(pool, *val); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + + + +/* Getting a directory's entries */ + + +struct dir_entries_args +{ + apr_hash_t **table_p; + svn_fs_root_t *root; + const char *path; +}; + + +/* *(BATON->table_p) will never be NULL on successful return */ +static svn_error_t * +txn_body_dir_entries(void *baton, + trail_t *trail) +{ + struct dir_entries_args *args = baton; + dag_node_t *node; + apr_hash_t *entries; + + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + + /* Get the entries for PARENT_PATH. */ + SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool)); + + /* Potentially initialize the return value to an empty hash. */ + *args->table_p = entries ? entries : apr_hash_make(trail->pool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_dir_entries(apr_hash_t **table_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct dir_entries_args args; + apr_pool_t *iterpool; + apr_hash_t *table; + svn_fs_t *fs = root->fs; + apr_hash_index_t *hi; + + args.table_p = &table; + args.root = root; + args.path = path; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_dir_entries, &args, + FALSE, pool)); + + iterpool = svn_pool_create(pool); + + /* Add in the kind data. */ + for (hi = apr_hash_first(pool, table); hi; hi = apr_hash_next(hi)) + { + svn_fs_dirent_t *entry; + struct node_kind_args nk_args; + void *val; + + svn_pool_clear(iterpool); + + /* KEY will be the entry name in ancestor (about which we + simply don't care), VAL the dirent. */ + apr_hash_this(hi, NULL, NULL, &val); + entry = val; + nk_args.id = entry->id; + + /* We don't need to have the retry function destroy the trail + pool because we're already doing that via the use of an + iteration pool. */ + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_node_kind, &nk_args, + FALSE, iterpool)); + entry->kind = nk_args.kind; + } + + svn_pool_destroy(iterpool); + + *table_p = table; + return SVN_NO_ERROR; +} + + + +/* Merges and commits. */ + + +struct deltify_committed_args +{ + svn_fs_t *fs; /* the filesystem */ + svn_revnum_t rev; /* revision just committed */ + const char *txn_id; /* transaction just committed */ +}; + + +struct txn_deltify_args +{ + /* The transaction ID whose nodes are being deltified. */ + const char *txn_id; + + /* The target is what we're deltifying. */ + const svn_fs_id_t *tgt_id; + + /* The base is what we're deltifying against. It's not necessarily + the "next" revision of the node; skip deltas mean we sometimes + deltify against a successor many generations away. This may be + NULL, in which case we'll avoid deltification and simply index + TGT_ID's data checksum. */ + const svn_fs_id_t *base_id; + + /* We only deltify props for directories. + ### Didn't we try removing this horrid little optimization once? + ### What was the result? I would have thought that skip deltas + ### mean directory undeltification is cheap enough now. */ + svn_boolean_t is_dir; +}; + + +static svn_error_t * +txn_body_txn_deltify(void *baton, trail_t *trail) +{ + struct txn_deltify_args *args = baton; + dag_node_t *tgt_node, *base_node; + base_fs_data_t *bfd = trail->fs->fsap_data; + + SVN_ERR(svn_fs_base__dag_get_node(&tgt_node, trail->fs, args->tgt_id, + trail, trail->pool)); + /* If we have something to deltify against, do so. */ + if (args->base_id) + { + SVN_ERR(svn_fs_base__dag_get_node(&base_node, trail->fs, args->base_id, + trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_deltify(tgt_node, base_node, args->is_dir, + args->txn_id, trail, trail->pool)); + } + + /* If we support rep sharing, and this isn't a directory, record a + mapping of TGT_NODE's data checksum to its representation key. */ + if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) + SVN_ERR(svn_fs_base__dag_index_checksums(tgt_node, trail, trail->pool)); + + return SVN_NO_ERROR; +} + + +struct txn_pred_count_args +{ + const svn_fs_id_t *id; + int pred_count; +}; + + +static svn_error_t * +txn_body_pred_count(void *baton, trail_t *trail) +{ + node_revision_t *noderev; + struct txn_pred_count_args *args = baton; + + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, trail->fs, + args->id, trail, trail->pool)); + args->pred_count = noderev->predecessor_count; + return SVN_NO_ERROR; +} + + +struct txn_pred_id_args +{ + const svn_fs_id_t *id; /* The node id whose predecessor we want. */ + const svn_fs_id_t *pred_id; /* The returned predecessor id. */ + apr_pool_t *pool; /* The pool in which to allocate pred_id. */ +}; + + +static svn_error_t * +txn_body_pred_id(void *baton, trail_t *trail) +{ + node_revision_t *nr; + struct txn_pred_id_args *args = baton; + + SVN_ERR(svn_fs_bdb__get_node_revision(&nr, trail->fs, args->id, + trail, trail->pool)); + if (nr->predecessor_id) + args->pred_id = svn_fs_base__id_copy(nr->predecessor_id, args->pool); + else + args->pred_id = NULL; + + return SVN_NO_ERROR; +} + + +/* Deltify PATH in ROOT's predecessor iff PATH is mutable under TXN_ID + in FS. If PATH is a mutable directory, recurse. + + NODE_ID is the node revision ID for PATH in ROOT, or NULL if that + value isn't known. KIND is the node kind for PATH in ROOT, or + svn_node_unknown is the kind isn't known. + + Use POOL for necessary allocations. */ +static svn_error_t * +deltify_mutable(svn_fs_t *fs, + svn_fs_root_t *root, + const char *path, + const svn_fs_id_t *node_id, + svn_node_kind_t kind, + const char *txn_id, + apr_pool_t *pool) +{ + const svn_fs_id_t *id = node_id; + apr_hash_t *entries = NULL; + struct txn_deltify_args td_args; + base_fs_data_t *bfd = fs->fsap_data; + + /* Get the ID for PATH under ROOT if it wasn't provided. */ + if (! node_id) + SVN_ERR(base_node_id(&id, root, path, pool)); + + /* Check for mutability. Not mutable? Go no further. This is safe + to do because for items in the tree to be mutable, their parent + dirs must also be mutable. Therefore, if a directory is not + mutable under TXN_ID, its children cannot be. */ + if (strcmp(svn_fs_base__id_txn_id(id), txn_id)) + return SVN_NO_ERROR; + + /* Is this a directory? */ + if (kind == svn_node_unknown) + SVN_ERR(base_check_path(&kind, root, path, pool)); + + /* If this is a directory, read its entries. */ + if (kind == svn_node_dir) + SVN_ERR(base_dir_entries(&entries, root, path, pool)); + + /* If there are entries, recurse on 'em. */ + if (entries) + { + apr_pool_t *subpool = svn_pool_create(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + /* KEY will be the entry name, VAL the dirent */ + const void *key; + void *val; + svn_fs_dirent_t *entry; + svn_pool_clear(subpool); + apr_hash_this(hi, &key, NULL, &val); + entry = val; + SVN_ERR(deltify_mutable(fs, root, + svn_fspath__join(path, key, subpool), + entry->id, entry->kind, txn_id, subpool)); + } + + svn_pool_destroy(subpool); + } + + /* Index ID's data checksum. */ + td_args.txn_id = txn_id; + td_args.tgt_id = id; + td_args.base_id = NULL; + td_args.is_dir = (kind == svn_node_dir); + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args, + TRUE, pool)); + + /* Finally, deltify old data against this node. */ + { + /* Prior to 1.6, we use the following algorithm to deltify nodes: + + Redeltify predecessor node-revisions of the one we added. The + idea is to require at most 2*lg(N) deltas to be applied to get + to any node-revision in a chain of N predecessors. We do this + using a technique derived from skip lists: + + - Always redeltify the immediate parent + + - If the number of predecessors is divisible by 2, + redeltify the revision two predecessors back + + - If the number of predecessors is divisible by 4, + redeltify the revision four predecessors back + + ... and so on. + + That's the theory, anyway. Unfortunately, if we strictly + follow that theory we get a bunch of overhead up front and no + great benefit until the number of predecessors gets large. So, + stop at redeltifying the parent if the number of predecessors + is less than 32, and also skip the second level (redeltifying + two predecessors back), since that doesn't help much. Also, + don't redeltify the oldest node-revision; it's potentially + expensive and doesn't help retrieve any other revision. + (Retrieving the oldest node-revision will still be fast, just + not as blindingly so.) + + For 1.6 and beyond, we just deltify the current node against its + predecessors, using skip deltas similar to the way FSFS does. */ + + int pred_count; + const svn_fs_id_t *pred_id; + struct txn_pred_count_args tpc_args; + apr_pool_t *subpools[2]; + int active_subpool = 0; + svn_revnum_t forward_delta_rev = 0; + + tpc_args.id = id; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_count, &tpc_args, + TRUE, pool)); + pred_count = tpc_args.pred_count; + + /* If nothing to deltify, then we're done. */ + if (pred_count == 0) + return SVN_NO_ERROR; + + subpools[0] = svn_pool_create(pool); + subpools[1] = svn_pool_create(pool); + + /* If we support the 'miscellaneous' table, check it to see if + there is a point in time before which we don't want to do + deltification. */ + /* ### FIXME: I think this is an unnecessary restriction. We + ### should be able to do something meaningful for most + ### deltification requests -- what that is depends on the + ### directory of the deltas for that revision, though. */ + if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT) + { + const char *val; + SVN_ERR(svn_fs_base__miscellaneous_get + (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool)); + if (val) + SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL)); + } + + if (bfd->format >= SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT + && forward_delta_rev <= root->rev) + { + /**** FORWARD DELTA STORAGE ****/ + + /* Decide which predecessor to deltify against. 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. */ + pred_count = pred_count & (pred_count - 1); + + /* Walk back a number of predecessors equal to the difference between + pred_count and the original predecessor count. (For example, if + the node has ten predecessors and we want the eighth node, walk back + two predecessors. */ + pred_id = id; + + /* We need to use two alternating pools because the id used in the + call to txn_body_pred_id is allocated by the previous inner + loop iteration. If we would clear the pool each iteration we + would free the previous result. */ + while ((pred_count++) < tpc_args.pred_count) + { + struct txn_pred_id_args tpi_args; + + active_subpool = !active_subpool; + svn_pool_clear(subpools[active_subpool]); + + tpi_args.id = pred_id; + tpi_args.pool = subpools[active_subpool]; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &tpi_args, + FALSE, subpools[active_subpool])); + pred_id = tpi_args.pred_id; + + if (pred_id == NULL) + return svn_error_create + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt DB: faulty predecessor count")); + + } + + /* Finally, do the deltification. */ + td_args.txn_id = txn_id; + td_args.tgt_id = id; + td_args.base_id = pred_id; + td_args.is_dir = (kind == svn_node_dir); + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args, + TRUE, subpools[active_subpool])); + } + else + { + int nlevels, lev, count; + + /**** REVERSE DELTA STORAGE ****/ + + /* Decide how many predecessors to redeltify. To save overhead, + don't redeltify anything but the immediate predecessor if there + are less than 32 predecessors. */ + nlevels = 1; + if (pred_count >= 32) + { + while (pred_count % 2 == 0) + { + pred_count /= 2; + nlevels++; + } + + /* Don't redeltify the oldest revision. */ + if (1 << (nlevels - 1) == pred_count) + nlevels--; + } + + /* Redeltify the desired number of predecessors. */ + count = 0; + pred_id = id; + + /* We need to use two alternating pools because the id used in the + call to txn_body_pred_id is allocated by the previous inner + loop iteration. If we would clear the pool each iteration we + would free the previous result. */ + for (lev = 0; lev < nlevels; lev++) + { + /* To save overhead, skip the second level (that is, never + redeltify the node-revision two predecessors back). */ + if (lev == 1) + continue; + + /* Note that COUNT is not reset between levels, and neither is + PREDNODE; we just keep counting from where we were up to + where we're supposed to get. */ + while (count < (1 << lev)) + { + struct txn_pred_id_args tpi_args; + + active_subpool = !active_subpool; + svn_pool_clear(subpools[active_subpool]); + + tpi_args.id = pred_id; + tpi_args.pool = subpools[active_subpool]; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, + &tpi_args, FALSE, + subpools[active_subpool])); + pred_id = tpi_args.pred_id; + + if (pred_id == NULL) + return svn_error_create + (SVN_ERR_FS_CORRUPT, 0, + _("Corrupt DB: faulty predecessor count")); + + count++; + } + + /* Finally, do the deltification. */ + td_args.txn_id = NULL; /* Don't require mutable reps */ + td_args.tgt_id = pred_id; + td_args.base_id = id; + td_args.is_dir = (kind == svn_node_dir); + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args, + TRUE, subpools[active_subpool])); + + } + } + + svn_pool_destroy(subpools[0]); + svn_pool_destroy(subpools[1]); + } + + return SVN_NO_ERROR; +} + + +struct get_root_args +{ + svn_fs_root_t *root; + dag_node_t *node; +}; + + +/* Set ARGS->node to the root node of ARGS->root. */ +static svn_error_t * +txn_body_get_root(void *baton, trail_t *trail) +{ + struct get_root_args *args = baton; + return get_dag(&(args->node), args->root, "", trail, trail->pool); +} + + + +static svn_error_t * +update_ancestry(svn_fs_t *fs, + const svn_fs_id_t *source_id, + const svn_fs_id_t *target_id, + const char *txn_id, + const char *target_path, + int source_pred_count, + trail_t *trail, + apr_pool_t *pool) +{ + node_revision_t *noderev; + + /* Set target's predecessor-id to source_id. */ + if (strcmp(svn_fs_base__id_txn_id(target_id), txn_id)) + return svn_error_createf + (SVN_ERR_FS_NOT_MUTABLE, NULL, + _("Unexpected immutable node at '%s'"), target_path); + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, target_id, + trail, pool)); + noderev->predecessor_id = source_id; + noderev->predecessor_count = source_pred_count; + if (noderev->predecessor_count != -1) + noderev->predecessor_count++; + return svn_fs_bdb__put_node_revision(fs, target_id, noderev, trail, pool); +} + + +/* Set the contents of CONFLICT_PATH to PATH, and return an + SVN_ERR_FS_CONFLICT error that indicates that there was a conflict + at PATH. Perform all allocations in POOL (except the allocation of + CONFLICT_PATH, which should be handled outside this function). */ +static svn_error_t * +conflict_err(svn_stringbuf_t *conflict_path, + const char *path) +{ + svn_stringbuf_set(conflict_path, path); + return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, + _("Conflict at '%s'"), path); +} + + +/* Merge changes between ANCESTOR and SOURCE into TARGET as part of + * TRAIL. ANCESTOR and TARGET must be distinct node revisions. + * TARGET_PATH should correspond to TARGET's full path in its + * filesystem, and is used for reporting conflict location. + * + * SOURCE, TARGET, and ANCESTOR are generally directories; this + * function recursively merges the directories' contents. If any are + * files, this function simply returns an error whenever SOURCE, + * TARGET, and ANCESTOR are all distinct node revisions. + * + * If there are differences between ANCESTOR and SOURCE that conflict + * with changes between ANCESTOR and TARGET, this function returns an + * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the + * conflicting node in TARGET, with TARGET_PATH prepended as a path. + * + * If there are no conflicting differences, CONFLICT_P is updated to + * the empty string. + * + * CONFLICT_P must point to a valid svn_stringbuf_t. + * + * Do any necessary temporary allocation in POOL. + */ +static svn_error_t * +merge(svn_stringbuf_t *conflict_p, + const char *target_path, + dag_node_t *target, + dag_node_t *source, + dag_node_t *ancestor, + const char *txn_id, + apr_int64_t *mergeinfo_increment_out, + trail_t *trail, + apr_pool_t *pool) +{ + const svn_fs_id_t *source_id, *target_id, *ancestor_id; + apr_hash_t *s_entries, *t_entries, *a_entries; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + svn_fs_t *fs; + int pred_count; + apr_int64_t mergeinfo_increment = 0; + base_fs_data_t *bfd = trail->fs->fsap_data; + + /* Make sure everyone comes from the same filesystem. */ + fs = svn_fs_base__dag_get_fs(ancestor); + if ((fs != svn_fs_base__dag_get_fs(source)) + || (fs != svn_fs_base__dag_get_fs(target))) + { + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Bad merge; ancestor, source, and target not all in same fs")); + } + + /* We have the same fs, now check it. */ + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + source_id = svn_fs_base__dag_get_id(source); + target_id = svn_fs_base__dag_get_id(target); + ancestor_id = svn_fs_base__dag_get_id(ancestor); + + /* It's improper to call this function with ancestor == target. */ + if (svn_fs_base__id_eq(ancestor_id, target_id)) + { + svn_string_t *id_str = svn_fs_base__id_unparse(target_id, pool); + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, + _("Bad merge; target '%s' has id '%s', same as ancestor"), + target_path, id_str->data); + } + + svn_stringbuf_setempty(conflict_p); + + /* Base cases: + * Either no change made in source, or same change as made in target. + * Both mean nothing to merge here. + */ + if (svn_fs_base__id_eq(ancestor_id, source_id) + || (svn_fs_base__id_eq(source_id, target_id))) + return SVN_NO_ERROR; + + /* Else proceed, knowing all three are distinct node revisions. + * + * How to merge from this point: + * + * if (not all 3 are directories) + * { + * early exit with conflict; + * } + * + * // Property changes may only be made to up-to-date + * // directories, because once the client commits the prop + * // change, it bumps the directory's revision, and therefore + * // must be able to depend on there being no other changes to + * // that directory in the repository. + * if (target's property list differs from ancestor's) + * conflict; + * + * For each entry NAME in the directory ANCESTOR: + * + * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of + * the name within ANCESTOR, SOURCE, and TARGET respectively. + * (Possibly null if NAME does not exist in SOURCE or TARGET.) + * + * If ANCESTOR-ENTRY == SOURCE-ENTRY, then: + * No changes were made to this entry while the transaction was in + * progress, so do nothing to the target. + * + * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then: + * A change was made to this entry while the transaction was in + * process, but the transaction did not touch this entry. Replace + * TARGET-ENTRY with SOURCE-ENTRY. + * + * Else: + * Changes were made to this entry both within the transaction and + * to the repository while the transaction was in progress. They + * must be merged or declared to be in conflict. + * + * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a + * double delete; flag a conflict. + * + * If any of the three entries is of type file, declare a conflict. + * + * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct + * modification of ANCESTOR-ENTRY (determine by comparing the + * node-id fields), declare a conflict. A replacement is + * incompatible with a modification or other replacement--even + * an identical replacement. + * + * Direct modifications were made to the directory ANCESTOR-ENTRY + * in both SOURCE and TARGET. Recursively merge these + * modifications. + * + * For each leftover entry NAME in the directory SOURCE: + * + * If NAME exists in TARGET, declare a conflict. Even if SOURCE and + * TARGET are adding exactly the same thing, two additions are not + * auto-mergeable with each other. + * + * Add NAME to TARGET with the entry from SOURCE. + * + * Now that we are done merging the changes from SOURCE into the + * directory TARGET, update TARGET's predecessor to be SOURCE. + */ + + if ((svn_fs_base__dag_node_kind(source) != svn_node_dir) + || (svn_fs_base__dag_node_kind(target) != svn_node_dir) + || (svn_fs_base__dag_node_kind(ancestor) != svn_node_dir)) + { + return conflict_err(conflict_p, target_path); + } + + + /* Possible early merge failure: if target and ancestor have + different property lists, then the merge should fail. + Propchanges can *only* be committed on an up-to-date directory. + ### TODO: see issue #418 about the inelegance of this. + + Another possible, similar, early merge failure: if source and + ancestor have different property lists (meaning someone else + changed directory properties while our commit transaction was + happening), the merge should fail. See issue #2751. + */ + { + node_revision_t *tgt_nr, *anc_nr, *src_nr; + + /* Get node revisions for our id's. */ + SVN_ERR(svn_fs_bdb__get_node_revision(&tgt_nr, fs, target_id, + trail, pool)); + SVN_ERR(svn_fs_bdb__get_node_revision(&anc_nr, fs, ancestor_id, + trail, pool)); + SVN_ERR(svn_fs_bdb__get_node_revision(&src_nr, fs, source_id, + trail, pool)); + + /* Now compare the prop-keys of the skels. Note that just because + the keys are different -doesn't- mean the proplists have + different contents. But merge() isn't concerned with contents; + it doesn't do a brute-force comparison on textual contents, so + it won't do that here either. Checking to see if the propkey + atoms are `equal' is enough. */ + if (! svn_fs_base__same_keys(tgt_nr->prop_key, anc_nr->prop_key)) + return conflict_err(conflict_p, target_path); + if (! svn_fs_base__same_keys(src_nr->prop_key, anc_nr->prop_key)) + return conflict_err(conflict_p, target_path); + } + + /* ### todo: it would be more efficient to simply check for a NULL + entries hash where necessary below than to allocate an empty hash + here, but another day, another day... */ + SVN_ERR(svn_fs_base__dag_dir_entries(&s_entries, source, trail, pool)); + if (! s_entries) + s_entries = apr_hash_make(pool); + SVN_ERR(svn_fs_base__dag_dir_entries(&t_entries, target, trail, pool)); + if (! t_entries) + t_entries = apr_hash_make(pool); + SVN_ERR(svn_fs_base__dag_dir_entries(&a_entries, ancestor, trail, pool)); + if (! a_entries) + a_entries = apr_hash_make(pool); + + /* for each entry E in a_entries... */ + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, a_entries); + hi; + hi = apr_hash_next(hi)) + { + svn_fs_dirent_t *s_entry, *t_entry, *a_entry; + + const void *key; + void *val; + apr_ssize_t klen; + + svn_pool_clear(iterpool); + + /* KEY will be the entry name in ancestor, VAL the dirent */ + apr_hash_this(hi, &key, &klen, &val); + a_entry = val; + + s_entry = apr_hash_get(s_entries, key, klen); + t_entry = apr_hash_get(t_entries, key, klen); + + /* No changes were made to this entry while the transaction was + in progress, so do nothing to the target. */ + if (s_entry && svn_fs_base__id_eq(a_entry->id, s_entry->id)) + goto end; + + /* A change was made to this entry while the transaction was in + process, but the transaction did not touch this entry. */ + else if (t_entry && svn_fs_base__id_eq(a_entry->id, t_entry->id)) + { + dag_node_t *t_ent_node; + apr_int64_t mergeinfo_start; + SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs, + t_entry->id, trail, iterpool)); + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_start, + t_ent_node, trail, + iterpool)); + mergeinfo_increment -= mergeinfo_start; + + if (s_entry) + { + dag_node_t *s_ent_node; + apr_int64_t mergeinfo_end; + SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs, + s_entry->id, trail, + iterpool)); + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, + &mergeinfo_end, + s_ent_node, trail, + iterpool)); + mergeinfo_increment += mergeinfo_end; + SVN_ERR(svn_fs_base__dag_set_entry(target, key, s_entry->id, + txn_id, trail, iterpool)); + } + else + { + SVN_ERR(svn_fs_base__dag_delete(target, key, txn_id, + trail, iterpool)); + } + } + + /* Changes were made to this entry both within the transaction + and to the repository while the transaction was in progress. + They must be merged or declared to be in conflict. */ + else + { + dag_node_t *s_ent_node, *t_ent_node, *a_ent_node; + const char *new_tpath; + apr_int64_t sub_mergeinfo_increment; + + /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a + double delete; if one of them is null, that's a delete versus + a modification. In any of these cases, flag a conflict. */ + if (s_entry == NULL || t_entry == NULL) + return conflict_err(conflict_p, + svn_fspath__join(target_path, + a_entry->name, + iterpool)); + + /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct + modification of ANCESTOR-ENTRY, declare a conflict. */ + if (strcmp(svn_fs_base__id_node_id(s_entry->id), + svn_fs_base__id_node_id(a_entry->id)) != 0 + || strcmp(svn_fs_base__id_copy_id(s_entry->id), + svn_fs_base__id_copy_id(a_entry->id)) != 0 + || strcmp(svn_fs_base__id_node_id(t_entry->id), + svn_fs_base__id_node_id(a_entry->id)) != 0 + || strcmp(svn_fs_base__id_copy_id(t_entry->id), + svn_fs_base__id_copy_id(a_entry->id)) != 0) + return conflict_err(conflict_p, + svn_fspath__join(target_path, + a_entry->name, + iterpool)); + + /* Fetch the nodes for our entries. */ + SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs, + s_entry->id, trail, iterpool)); + SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs, + t_entry->id, trail, iterpool)); + SVN_ERR(svn_fs_base__dag_get_node(&a_ent_node, fs, + a_entry->id, trail, iterpool)); + + /* If any of the three entries is of type file, flag a conflict. */ + if ((svn_fs_base__dag_node_kind(s_ent_node) == svn_node_file) + || (svn_fs_base__dag_node_kind(t_ent_node) == svn_node_file) + || (svn_fs_base__dag_node_kind(a_ent_node) == svn_node_file)) + return conflict_err(conflict_p, + svn_fspath__join(target_path, + a_entry->name, + iterpool)); + + /* Direct modifications were made to the directory + ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively + merge these modifications. */ + new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool); + SVN_ERR(merge(conflict_p, new_tpath, + t_ent_node, s_ent_node, a_ent_node, + txn_id, &sub_mergeinfo_increment, trail, iterpool)); + mergeinfo_increment += sub_mergeinfo_increment; + } + + /* We've taken care of any possible implications E could have. + Remove it from source_entries, so it's easy later to loop + over all the source entries that didn't exist in + ancestor_entries. */ + end: + apr_hash_set(s_entries, key, klen, NULL); + } + + /* For each entry E in source but not in ancestor */ + for (hi = apr_hash_first(pool, s_entries); + hi; + hi = apr_hash_next(hi)) + { + svn_fs_dirent_t *s_entry, *t_entry; + const void *key; + void *val; + apr_ssize_t klen; + dag_node_t *s_ent_node; + apr_int64_t mergeinfo_s; + + svn_pool_clear(iterpool); + + apr_hash_this(hi, &key, &klen, &val); + s_entry = val; + t_entry = apr_hash_get(t_entries, key, klen); + + /* If NAME exists in TARGET, declare a conflict. */ + if (t_entry) + return conflict_err(conflict_p, + svn_fspath__join(target_path, + t_entry->name, + iterpool)); + + SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs, + s_entry->id, trail, iterpool)); + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_s, + s_ent_node, trail, + iterpool)); + mergeinfo_increment += mergeinfo_s; + SVN_ERR(svn_fs_base__dag_set_entry + (target, s_entry->name, s_entry->id, txn_id, trail, iterpool)); + } + svn_pool_destroy(iterpool); + + /* Now that TARGET has absorbed all of the history between ANCESTOR + and SOURCE, we can update its predecessor to point to SOURCE. */ + SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count, source, + trail, pool)); + SVN_ERR(update_ancestry(fs, source_id, target_id, txn_id, target_path, + pred_count, trail, pool)); + + /* Tweak mergeinfo data if our format supports it. */ + if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT) + { + SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(target, + mergeinfo_increment, + txn_id, trail, pool)); + } + + if (mergeinfo_increment_out) + *mergeinfo_increment_out = mergeinfo_increment; + + return SVN_NO_ERROR; +} + + +struct merge_args +{ + /* The ancestor for the merge. If this is null, then TXN's base is + used as the ancestor for the merge. */ + dag_node_t *ancestor_node; + + /* This is the SOURCE node for the merge. It may not be null. */ + dag_node_t *source_node; + + /* This is the TARGET of the merge. It may not be null. If + ancestor_node above is null, then this txn's base is used as the + ancestor for the merge. */ + svn_fs_txn_t *txn; + + /* If a conflict results, this is updated to the path in the txn that + conflicted. It must point to a valid svn_stringbuf_t before calling + svn_fs_base__retry_txn, as this determines the pool used to allocate any + required memory. */ + svn_stringbuf_t *conflict; +}; + + +/* Merge changes between an ancestor and BATON->source_node into + BATON->txn. The ancestor is either BATON->ancestor_node, or if + that is null, BATON->txn's base node. + + If the merge is successful, BATON->txn's base will become + BATON->source_node, and its root node will have a new ID, a + successor of BATON->source_node. */ +static svn_error_t * +txn_body_merge(void *baton, trail_t *trail) +{ + struct merge_args *args = baton; + dag_node_t *source_node, *txn_root_node, *ancestor_node; + const svn_fs_id_t *source_id; + svn_fs_t *fs = args->txn->fs; + const char *txn_id = args->txn->id; + + source_node = args->source_node; + ancestor_node = args->ancestor_node; + source_id = svn_fs_base__dag_get_id(source_node); + + SVN_ERR(svn_fs_base__dag_txn_root(&txn_root_node, fs, txn_id, + trail, trail->pool)); + + if (ancestor_node == NULL) + { + SVN_ERR(svn_fs_base__dag_txn_base_root(&ancestor_node, fs, + txn_id, trail, trail->pool)); + } + + if (svn_fs_base__id_eq(svn_fs_base__dag_get_id(ancestor_node), + svn_fs_base__dag_get_id(txn_root_node))) + { + /* If no changes have been made in TXN since its current base, + then it can't conflict with any changes since that base. So + we just set *both* its base and root to source, making TXN + in effect a repeat of source. */ + + /* ### kff todo: this would, of course, be a mighty silly thing + for the caller to do, and we might want to consider whether + this response is really appropriate. */ + + SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id, + trail, trail->pool)); + SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, source_id, + trail, trail->pool)); + } + else + { + int pred_count; + + SVN_ERR(merge(args->conflict, "/", txn_root_node, source_node, + ancestor_node, txn_id, NULL, trail, trail->pool)); + + SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count, + source_node, trail, + trail->pool)); + + /* After the merge, txn's new "ancestor" is now really the node + at source_id, so record that fact. Think of this as + ratcheting the txn forward in time, so it can't backslide and + forget the merging work that's already been done. */ + SVN_ERR(update_ancestry(fs, source_id, + svn_fs_base__dag_get_id(txn_root_node), + txn_id, "/", pred_count, trail, trail->pool)); + SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id, + trail, trail->pool)); + } + + return SVN_NO_ERROR; +} + + +/* Verify that there are registered with TRAIL->fs all the locks + necessary to permit all the changes associated with TXN_NAME. */ +static svn_error_t * +verify_locks(const char *txn_name, + trail_t *trail, + apr_pool_t *pool) +{ + apr_pool_t *subpool = svn_pool_create(pool); + apr_hash_t *changes; + apr_hash_index_t *hi; + apr_array_header_t *changed_paths; + svn_stringbuf_t *last_recursed = NULL; + int i; + + /* Fetch the changes for this transaction. */ + SVN_ERR(svn_fs_bdb__changes_fetch(&changes, trail->fs, txn_name, + trail, pool)); + + /* Make an array of the changed paths, and sort them depth-first-ily. */ + changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1, + sizeof(const char *)); + for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) + { + const void *key; + apr_hash_this(hi, &key, NULL, NULL); + APR_ARRAY_PUSH(changed_paths, const char *) = key; + } + qsort(changed_paths->elts, changed_paths->nelts, + changed_paths->elt_size, svn_sort_compare_paths); + + /* Now, traverse the array of changed paths, verify locks. Note + that if we need to do a recursive verification a path, we'll skip + over children of that path when we get to them. */ + for (i = 0; i < changed_paths->nelts; i++) + { + const char *path; + svn_fs_path_change2_t *change; + svn_boolean_t recurse = TRUE; + + svn_pool_clear(subpool); + path = APR_ARRAY_IDX(changed_paths, i, const char *); + + /* If this path has already been verified as part of a recursive + check of one of its parents, no need to do it again. */ + if (last_recursed + && svn_fspath__skip_ancestor(last_recursed->data, path)) + continue; + + /* Fetch the change associated with our path. */ + change = svn_hash_gets(changes, path); + + /* What does it mean to succeed at lock verification for a given + path? For an existing file or directory getting modified + (text, props), it means we hold the lock on the file or + directory. For paths being added or removed, we need to hold + the locks for that path and any children of that path. + + WHEW! We have no reliable way to determine the node kind of + deleted items, but fortunately we are going to do a recursive + check on deleted paths regardless of their kind. */ + if (change->change_kind == svn_fs_path_change_modify) + recurse = FALSE; + SVN_ERR(svn_fs_base__allow_locked_operation(path, recurse, + trail, subpool)); + + /* If we just did a recursive check, remember the path we + checked (so children can be skipped). */ + if (recurse) + { + if (! last_recursed) + last_recursed = svn_stringbuf_create(path, pool); + else + svn_stringbuf_set(last_recursed, path); + } + } + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +struct commit_args +{ + svn_fs_txn_t *txn; + svn_revnum_t new_rev; +}; + + +/* Commit ARGS->txn, setting ARGS->new_rev to the resulting new + * revision, if ARGS->txn is up-to-date with respect to the repository. + * + * Up-to-date means that ARGS->txn's base root is the same as the root + * of the youngest revision. If ARGS->txn is not up-to-date, the + * error SVN_ERR_FS_TXN_OUT_OF_DATE is returned, and the commit fails: no + * new revision is created, and ARGS->new_rev is not touched. + * + * If the commit succeeds, ARGS->txn is destroyed. + */ +static svn_error_t * +txn_body_commit(void *baton, trail_t *trail) +{ + struct commit_args *args = baton; + + svn_fs_txn_t *txn = args->txn; + svn_fs_t *fs = txn->fs; + const char *txn_name = txn->id; + + svn_revnum_t youngest_rev; + const svn_fs_id_t *y_rev_root_id; + dag_node_t *txn_base_root_node; + + /* Getting the youngest revision locks the revisions table until + this trail is done. */ + SVN_ERR(svn_fs_bdb__youngest_rev(&youngest_rev, fs, trail, trail->pool)); + + /* If the root of the youngest revision is the same as txn's base, + then no further merging is necessary and we can commit. */ + SVN_ERR(svn_fs_base__rev_get_root(&y_rev_root_id, fs, youngest_rev, + trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_txn_base_root(&txn_base_root_node, fs, txn_name, + trail, trail->pool)); + /* ### kff todo: it seems weird to grab the ID for one, and the node + for the other. We can certainly do the comparison we need, but + it would be nice to grab the same type of information from the + start, instead of having to transform one of them. */ + if (! svn_fs_base__id_eq(y_rev_root_id, + svn_fs_base__dag_get_id(txn_base_root_node))) + { + svn_string_t *id_str = svn_fs_base__id_unparse(y_rev_root_id, + trail->pool); + return svn_error_createf + (SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, + _("Transaction '%s' out-of-date with respect to revision '%s'"), + txn_name, id_str->data); + } + + /* Locks may have been added (or stolen) between the calling of + previous svn_fs.h functions and svn_fs_commit_txn(), so we need + to re-examine every changed-path in the txn and re-verify all + discovered locks. */ + SVN_ERR(verify_locks(txn_name, trail, trail->pool)); + + /* Else, commit the txn. */ + return svn_fs_base__dag_commit_txn(&(args->new_rev), txn, trail, + trail->pool); +} + + +/* Note: it is acceptable for this function to call back into + top-level FS interfaces because it does not itself use trails. */ +svn_error_t * +svn_fs_base__commit_txn(const char **conflict_p, + svn_revnum_t *new_rev, + svn_fs_txn_t *txn, + apr_pool_t *pool) +{ + /* How do commits work in Subversion? + * + * When you're ready to commit, here's what you have: + * + * 1. A transaction, with a mutable tree hanging off it. + * 2. A base revision, against which TXN_TREE was made. + * 3. A latest revision, which may be newer than the base rev. + * + * The problem is that if latest != base, then one can't simply + * attach the txn root as the root of the new revision, because that + * would lose all the changes between base and latest. It is also + * not acceptable to insist that base == latest; in a busy + * repository, commits happen too fast to insist that everyone keep + * their entire tree up-to-date at all times. Non-overlapping + * changes should not interfere with each other. + * + * The solution is to merge the changes between base and latest into + * the txn tree [see the function merge()]. The txn tree is the + * only one of the three trees that is mutable, so it has to be the + * one to adjust. + * + * You might have to adjust it more than once, if a new latest + * revision gets committed while you were merging in the previous + * one. For example: + * + * 1. Jane starts txn T, based at revision 6. + * 2. Someone commits (or already committed) revision 7. + * 3. Jane's starts merging the changes between 6 and 7 into T. + * 4. Meanwhile, someone commits revision 8. + * 5. Jane finishes the 6-->7 merge. T could now be committed + * against a latest revision of 7, if only that were still the + * latest. Unfortunately, 8 is now the latest, so... + * 6. Jane starts merging the changes between 7 and 8 into T. + * 7. Meanwhile, no one commits any new revisions. Whew. + * 8. Jane commits T, creating revision 9, whose tree is exactly + * T's tree, except immutable now. + * + * Lather, rinse, repeat. + */ + + svn_error_t *err; + svn_fs_t *fs = txn->fs; + apr_pool_t *subpool = svn_pool_create(pool); + + /* Initialize output params. */ + *new_rev = SVN_INVALID_REVNUM; + if (conflict_p) + *conflict_p = NULL; + + while (1729) + { + struct get_root_args get_root_args; + struct merge_args merge_args; + struct commit_args commit_args; + svn_revnum_t youngish_rev; + svn_fs_root_t *youngish_root; + dag_node_t *youngish_root_node; + + svn_pool_clear(subpool); + + /* Get the *current* youngest revision, in one short-lived + Berkeley transaction. (We don't want the revisions table + locked while we do the main merge.) We call it "youngish" + because new revisions might get committed after we've + obtained it. */ + + SVN_ERR(svn_fs_base__youngest_rev(&youngish_rev, fs, subpool)); + SVN_ERR(svn_fs_base__revision_root(&youngish_root, fs, youngish_rev, + subpool)); + + /* Get the dag node for the youngest revision, also in one + Berkeley transaction. Later we'll use it as the SOURCE + argument to a merge, and if the merge succeeds, this youngest + root node will become the new base root for the svn txn that + was the target of the merge (but note that the youngest rev + may have changed by then -- that's why we're careful to get + this root in its own bdb txn here). */ + get_root_args.root = youngish_root; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args, + FALSE, subpool)); + youngish_root_node = get_root_args.node; + + /* Try to merge. If the merge succeeds, the base root node of + TARGET's txn will become the same as youngish_root_node, so + any future merges will only be between that node and whatever + the root node of the youngest rev is by then. */ + merge_args.ancestor_node = NULL; + merge_args.source_node = youngish_root_node; + merge_args.txn = txn; + merge_args.conflict = svn_stringbuf_create_empty(pool); /* use pool */ + err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args, + FALSE, subpool); + if (err) + { + if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) + *conflict_p = merge_args.conflict->data; + return svn_error_trace(err); + } + + /* Try to commit. */ + commit_args.txn = txn; + err = svn_fs_base__retry_txn(fs, txn_body_commit, &commit_args, + FALSE, subpool); + if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)) + { + /* Did someone else finish committing a new revision while we + were in mid-merge or mid-commit? If so, we'll need to + loop again to merge the new changes in, then try to + commit again. Or if that's not what happened, then just + return the error. */ + svn_revnum_t youngest_rev; + svn_error_t *err2 = svn_fs_base__youngest_rev(&youngest_rev, fs, + subpool); + if (err2) + { + svn_error_clear(err); + return svn_error_trace(err2); /* err2 is bad, + it should not occur */ + } + else if (youngest_rev == youngish_rev) + return svn_error_trace(err); + else + svn_error_clear(err); + } + else if (err) + { + return svn_error_trace(err); + } + else + { + /* Set the return value -- our brand spankin' new revision! */ + *new_rev = commit_args.new_rev; + break; + } + } + + svn_pool_destroy(subpool); + 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. */ +static svn_error_t * +base_merge(const char **conflict_p, + svn_fs_root_t *source_root, + const char *source_path, + svn_fs_root_t *target_root, + const char *target_path, + svn_fs_root_t *ancestor_root, + const char *ancestor_path, + apr_pool_t *pool) +{ + dag_node_t *source, *ancestor; + struct get_root_args get_root_args; + struct merge_args merge_args; + svn_fs_txn_t *txn; + svn_error_t *err; + svn_fs_t *fs; + + if (! target_root->is_txn_root) + return SVN_FS__NOT_TXN(target_root); + + /* Paranoia. */ + fs = ancestor_root->fs; + if ((source_root->fs != fs) || (target_root->fs != fs)) + { + return svn_error_create + (SVN_ERR_FS_CORRUPT, NULL, + _("Bad merge; ancestor, source, and target not all in same fs")); + } + + /* ### kff todo: is there any compelling reason to get the nodes in + one db transaction? Right now we don't; txn_body_get_root() gets + one node at a time. This will probably need to change: + + Jim Blandy <jimb@zwingli.cygnus.com> writes: + > svn_fs_merge needs to be a single transaction, to protect it against + > people deleting parents of nodes it's working on, etc. + */ + + /* Get the ancestor node. */ + get_root_args.root = ancestor_root; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args, + FALSE, pool)); + ancestor = get_root_args.node; + + /* Get the source node. */ + get_root_args.root = source_root; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args, + FALSE, pool)); + source = get_root_args.node; + + /* Open a txn for the txn root into which we're merging. */ + SVN_ERR(svn_fs_base__open_txn(&txn, fs, target_root->txn, pool)); + + /* Merge changes between ANCESTOR and SOURCE into TXN. */ + merge_args.source_node = source; + merge_args.ancestor_node = ancestor; + merge_args.txn = txn; + merge_args.conflict = svn_stringbuf_create_empty(pool); + err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args, FALSE, pool); + if (err) + { + if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) + *conflict_p = merge_args.conflict->data; + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} + + +struct rev_get_txn_id_args +{ + const char **txn_id; + svn_revnum_t revision; +}; + + +static svn_error_t * +txn_body_rev_get_txn_id(void *baton, trail_t *trail) +{ + struct rev_get_txn_id_args *args = baton; + return svn_fs_base__rev_get_txn_id(args->txn_id, trail->fs, + args->revision, trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__deltify(svn_fs_t *fs, + svn_revnum_t revision, + apr_pool_t *pool) +{ + svn_fs_root_t *root; + const char *txn_id; + struct rev_get_txn_id_args args; + base_fs_data_t *bfd = fs->fsap_data; + + if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT) + { + const char *val; + svn_revnum_t forward_delta_rev = 0; + + SVN_ERR(svn_fs_base__miscellaneous_get + (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool)); + if (val) + SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL)); + + /* ### FIXME: Unnecessarily harsh requirement? (cmpilato). */ + if (revision <= forward_delta_rev) + return svn_error_createf + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot deltify revisions prior to r%ld"), forward_delta_rev+1); + } + + SVN_ERR(svn_fs_base__revision_root(&root, fs, revision, pool)); + + args.txn_id = &txn_id; + args.revision = revision; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_rev_get_txn_id, &args, + FALSE, pool)); + + return deltify_mutable(fs, root, "/", NULL, svn_node_dir, txn_id, pool); +} + + +/* Modifying directories */ + + +struct make_dir_args +{ + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_make_dir(void *baton, + trail_t *trail) +{ + struct make_dir_args *args = baton; + svn_fs_root_t *root = args->root; + const char *path = args->path; + parent_path_t *parent_path; + dag_node_t *sub_dir; + const char *txn_id = root->txn; + + SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, + txn_id, trail, trail->pool)); + + /* If there's already a sub-directory by that name, complain. This + also catches the case of trying to make a subdirectory named `/'. */ + if (parent_path->node) + return SVN_FS__ALREADY_EXISTS(root, path); + + /* Check to see if some lock is 'reserving' a file-path or dir-path + at that location, or even some child-path; if so, check that we + can use it. */ + if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + { + SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE, + trail, trail->pool)); + } + + /* Create the subdirectory. */ + SVN_ERR(make_path_mutable(root, parent_path->parent, path, + trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_make_dir(&sub_dir, + parent_path->parent->node, + parent_path_path(parent_path->parent, + trail->pool), + parent_path->entry, + txn_id, + trail, trail->pool)); + + /* Make a record of this modification in the changes table. */ + return add_change(root->fs, txn_id, path, + svn_fs_base__dag_get_id(sub_dir), + svn_fs_path_change_add, FALSE, FALSE, + trail, trail->pool); +} + + +static svn_error_t * +base_make_dir(svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct make_dir_args args; + + if (! root->is_txn_root) + return SVN_FS__NOT_TXN(root); + + args.root = root; + args.path = path; + return svn_fs_base__retry_txn(root->fs, txn_body_make_dir, &args, + TRUE, pool); +} + + +struct delete_args +{ + svn_fs_root_t *root; + const char *path; +}; + + +/* If this returns SVN_ERR_FS_NO_SUCH_ENTRY, it means that the + basename of PATH is missing from its parent, that is, the final + target of the deletion is missing. */ +static svn_error_t * +txn_body_delete(void *baton, + trail_t *trail) +{ + struct delete_args *args = baton; + svn_fs_root_t *root = args->root; + const char *path = args->path; + parent_path_t *parent_path; + const char *txn_id = root->txn; + base_fs_data_t *bfd = trail->fs->fsap_data; + + if (! root->is_txn_root) + return SVN_FS__NOT_TXN(root); + + SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, + trail, trail->pool)); + + /* We can't remove the root of the filesystem. */ + if (! parent_path->parent) + return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL, + _("The root directory cannot be deleted")); + + /* Check to see if path (or any child thereof) is locked; if so, + check that we can use the existing lock(s). */ + if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + { + SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE, + trail, trail->pool)); + } + + /* Make the parent directory mutable. */ + SVN_ERR(make_path_mutable(root, parent_path->parent, path, + trail, trail->pool)); + + /* Decrement mergeinfo counts on the parents of this node by the + count it previously carried, if our format supports it. */ + if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT) + { + apr_int64_t mergeinfo_count; + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_count, + parent_path->node, + trail, trail->pool)); + SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent, + -mergeinfo_count, txn_id, + trail, trail->pool)); + } + + /* Do the deletion. */ + SVN_ERR(svn_fs_base__dag_delete(parent_path->parent->node, + parent_path->entry, + txn_id, trail, trail->pool)); + + + /* Make a record of this modification in the changes table. */ + return add_change(root->fs, txn_id, path, + svn_fs_base__dag_get_id(parent_path->node), + svn_fs_path_change_delete, FALSE, FALSE, trail, + trail->pool); +} + + +static svn_error_t * +base_delete_node(svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct delete_args args; + + args.root = root; + args.path = path; + return svn_fs_base__retry_txn(root->fs, txn_body_delete, &args, + TRUE, pool); +} + + +struct copy_args +{ + svn_fs_root_t *from_root; + const char *from_path; + svn_fs_root_t *to_root; + const char *to_path; + svn_boolean_t preserve_history; +}; + + +static svn_error_t * +txn_body_copy(void *baton, + trail_t *trail) +{ + struct copy_args *args = baton; + svn_fs_root_t *from_root = args->from_root; + const char *from_path = args->from_path; + svn_fs_root_t *to_root = args->to_root; + const char *to_path = args->to_path; + dag_node_t *from_node; + parent_path_t *to_parent_path; + const char *txn_id = to_root->txn; + + /* Get the NODE for FROM_PATH in FROM_ROOT.*/ + SVN_ERR(get_dag(&from_node, from_root, from_path, trail, trail->pool)); + + /* Build up the parent path from TO_PATH in TO_ROOT. If the last + component does not exist, it's not that big a deal. We'll just + make one there. */ + SVN_ERR(open_path(&to_parent_path, to_root, to_path, + open_path_last_optional, txn_id, trail, trail->pool)); + + /* Check to see if to-path (or any child thereof) is locked, or at + least 'reserved', whether it exists or not; if so, check that we + can use the existing lock(s). */ + if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + { + SVN_ERR(svn_fs_base__allow_locked_operation(to_path, TRUE, + trail, trail->pool)); + } + + /* If the destination node already exists as the same node as the + source (in other words, this operation would result in nothing + happening at all), just do nothing an return successfully, + proud that you saved yourself from a tiresome task. */ + if ((to_parent_path->node) + && (svn_fs_base__id_compare(svn_fs_base__dag_get_id(from_node), + svn_fs_base__dag_get_id + (to_parent_path->node)) == 0)) + return SVN_NO_ERROR; + + if (! from_root->is_txn_root) + { + svn_fs_path_change_kind_t kind; + dag_node_t *new_node; + apr_int64_t old_mergeinfo_count = 0, mergeinfo_count; + base_fs_data_t *bfd = trail->fs->fsap_data; + + /* If TO_PATH already existed prior to the copy, note that this + operation is a replacement, not an addition. */ + if (to_parent_path->node) + kind = svn_fs_path_change_replace; + else + kind = svn_fs_path_change_add; + + /* Make sure the target node's parents are mutable. */ + SVN_ERR(make_path_mutable(to_root, to_parent_path->parent, + to_path, trail, trail->pool)); + + /* If this is a replacement operation, we need to know the old + node's mergeinfo count. */ + if (to_parent_path->node) + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, + &old_mergeinfo_count, + to_parent_path->node, + trail, trail->pool)); + /* Do the copy. */ + SVN_ERR(svn_fs_base__dag_copy(to_parent_path->parent->node, + to_parent_path->entry, + from_node, + args->preserve_history, + from_root->rev, + from_path, txn_id, trail, trail->pool)); + + /* Adjust the mergeinfo counts of the destination's parents if + our format supports it. */ + if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT) + { + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, + &mergeinfo_count, + from_node, trail, + trail->pool)); + SVN_ERR(adjust_parent_mergeinfo_counts + (to_parent_path->parent, + mergeinfo_count - old_mergeinfo_count, + txn_id, trail, trail->pool)); + } + + /* Make a record of this modification in the changes table. */ + SVN_ERR(get_dag(&new_node, to_root, to_path, trail, trail->pool)); + SVN_ERR(add_change(to_root->fs, txn_id, to_path, + svn_fs_base__dag_get_id(new_node), + kind, FALSE, FALSE, trail, trail->pool)); + } + else + { + /* See IZ Issue #436 */ + /* Copying from transaction roots not currently available. + + ### cmpilato todo someday: make this not so. :-) Note that + when copying from mutable trees, you have to make sure that + you aren't creating a cyclic graph filesystem, and a simple + referencing operation won't cut it. Currently, we should not + be able to reach this clause, and the interface reports that + this only works from immutable trees anyway, but JimB has + stated that this requirement need not be necessary in the + future. */ + + SVN_ERR_MALFUNCTION(); + } + + return SVN_NO_ERROR; +} + + +/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE. + Use POOL for temporary allocation only. + Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */ +static svn_error_t * +fs_same_p(svn_boolean_t *same_p, + svn_fs_t *fs1, + svn_fs_t *fs2, + apr_pool_t *pool) +{ + *same_p = ! strcmp(fs1->uuid, fs2->uuid); + return SVN_NO_ERROR; +} + +/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under + TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in + the copies table. Perform temporary allocations in POOL. */ +static svn_error_t * +copy_helper(svn_fs_root_t *from_root, + const char *from_path, + svn_fs_root_t *to_root, + const char *to_path, + svn_boolean_t preserve_history, + apr_pool_t *pool) +{ + struct copy_args args; + svn_boolean_t same_p; + + /* Use an error check, not an assert, because even the caller cannot + guarantee that a filesystem's UUID has not changed "on the fly". */ + SVN_ERR(fs_same_p(&same_p, from_root->fs, to_root->fs, pool)); + if (! same_p) + return svn_error_createf + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot copy between two different filesystems ('%s' and '%s')"), + from_root->fs->path, to_root->fs->path); + + if (! to_root->is_txn_root) + return SVN_FS__NOT_TXN(to_root); + + if (from_root->is_txn_root) + return svn_error_create + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Copy from mutable tree not currently supported")); + + args.from_root = from_root; + args.from_path = from_path; + args.to_root = to_root; + args.to_path = to_path; + args.preserve_history = preserve_history; + + return svn_fs_base__retry_txn(to_root->fs, txn_body_copy, &args, + TRUE, pool); +} + +static svn_error_t * +base_copy(svn_fs_root_t *from_root, + const char *from_path, + svn_fs_root_t *to_root, + const char *to_path, + apr_pool_t *pool) +{ + return copy_helper(from_root, from_path, to_root, to_path, TRUE, pool); +} + + +static svn_error_t * +base_revision_link(svn_fs_root_t *from_root, + svn_fs_root_t *to_root, + const char *path, + apr_pool_t *pool) +{ + return copy_helper(from_root, path, to_root, path, FALSE, pool); +} + + +struct copied_from_args +{ + svn_fs_root_t *root; /* Root for the node whose ancestry we seek. */ + const char *path; /* Path for the node whose ancestry we seek. */ + + svn_revnum_t result_rev; /* Revision, if any, of the ancestor. */ + const char *result_path; /* Path, if any, of the ancestor. */ + + apr_pool_t *pool; /* Allocate `result_path' here. */ +}; + + +static svn_error_t * +txn_body_copied_from(void *baton, trail_t *trail) +{ + struct copied_from_args *args = baton; + const svn_fs_id_t *node_id, *pred_id; + dag_node_t *node; + svn_fs_t *fs = args->root->fs; + + /* Clear the return variables. */ + args->result_path = NULL; + args->result_rev = SVN_INVALID_REVNUM; + + /* Fetch the NODE in question. */ + SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool)); + node_id = svn_fs_base__dag_get_id(node); + + /* Check the node's predecessor-ID. If it doesn't have one, it + isn't a copy. */ + SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node, + trail, trail->pool)); + if (! pred_id) + return SVN_NO_ERROR; + + /* If NODE's copy-ID is the same as that of its predecessor... */ + if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id), + svn_fs_base__id_copy_id(pred_id)) != 0) + { + /* ... then NODE was either the target of a copy operation, + a copied subtree item. We examine the actual copy record + to determine which is the case. */ + copy_t *copy; + SVN_ERR(svn_fs_bdb__get_copy(©, fs, + svn_fs_base__id_copy_id(node_id), + trail, trail->pool)); + if ((copy->kind == copy_kind_real) + && svn_fs_base__id_eq(copy->dst_noderev_id, node_id)) + { + args->result_path = copy->src_path; + SVN_ERR(svn_fs_base__txn_get_revision(&(args->result_rev), fs, + copy->src_txn_id, + trail, trail->pool)); + } + } + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_copied_from(svn_revnum_t *rev_p, + const char **path_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct copied_from_args args; + apr_pool_t *scratch_pool = svn_pool_create(pool); + args.root = root; + args.path = path; + args.pool = pool; + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_copied_from, &args, + FALSE, scratch_pool)); + + *rev_p = args.result_rev; + *path_p = args.result_path ? apr_pstrdup(pool, args.result_path) : NULL; + + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + + + +/* Files. */ + + +struct make_file_args +{ + svn_fs_root_t *root; + const char *path; +}; + + +static svn_error_t * +txn_body_make_file(void *baton, + trail_t *trail) +{ + struct make_file_args *args = baton; + svn_fs_root_t *root = args->root; + const char *path = args->path; + parent_path_t *parent_path; + dag_node_t *child; + const char *txn_id = root->txn; + + SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, + txn_id, trail, trail->pool)); + + /* If there's already a file by that name, complain. + This also catches the case of trying to make a file named `/'. */ + if (parent_path->node) + return SVN_FS__ALREADY_EXISTS(root, path); + + /* Check to see if some lock is 'reserving' a file-path or dir-path + at that location, or even some child-path; if so, check that we + can use it. */ + if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + { + SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE, + trail, trail->pool)); + } + + /* Create the file. */ + SVN_ERR(make_path_mutable(root, parent_path->parent, path, + trail, trail->pool)); + SVN_ERR(svn_fs_base__dag_make_file(&child, + parent_path->parent->node, + parent_path_path(parent_path->parent, + trail->pool), + parent_path->entry, + txn_id, + trail, trail->pool)); + + /* Make a record of this modification in the changes table. */ + return add_change(root->fs, txn_id, path, + svn_fs_base__dag_get_id(child), + svn_fs_path_change_add, TRUE, FALSE, + trail, trail->pool); +} + + +static svn_error_t * +base_make_file(svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct make_file_args args; + + args.root = root; + args.path = path; + return svn_fs_base__retry_txn(root->fs, txn_body_make_file, &args, + TRUE, pool); +} + + + +struct file_length_args +{ + svn_fs_root_t *root; + const char *path; + svn_filesize_t length; /* OUT parameter */ +}; + +static svn_error_t * +txn_body_file_length(void *baton, + trail_t *trail) +{ + struct file_length_args *args = baton; + dag_node_t *file; + + /* First create a dag_node_t from the root/path pair. */ + SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool)); + + /* Now fetch its length */ + return svn_fs_base__dag_file_length(&args->length, file, + trail, trail->pool); +} + +static svn_error_t * +base_file_length(svn_filesize_t *length_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct file_length_args args; + + args.root = root; + args.path = path; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_length, &args, + TRUE, pool)); + + *length_p = args.length; + return SVN_NO_ERROR; +} + + +struct file_checksum_args +{ + svn_fs_root_t *root; + const char *path; + svn_checksum_kind_t kind; + svn_checksum_t **checksum; /* OUT parameter */ +}; + +static svn_error_t * +txn_body_file_checksum(void *baton, + trail_t *trail) +{ + struct file_checksum_args *args = baton; + dag_node_t *file; + + SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool)); + + return svn_fs_base__dag_file_checksum(args->checksum, args->kind, file, + trail, trail->pool); +} + +static svn_error_t * +base_file_checksum(svn_checksum_t **checksum, + svn_checksum_kind_t kind, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct file_checksum_args args; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + args.root = root; + args.path = path; + args.kind = kind; + args.checksum = checksum; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_checksum, &args, + FALSE, scratch_pool)); + *checksum = svn_checksum_dup(*checksum, pool); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + + +/* --- Machinery for svn_fs_file_contents() --- */ + + +/* Local baton type for txn_body_get_file_contents. */ +typedef struct file_contents_baton_t +{ + /* The file we want to read. */ + svn_fs_root_t *root; + const char *path; + + /* The dag_node that will be made from the above. */ + dag_node_t *node; + + /* The pool in which `file_stream' (below) is allocated. */ + apr_pool_t *pool; + + /* The readable file stream that will be made from the + dag_node. (And returned to the caller.) */ + svn_stream_t *file_stream; + +} file_contents_baton_t; + + +/* Main body of svn_fs_file_contents; converts a root/path pair into + a readable file stream (in the context of a db txn). */ +static svn_error_t * +txn_body_get_file_contents(void *baton, trail_t *trail) +{ + file_contents_baton_t *fb = (file_contents_baton_t *) baton; + + /* First create a dag_node_t from the root/path pair. */ + SVN_ERR(get_dag(&(fb->node), fb->root, fb->path, trail, trail->pool)); + + /* Then create a readable stream from the dag_node_t. */ + return svn_fs_base__dag_get_contents(&(fb->file_stream), + fb->node, trail, fb->pool); +} + + + +static svn_error_t * +base_file_contents(svn_stream_t **contents, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + file_contents_baton_t *fb = apr_pcalloc(pool, sizeof(*fb)); + fb->root = root; + fb->path = path; + fb->pool = pool; + + /* Create the readable stream in the context of a db txn. */ + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_get_file_contents, fb, + FALSE, pool)); + + *contents = fb->file_stream; + return SVN_NO_ERROR; +} + +/* --- End machinery for svn_fs_file_contents() --- */ + + + +/* --- Machinery for svn_fs_apply_textdelta() --- */ + + +/* Local baton type for all the helper functions below. */ +typedef struct txdelta_baton_t +{ + /* This is the custom-built window consumer given to us by the delta + library; it uniquely knows how to read data from our designated + "source" stream, interpret the window, and write data to our + designated "target" stream (in this case, our repos file.) */ + svn_txdelta_window_handler_t interpreter; + void *interpreter_baton; + + /* The original file info */ + svn_fs_root_t *root; + const char *path; + + /* Derived from the file info */ + dag_node_t *node; + + svn_stream_t *source_stream; + svn_stream_t *target_stream; + svn_stream_t *string_stream; + svn_stringbuf_t *target_string; + + /* Checksums for the base text against which a delta is to be + applied, and for the resultant fulltext, respectively. Either or + both may be null, in which case ignored. */ + svn_checksum_t *base_checksum; + svn_checksum_t *result_checksum; + + /* Pool used by db txns */ + apr_pool_t *pool; + +} txdelta_baton_t; + + +/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits. + * This closes BATON->target_stream. + * + * Note: If you're confused about how this function relates to another + * of similar name, think of it this way: + * + * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits() + * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits() + */ +static svn_error_t * +txn_body_txdelta_finalize_edits(void *baton, trail_t *trail) +{ + txdelta_baton_t *tb = (txdelta_baton_t *) baton; + SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node, + tb->result_checksum, + tb->root->txn, + trail, trail->pool)); + + /* Make a record of this modification in the changes table. */ + return add_change(tb->root->fs, tb->root->txn, tb->path, + svn_fs_base__dag_get_id(tb->node), + svn_fs_path_change_modify, TRUE, FALSE, trail, + trail->pool); +} + + +/* ### see comment in window_consumer() regarding this function. */ + +/* Helper function of generic type `svn_write_fn_t'. Implements a + writable stream which appends to an svn_stringbuf_t. */ +static svn_error_t * +write_to_string(void *baton, const char *data, apr_size_t *len) +{ + txdelta_baton_t *tb = (txdelta_baton_t *) baton; + svn_stringbuf_appendbytes(tb->target_string, data, *len); + return SVN_NO_ERROR; +} + + + +/* The main window handler returned by svn_fs_apply_textdelta. */ +static svn_error_t * +window_consumer(svn_txdelta_window_t *window, void *baton) +{ + txdelta_baton_t *tb = (txdelta_baton_t *) baton; + + /* Send the window right through to the custom window interpreter. + In theory, the interpreter will then write more data to + cb->target_string. */ + SVN_ERR(tb->interpreter(window, tb->interpreter_baton)); + + /* ### the write_to_string() callback for the txdelta's output stream + ### should be doing all the flush determination logic, not here. + ### in a drastic case, a window could generate a LOT more than the + ### maximum buffer size. we want to flush to the underlying target + ### stream much sooner (e.g. also in a streamy fashion). also, by + ### moving this logic inside the stream, the stream becomes nice + ### and encapsulated: it holds all the logic about buffering and + ### flushing. + ### + ### further: I believe the buffering should be removed from tree.c + ### the buffering should go into the target_stream itself, which + ### is defined by reps-string.c. Specifically, I think the + ### rep_write_contents() function will handle the buffering and + ### the spill to the underlying DB. by locating it there, then + ### anybody who gets a writable stream for FS content can take + ### advantage of the buffering capability. this will be important + ### when we export an FS API function for writing a fulltext into + ### the FS, rather than forcing that fulltext thru apply_textdelta. + */ + + /* Check to see if we need to purge the portion of the contents that + have been written thus far. */ + if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE)) + { + apr_size_t len = tb->target_string->len; + SVN_ERR(svn_stream_write(tb->target_stream, + tb->target_string->data, + &len)); + svn_stringbuf_setempty(tb->target_string); + } + + /* Is the window NULL? If so, we're done. */ + if (! window) + { + /* Close the internal-use stream. ### This used to be inside of + txn_body_fulltext_finalize_edits(), but that invoked a nested + Berkeley DB transaction -- scandalous! */ + SVN_ERR(svn_stream_close(tb->target_stream)); + + /* Tell the dag subsystem that we're finished with our edits. */ + SVN_ERR(svn_fs_base__retry_txn(tb->root->fs, + txn_body_txdelta_finalize_edits, tb, + FALSE, tb->pool)); + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +txn_body_apply_textdelta(void *baton, trail_t *trail) +{ + txdelta_baton_t *tb = (txdelta_baton_t *) baton; + parent_path_t *parent_path; + const char *txn_id = tb->root->txn; + + /* Call open_path with no flags, as we want this to return an error + if the node for which we are searching doesn't exist. */ + SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, + trail, trail->pool)); + + /* Check to see if path is locked; if so, check that we can use it. */ + if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE, + trail, trail->pool)); + + /* Now, make sure this path is mutable. */ + SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, + trail, trail->pool)); + tb->node = parent_path->node; + + if (tb->base_checksum) + { + svn_checksum_t *checksum; + + /* Until we finalize the node, its data_key points to the old + contents, in other words, the base text. */ + SVN_ERR(svn_fs_base__dag_file_checksum(&checksum, + tb->base_checksum->kind, + tb->node, trail, trail->pool)); + /* TODO: This only compares checksums if they are the same kind, but + we're calculating both SHA1 and MD5 checksums somewhere in + reps-strings.c. Could we keep them both around somehow so this + check could be more comprehensive? */ + if (!svn_checksum_match(tb->base_checksum, checksum)) + return svn_checksum_mismatch_err(tb->base_checksum, checksum, + trail->pool, + _("Base checksum mismatch on '%s'"), + tb->path); + } + + /* Make a readable "source" stream out of the current contents of + ROOT/PATH; obviously, this must done in the context of a db_txn. + The stream is returned in tb->source_stream. */ + SVN_ERR(svn_fs_base__dag_get_contents(&(tb->source_stream), + tb->node, trail, tb->pool)); + + /* Make a writable "target" stream */ + SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->target_stream), tb->node, + txn_id, trail, tb->pool)); + + /* Make a writable "string" stream which writes data to + tb->target_string. */ + tb->target_string = svn_stringbuf_create_empty(tb->pool); + tb->string_stream = svn_stream_create(tb, tb->pool); + svn_stream_set_write(tb->string_stream, write_to_string); + + /* Now, create a custom window handler that uses our two streams. */ + svn_txdelta_apply(tb->source_stream, + tb->string_stream, + NULL, + tb->path, + tb->pool, + &(tb->interpreter), + &(tb->interpreter_baton)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_apply_textdelta(svn_txdelta_window_handler_t *contents_p, + void **contents_baton_p, + svn_fs_root_t *root, + const char *path, + svn_checksum_t *base_checksum, + svn_checksum_t *result_checksum, + apr_pool_t *pool) +{ + txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); + + tb->root = root; + tb->path = path; + tb->pool = pool; + tb->base_checksum = svn_checksum_dup(base_checksum, pool); + tb->result_checksum = svn_checksum_dup(result_checksum, pool); + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_textdelta, tb, + FALSE, pool)); + + *contents_p = window_consumer; + *contents_baton_p = tb; + return SVN_NO_ERROR; +} + +/* --- End machinery for svn_fs_apply_textdelta() --- */ + +/* --- Machinery for svn_fs_apply_text() --- */ + +/* Baton for svn_fs_apply_text(). */ +struct text_baton_t +{ + /* The original file info */ + svn_fs_root_t *root; + const char *path; + + /* Derived from the file info */ + dag_node_t *node; + + /* The returned stream that will accept the file's new contents. */ + svn_stream_t *stream; + + /* The actual fs stream that the returned stream will write to. */ + svn_stream_t *file_stream; + + /* Checksum for the final fulltext written to the file. May + be null, in which case ignored. */ + svn_checksum_t *result_checksum; + + /* Pool used by db txns */ + apr_pool_t *pool; +}; + + +/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits, but for + * fulltext data, not text deltas. Closes BATON->file_stream. + * + * Note: If you're confused about how this function relates to another + * of similar name, think of it this way: + * + * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits() + * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits() + */ +static svn_error_t * +txn_body_fulltext_finalize_edits(void *baton, trail_t *trail) +{ + struct text_baton_t *tb = baton; + SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node, + tb->result_checksum, + tb->root->txn, + trail, trail->pool)); + + /* Make a record of this modification in the changes table. */ + return add_change(tb->root->fs, tb->root->txn, tb->path, + svn_fs_base__dag_get_id(tb->node), + svn_fs_path_change_modify, TRUE, FALSE, trail, + trail->pool); +} + +/* Write function for the publically returned stream. */ +static svn_error_t * +text_stream_writer(void *baton, + const char *data, + apr_size_t *len) +{ + struct text_baton_t *tb = baton; + + /* Psst, here's some data. Pass it on to the -real- file stream. */ + return svn_stream_write(tb->file_stream, data, len); +} + +/* Close function for the publically returned stream. */ +static svn_error_t * +text_stream_closer(void *baton) +{ + struct text_baton_t *tb = baton; + + /* Close the internal-use stream. ### This used to be inside of + txn_body_fulltext_finalize_edits(), but that invoked a nested + Berkeley DB transaction -- scandalous! */ + SVN_ERR(svn_stream_close(tb->file_stream)); + + /* Need to tell fs that we're done sending text */ + return svn_fs_base__retry_txn(tb->root->fs, + txn_body_fulltext_finalize_edits, tb, + FALSE, tb->pool); +} + + +static svn_error_t * +txn_body_apply_text(void *baton, trail_t *trail) +{ + struct text_baton_t *tb = baton; + parent_path_t *parent_path; + const char *txn_id = tb->root->txn; + + /* Call open_path with no flags, as we want this to return an error + if the node for which we are searching doesn't exist. */ + SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, + trail, trail->pool)); + + /* Check to see if path is locked; if so, check that we can use it. */ + if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) + SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE, + trail, trail->pool)); + + /* Now, make sure this path is mutable. */ + SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, + trail, trail->pool)); + tb->node = parent_path->node; + + /* Make a writable stream for replacing the file's text. */ + SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->file_stream), tb->node, + txn_id, trail, tb->pool)); + + /* Create a 'returnable' stream which writes to the file_stream. */ + tb->stream = svn_stream_create(tb, tb->pool); + svn_stream_set_write(tb->stream, text_stream_writer); + svn_stream_set_close(tb->stream, text_stream_closer); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_apply_text(svn_stream_t **contents_p, + svn_fs_root_t *root, + const char *path, + svn_checksum_t *result_checksum, + apr_pool_t *pool) +{ + struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); + + tb->root = root; + tb->path = path; + tb->pool = pool; + tb->result_checksum = svn_checksum_dup(result_checksum, pool); + + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_text, tb, + FALSE, pool)); + + *contents_p = tb->stream; + return SVN_NO_ERROR; +} + +/* --- End machinery for svn_fs_apply_text() --- */ + + +/* Note: we're sharing the `things_changed_args' struct with + svn_fs_props_changed(). */ + +static svn_error_t * +txn_body_contents_changed(void *baton, trail_t *trail) +{ + struct things_changed_args *args = baton; + dag_node_t *node1, *node2; + + SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool)); + SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool)); + return svn_fs_base__things_different(NULL, args->changed_p, + node1, node2, trail, trail->pool); +} + + +/* Note: it is acceptable for this function to call back into + top-level interfaces because it does not itself use trails. */ +static svn_error_t * +base_contents_changed(svn_boolean_t *changed_p, + svn_fs_root_t *root1, + const char *path1, + svn_fs_root_t *root2, + const char *path2, + apr_pool_t *pool) +{ + struct things_changed_args args; + + /* Check that roots are in the same fs. */ + if (root1->fs != root2->fs) + return svn_error_create + (SVN_ERR_FS_GENERAL, NULL, + _("Cannot compare file contents between two different filesystems")); + + /* Check that both paths are files. */ + { + svn_node_kind_t kind; + + SVN_ERR(base_check_path(&kind, root1, path1, pool)); + if (kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1); + + SVN_ERR(base_check_path(&kind, root2, path2, pool)); + if (kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2); + } + + args.root1 = root1; + args.root2 = root2; + args.path1 = path1; + args.path2 = path2; + args.changed_p = changed_p; + args.pool = pool; + + return svn_fs_base__retry_txn(root1->fs, txn_body_contents_changed, &args, + TRUE, pool); +} + + + +/* Public interface to computing file text deltas. */ + +/* Note: it is acceptable for this function to call back into + public FS API interfaces because it does not itself use trails. */ +static svn_error_t * +base_get_file_delta_stream(svn_txdelta_stream_t **stream_p, + svn_fs_root_t *source_root, + const char *source_path, + svn_fs_root_t *target_root, + const char *target_path, + apr_pool_t *pool) +{ + svn_stream_t *source, *target; + svn_txdelta_stream_t *delta_stream; + + /* Get read functions for the source file contents. */ + if (source_root && source_path) + SVN_ERR(base_file_contents(&source, source_root, source_path, pool)); + else + source = svn_stream_empty(pool); + + /* Get read functions for the target file contents. */ + SVN_ERR(base_file_contents(&target, target_root, target_path, pool)); + + /* Create a delta stream that turns the ancestor into the target. */ + svn_txdelta2(&delta_stream, source, target, TRUE, pool); + + *stream_p = delta_stream; + return SVN_NO_ERROR; +} + + + +/* Finding Changes */ + +struct paths_changed_args +{ + apr_hash_t *changes; + svn_fs_root_t *root; +}; + + +static svn_error_t * +txn_body_paths_changed(void *baton, + trail_t *trail) +{ + /* WARNING: This is called *without* the protection of a Berkeley DB + transaction. If you modify this function, keep that in mind. */ + + struct paths_changed_args *args = baton; + const char *txn_id; + svn_fs_t *fs = args->root->fs; + + /* Get the transaction ID from ROOT. */ + if (! args->root->is_txn_root) + SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, args->root->rev, + trail, trail->pool)); + else + txn_id = args->root->txn; + + return svn_fs_bdb__changes_fetch(&(args->changes), fs, txn_id, + trail, trail->pool); +} + + +static svn_error_t * +base_paths_changed(apr_hash_t **changed_paths_p, + svn_fs_root_t *root, + apr_pool_t *pool) +{ + struct paths_changed_args args; + args.root = root; + args.changes = NULL; + SVN_ERR(svn_fs_base__retry(root->fs, txn_body_paths_changed, &args, + FALSE, pool)); + *changed_paths_p = args.changes; + return SVN_NO_ERROR; +} + + + +/* Our coolio opaque history object. */ +typedef struct base_history_data_t +{ + /* filesystem object */ + svn_fs_t *fs; + + /* path and revision of historical location */ + const char *path; + svn_revnum_t revision; + + /* internal-use hints about where to resume the history search. */ + const char *path_hint; + svn_revnum_t rev_hint; + + /* FALSE until the first call to svn_fs_history_prev(). */ + svn_boolean_t is_interesting; +} base_history_data_t; + + +static svn_fs_history_t *assemble_history(svn_fs_t *fs, const char *path, + svn_revnum_t revision, + svn_boolean_t is_interesting, + const char *path_hint, + svn_revnum_t rev_hint, + apr_pool_t *pool); + + +static svn_error_t * +base_node_history(svn_fs_history_t **history_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + + /* We require a revision root. */ + if (root->is_txn_root) + return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); + + /* And we require that the path exist in the root. */ + SVN_ERR(base_check_path(&kind, root, path, pool)); + if (kind == svn_node_none) + return SVN_FS__NOT_FOUND(root, path); + + /* Okay, all seems well. Build our history object and return it. */ + *history_p = assemble_history(root->fs, + svn_fs__canonicalize_abspath(path, pool), + root->rev, FALSE, NULL, + SVN_INVALID_REVNUM, pool); + return SVN_NO_ERROR; +} + + +/* Examine the PARENT_PATH structure chain to determine how copy IDs + would be doled out in the event that PARENT_PATH was made mutable. + Return the ID of the copy that last affected PARENT_PATH (and the + COPY itself, if we've already fetched it). +*/ +static svn_error_t * +examine_copy_inheritance(const char **copy_id, + copy_t **copy, + svn_fs_t *fs, + parent_path_t *parent_path, + trail_t *trail, + apr_pool_t *pool) +{ + /* The default response -- our current copy ID, and no fetched COPY. */ + *copy_id = svn_fs_base__id_copy_id + (svn_fs_base__dag_get_id(parent_path->node)); + *copy = NULL; + + /* If we have no parent (we are looking at the root node), or if + this node is supposed to inherit from itself, return that fact. */ + if (! parent_path->parent) + return SVN_NO_ERROR; + + /* We could be a branch destination (which would answer our question + altogether)! But then, again, we might just have been modified + in this revision, so all bets are off. */ + if (parent_path->copy_inherit == copy_id_inherit_self) + { + /* A copy ID of "0" means we've never been branched. Therefore, + there are no copies relevant to our history. */ + if (((*copy_id)[0] == '0') && ((*copy_id)[1] == '\0')) + return SVN_NO_ERROR; + + /* Get the COPY record. If it was a real copy (not an implicit + one), we have our answer. Otherwise, we fall through to the + recursive case. */ + SVN_ERR(svn_fs_bdb__get_copy(copy, fs, *copy_id, trail, pool)); + if ((*copy)->kind != copy_kind_soft) + return SVN_NO_ERROR; + } + + /* Otherwise, our answer is dependent upon our parent. */ + return examine_copy_inheritance(copy_id, copy, fs, + parent_path->parent, trail, pool); +} + + +struct history_prev_args +{ + svn_fs_history_t **prev_history_p; + svn_fs_history_t *history; + svn_boolean_t cross_copies; + apr_pool_t *pool; +}; + + +static svn_error_t * +txn_body_history_prev(void *baton, trail_t *trail) +{ + struct history_prev_args *args = baton; + svn_fs_history_t **prev_history = args->prev_history_p; + svn_fs_history_t *history = args->history; + base_history_data_t *bhd = history->fsap_data; + const char *commit_path, *src_path, *path = bhd->path; + svn_revnum_t commit_rev, src_rev, dst_rev, revision = bhd->revision; + apr_pool_t *retpool = args->pool; + svn_fs_t *fs = bhd->fs; + parent_path_t *parent_path; + dag_node_t *node; + svn_fs_root_t *root; + const svn_fs_id_t *node_id; + const char *end_copy_id = NULL; + struct revision_root_args rr_args; + svn_boolean_t reported = bhd->is_interesting; + const char *txn_id; + copy_t *copy = NULL; + svn_boolean_t retry = FALSE; + + /* Initialize our return value. */ + *prev_history = NULL; + + /* If our last history report left us hints about where to pickup + the chase, then our last report was on the destination of a + copy. If we are crossing copies, start from those locations, + otherwise, we're all done here. */ + if (bhd->path_hint && SVN_IS_VALID_REVNUM(bhd->rev_hint)) + { + reported = FALSE; + if (! args->cross_copies) + return SVN_NO_ERROR; + path = bhd->path_hint; + revision = bhd->rev_hint; + } + + /* Construct a ROOT for the current revision. */ + rr_args.root_p = &root; + rr_args.rev = revision; + SVN_ERR(txn_body_revision_root(&rr_args, trail)); + + /* Open PATH/REVISION, and get its node and a bunch of other + goodies. */ + SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, revision, trail, + trail->pool)); + SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, + trail, trail->pool)); + node = parent_path->node; + node_id = svn_fs_base__dag_get_id(node); + commit_path = svn_fs_base__dag_get_created_path(node); + SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node, + trail, trail->pool)); + + /* The Subversion filesystem is written in such a way that a given + line of history may have at most one interesting history point + per filesystem revision. Either that node was edited (and + possibly copied), or it was copied but not edited. And a copy + source cannot be from the same revision as its destination. So, + if our history revision matches its node's commit revision, we + know that ... */ + if (revision == commit_rev) + { + if (! reported) + { + /* ... we either have not yet reported on this revision (and + need now to do so) ... */ + *prev_history = assemble_history(fs, + apr_pstrdup(retpool, commit_path), + commit_rev, TRUE, NULL, + SVN_INVALID_REVNUM, retpool); + return SVN_NO_ERROR; + } + else + { + /* ... or we *have* reported on this revision, and must now + progress toward this node's predecessor (unless there is + no predecessor, in which case we're all done!). */ + const svn_fs_id_t *pred_id; + + SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node, + trail, trail->pool)); + if (! pred_id) + return SVN_NO_ERROR; + + /* Replace NODE and friends with the information from its + predecessor. */ + SVN_ERR(svn_fs_base__dag_get_node(&node, fs, pred_id, + trail, trail->pool)); + node_id = svn_fs_base__dag_get_id(node); + commit_path = svn_fs_base__dag_get_created_path(node); + SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node, + trail, trail->pool)); + } + } + + /* Calculate a possibly relevant copy ID. */ + SVN_ERR(examine_copy_inheritance(&end_copy_id, ©, fs, + parent_path, trail, trail->pool)); + + /* Initialize some state variables. */ + src_path = NULL; + src_rev = SVN_INVALID_REVNUM; + dst_rev = SVN_INVALID_REVNUM; + + /* If our current copy ID (which is either the real copy ID of our + node, or the last copy ID which would affect our node if it were + to be made mutable) diffs at all from that of its predecessor + (which is either a real predecessor, or is the node itself + playing the predecessor role to an imaginary mutable successor), + then we need to report a copy. */ + if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id), + end_copy_id) != 0) + { + const char *remainder; + dag_node_t *dst_node; + const char *copy_dst; + + /* Get the COPY record if we haven't already fetched it. */ + if (! copy) + SVN_ERR(svn_fs_bdb__get_copy(©, fs, end_copy_id, trail, + trail->pool)); + + /* Figure out the destination path of the copy operation. */ + SVN_ERR(svn_fs_base__dag_get_node(&dst_node, fs, + copy->dst_noderev_id, + trail, trail->pool)); + copy_dst = svn_fs_base__dag_get_created_path(dst_node); + + /* If our current path was the very destination of the copy, + then our new current path will be the copy source. If our + current path was instead the *child* of the destination of + the copy, then figure out its previous location by taking its + path relative to the copy destination and appending that to + the copy source. Finally, if our current path doesn't meet + one of these other criteria ... ### for now just fallback to + the old copy hunt algorithm. */ + remainder = svn_fspath__skip_ancestor(copy_dst, path); + + if (remainder) + { + /* If we get here, then our current path is the destination + of, or the child of the destination of, a copy. Fill + in the return values and get outta here. */ + SVN_ERR(svn_fs_base__txn_get_revision + (&src_rev, fs, copy->src_txn_id, trail, trail->pool)); + SVN_ERR(svn_fs_base__txn_get_revision + (&dst_rev, fs, + svn_fs_base__id_txn_id(copy->dst_noderev_id), + trail, trail->pool)); + src_path = svn_fspath__join(copy->src_path, remainder, + trail->pool); + if (copy->kind == copy_kind_soft) + retry = TRUE; + } + } + + /* If we calculated a copy source path and revision, and the + copy source revision doesn't pre-date a revision in which we + *know* our node was modified, we'll make a 'copy-style' history + object. */ + if (src_path && SVN_IS_VALID_REVNUM(src_rev) && (src_rev >= commit_rev)) + { + /* It's possible for us to find a copy location that is the same + as the history point we've just reported. If that happens, + we simply need to take another trip through this history + search. */ + if ((dst_rev == revision) && reported) + retry = TRUE; + + *prev_history = assemble_history(fs, apr_pstrdup(retpool, path), + dst_rev, ! retry, + src_path, src_rev, retpool); + } + else + { + *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path), + commit_rev, TRUE, NULL, + SVN_INVALID_REVNUM, retpool); + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_history_prev(svn_fs_history_t **prev_history_p, + svn_fs_history_t *history, + svn_boolean_t cross_copies, + apr_pool_t *pool) +{ + svn_fs_history_t *prev_history = NULL; + base_history_data_t *bhd = history->fsap_data; + svn_fs_t *fs = bhd->fs; + + /* Special case: the root directory changes in every single + revision, no exceptions. And, the root can't be the target (or + child of a target -- duh) of a copy. So, if that's our path, + then we need only decrement our revision by 1, and there you go. */ + if (strcmp(bhd->path, "/") == 0) + { + if (! bhd->is_interesting) + prev_history = assemble_history(fs, "/", bhd->revision, + 1, NULL, SVN_INVALID_REVNUM, pool); + else if (bhd->revision > 0) + prev_history = assemble_history(fs, "/", bhd->revision - 1, + 1, NULL, SVN_INVALID_REVNUM, pool); + } + else + { + struct history_prev_args args; + prev_history = history; + + while (1) + { + /* Get a trail, and get to work. */ + + args.prev_history_p = &prev_history; + args.history = prev_history; + args.cross_copies = cross_copies; + args.pool = pool; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_history_prev, &args, + FALSE, pool)); + if (! prev_history) + break; + bhd = prev_history->fsap_data; + if (bhd->is_interesting) + break; + } + } + + *prev_history_p = prev_history; + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_history_location(const char **path, + svn_revnum_t *revision, + svn_fs_history_t *history, + apr_pool_t *pool) +{ + base_history_data_t *bhd = history->fsap_data; + + *path = apr_pstrdup(pool, bhd->path); + *revision = bhd->revision; + return SVN_NO_ERROR; +} + + +static history_vtable_t history_vtable = { + base_history_prev, + base_history_location +}; + + + +struct closest_copy_args +{ + svn_fs_root_t **root_p; + const char **path_p; + svn_fs_root_t *root; + const char *path; + apr_pool_t *pool; +}; + + +static svn_error_t * +txn_body_closest_copy(void *baton, trail_t *trail) +{ + struct closest_copy_args *args = baton; + svn_fs_root_t *root = args->root; + const char *path = args->path; + svn_fs_t *fs = root->fs; + parent_path_t *parent_path; + const svn_fs_id_t *node_id; + const char *txn_id, *copy_id; + copy_t *copy = NULL; + svn_fs_root_t *copy_dst_root; + dag_node_t *path_node_in_copy_dst, *copy_dst_node, *copy_dst_root_node; + const char *copy_dst_path; + svn_revnum_t copy_dst_rev, created_rev; + svn_error_t *err; + + *(args->path_p) = NULL; + *(args->root_p) = NULL; + + /* Get the transaction ID associated with our root. */ + if (root->is_txn_root) + txn_id = root->txn; + else + SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, root->rev, + trail, trail->pool)); + + /* Open PATH in ROOT -- it must exist. */ + SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, + trail, trail->pool)); + node_id = svn_fs_base__dag_get_id(parent_path->node); + + /* Now, examine the copy inheritance rules in play should our path + be made mutable in the future (if it isn't already). This will + tell us about the youngest affecting copy. */ + SVN_ERR(examine_copy_inheritance(©_id, ©, fs, parent_path, + trail, trail->pool)); + + /* Easy out: if the copy ID is 0, there's nothing of interest here. */ + if (((copy_id)[0] == '0') && ((copy_id)[1] == '\0')) + return SVN_NO_ERROR; + + /* Fetch our copy if examine_copy_inheritance() didn't do it for us. */ + if (! copy) + SVN_ERR(svn_fs_bdb__get_copy(©, fs, copy_id, trail, trail->pool)); + + /* Figure out the destination path and revision of the copy operation. */ + SVN_ERR(svn_fs_base__dag_get_node(©_dst_node, fs, copy->dst_noderev_id, + trail, trail->pool)); + copy_dst_path = svn_fs_base__dag_get_created_path(copy_dst_node); + SVN_ERR(svn_fs_base__dag_get_revision(©_dst_rev, copy_dst_node, + trail, trail->pool)); + + /* Turn that revision into a revision root. */ + SVN_ERR(svn_fs_base__dag_revision_root(©_dst_root_node, fs, + copy_dst_rev, trail, args->pool)); + copy_dst_root = make_revision_root(fs, copy_dst_rev, + copy_dst_root_node, args->pool); + + /* It is possible that this node was created from scratch at some + revision between COPY_DST_REV and the transaction associated with + our ROOT. Make sure that PATH exists as of COPY_DST_REV and is + related to this node-rev. */ + err = get_dag(&path_node_in_copy_dst, copy_dst_root, path, + trail, trail->pool); + if (err) + { + if ((err->apr_err == SVN_ERR_FS_NOT_FOUND) + || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + return svn_error_trace(err); + } + if ((svn_fs_base__dag_node_kind(path_node_in_copy_dst) == svn_node_none) + || (! (svn_fs_base__id_check_related + (node_id, svn_fs_base__dag_get_id(path_node_in_copy_dst))))) + { + return SVN_NO_ERROR; + } + + /* One final check must be done here. If you copy a directory and + create a new entity somewhere beneath that directory in the same + txn, then we can't claim that the copy affected the new entity. + For example, if you do: + + copy dir1 dir2 + create dir2/new-thing + commit + + then dir2/new-thing was not affected by the copy of dir1 to dir2. + We detect this situation by asking if PATH@COPY_DST_REV's + created-rev is COPY_DST_REV, and that node-revision has no + predecessors, then there is no relevant closest copy. + */ + SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node_in_copy_dst, + trail, trail->pool)); + if (created_rev == copy_dst_rev) + { + const svn_fs_id_t *pred_id; + SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, + path_node_in_copy_dst, + trail, trail->pool)); + if (! pred_id) + return SVN_NO_ERROR; + } + + *(args->path_p) = apr_pstrdup(args->pool, copy_dst_path); + *(args->root_p) = copy_dst_root; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +base_closest_copy(svn_fs_root_t **root_p, + const char **path_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct closest_copy_args args; + svn_fs_t *fs = root->fs; + svn_fs_root_t *closest_root = NULL; + const char *closest_path = NULL; + + args.root_p = &closest_root; + args.path_p = &closest_path; + args.root = root; + args.path = path; + args.pool = pool; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_closest_copy, &args, + FALSE, pool)); + *root_p = closest_root; + *path_p = closest_path; + return SVN_NO_ERROR; +} + + +/* Return a new history object (marked as "interesting") for PATH and + REVISION, allocated in POOL, and with its members set to the values + of the parameters provided. Note that PATH and PATH_HINT are not + duped into POOL -- it is the responsibility of the caller to ensure + that this happens. */ +static svn_fs_history_t * +assemble_history(svn_fs_t *fs, + const char *path, + svn_revnum_t revision, + svn_boolean_t is_interesting, + const char *path_hint, + svn_revnum_t rev_hint, + apr_pool_t *pool) +{ + svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history)); + base_history_data_t *bhd = apr_pcalloc(pool, sizeof(*bhd)); + bhd->path = path; + bhd->revision = revision; + bhd->is_interesting = is_interesting; + bhd->path_hint = path_hint; + bhd->rev_hint = rev_hint; + bhd->fs = fs; + history->vtable = &history_vtable; + history->fsap_data = bhd; + return history; +} + + +svn_error_t * +svn_fs_base__get_path_kind(svn_node_kind_t *kind, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + svn_revnum_t head_rev; + svn_fs_root_t *root; + dag_node_t *root_dir, *path_node; + svn_error_t *err; + + /* Get HEAD revision, */ + SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool)); + + /* Then convert it into a root_t, */ + SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev, + trail, pool)); + root = make_revision_root(trail->fs, head_rev, root_dir, pool); + + /* And get the dag_node for path in the root_t. */ + err = get_dag(&path_node, root, path, trail, pool); + if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND)) + { + svn_error_clear(err); + *kind = svn_node_none; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + *kind = svn_fs_base__dag_node_kind(path_node); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__get_path_created_rev(svn_revnum_t *rev, + const char *path, + trail_t *trail, + apr_pool_t *pool) +{ + svn_revnum_t head_rev, created_rev; + svn_fs_root_t *root; + dag_node_t *root_dir, *path_node; + svn_error_t *err; + + /* Get HEAD revision, */ + SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool)); + + /* Then convert it into a root_t, */ + SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev, + trail, pool)); + root = make_revision_root(trail->fs, head_rev, root_dir, pool); + + /* And get the dag_node for path in the root_t. */ + err = get_dag(&path_node, root, path, trail, pool); + if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND)) + { + svn_error_clear(err); + *rev = SVN_INVALID_REVNUM; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + /* Find the created_rev of the dag_node. */ + SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node, + trail, pool)); + + *rev = created_rev; + return SVN_NO_ERROR; +} + + + +/*** Finding the Origin of a Line of History ***/ + +/* Set *PREV_PATH and *PREV_REV to the path and revision which + represent the location at which PATH in FS was located immediately + prior to REVISION iff there was a copy operation (to PATH or one of + its parent directories) between that previous location and + PATH@REVISION. + + If there was no such copy operation in that portion of PATH's + history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM. + + WARNING: Do *not* call this from inside a trail. */ +static svn_error_t * +prev_location(const char **prev_path, + svn_revnum_t *prev_rev, + svn_fs_t *fs, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + const char *copy_path, *copy_src_path, *remainder; + svn_fs_root_t *copy_root; + svn_revnum_t copy_src_rev; + + /* Ask about the most recent copy which affected PATH@REVISION. If + there was no such copy, we're done. */ + SVN_ERR(base_closest_copy(©_root, ©_path, root, path, pool)); + if (! copy_root) + { + *prev_rev = SVN_INVALID_REVNUM; + *prev_path = NULL; + return SVN_NO_ERROR; + } + + /* Ultimately, it's not the path of the closest copy's source that + we care about -- it's our own path's location in the copy source + revision. So we'll tack the relative path that expresses the + difference between the copy destination and our path in the copy + revision onto the copy source path to determine this information. + + In other words, if our path is "/branches/my-branch/foo/bar", and + we know that the closest relevant copy was a copy of "/trunk" to + "/branches/my-branch", then that relative path under the copy + destination is "/foo/bar". Tacking that onto the copy source + path tells us that our path was located at "/trunk/foo/bar" + before the copy. + */ + SVN_ERR(base_copied_from(©_src_rev, ©_src_path, + copy_root, copy_path, pool)); + remainder = svn_fspath__skip_ancestor(copy_path, path); + *prev_path = svn_fspath__join(copy_src_path, remainder, pool); + *prev_rev = copy_src_rev; + return SVN_NO_ERROR; +} + + +struct id_created_rev_args { + svn_revnum_t revision; + const svn_fs_id_t *id; + const char *path; +}; + + +static svn_error_t * +txn_body_id_created_rev(void *baton, trail_t *trail) +{ + struct id_created_rev_args *args = baton; + dag_node_t *node; + + SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id, + trail, trail->pool)); + return svn_fs_base__dag_get_revision(&(args->revision), node, + trail, trail->pool); +} + + +struct get_set_node_origin_args { + const svn_fs_id_t *origin_id; + const char *node_id; +}; + + +static svn_error_t * +txn_body_get_node_origin(void *baton, trail_t *trail) +{ + struct get_set_node_origin_args *args = baton; + return svn_fs_bdb__get_node_origin(&(args->origin_id), trail->fs, + args->node_id, trail, trail->pool); +} + +static svn_error_t * +txn_body_set_node_origin(void *baton, trail_t *trail) +{ + struct get_set_node_origin_args *args = baton; + return svn_fs_bdb__set_node_origin(trail->fs, args->node_id, + args->origin_id, trail, trail->pool); +} + +static svn_error_t * +base_node_origin_rev(svn_revnum_t *revision, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + svn_fs_t *fs = root->fs; + base_fs_data_t *bfd = fs->fsap_data; + struct get_set_node_origin_args args; + const svn_fs_id_t *origin_id = NULL; + struct id_created_rev_args icr_args; + + /* Canonicalize the input path so that the path-math that + prev_location() does below will work. */ + path = svn_fs__canonicalize_abspath(path, pool); + + /* If we have support for the node-origins table, we'll try to use + it. */ + if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT) + { + const svn_fs_id_t *id; + svn_error_t *err; + + SVN_ERR(base_node_id(&id, root, path, pool)); + args.node_id = svn_fs_base__id_node_id(id); + err = svn_fs_base__retry_txn(root->fs, txn_body_get_node_origin, &args, + FALSE, pool); + + /* If we got a value for the origin node-revision-ID, that's + great. If we didn't, that's sad but non-fatal -- we'll just + figure it out the hard way, then record it so we don't have + suffer again the next time. */ + if (! err) + { + origin_id = args.origin_id; + } + else if (err->apr_err == SVN_ERR_FS_NO_SUCH_NODE_ORIGIN) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + SVN_ERR(err); + } + + /* If we haven't yet found a node origin ID, we'll go spelunking for one. */ + if (! origin_id) + { + svn_fs_root_t *curroot = root; + apr_pool_t *subpool = svn_pool_create(pool); + apr_pool_t *predidpool = svn_pool_create(pool); + svn_stringbuf_t *lastpath = + svn_stringbuf_create(path, pool); + svn_revnum_t lastrev = SVN_INVALID_REVNUM; + const svn_fs_id_t *pred_id; + + /* Walk the closest-copy chain back to the first copy in our history. + + NOTE: We merely *assume* that this is faster than walking the + predecessor chain, because we *assume* that copies of parent + directories happen less often than modifications to a given item. */ + while (1) + { + svn_revnum_t currev; + const char *curpath = lastpath->data; + + /* Get a root pointing to LASTREV. (The first time around, + LASTREV is invalid, but that's cool because CURROOT is + already initialized.) */ + if (SVN_IS_VALID_REVNUM(lastrev)) + SVN_ERR(svn_fs_base__revision_root(&curroot, fs, + lastrev, subpool)); + + /* Find the previous location using the closest-copy shortcut. */ + SVN_ERR(prev_location(&curpath, &currev, fs, curroot, + curpath, subpool)); + if (! curpath) + break; + + /* Update our LASTPATH and LASTREV variables (which survive + SUBPOOL). */ + svn_stringbuf_set(lastpath, curpath); + lastrev = currev; + } + + /* Walk the predecessor links back to origin. */ + SVN_ERR(base_node_id(&pred_id, curroot, lastpath->data, pool)); + while (1) + { + struct txn_pred_id_args pid_args; + svn_pool_clear(subpool); + pid_args.id = pred_id; + pid_args.pred_id = NULL; + pid_args.pool = subpool; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &pid_args, + FALSE, subpool)); + if (! pid_args.pred_id) + break; + svn_pool_clear(predidpool); + pred_id = svn_fs_base__id_copy(pid_args.pred_id, predidpool); + } + + /* Okay. PRED_ID should hold our origin ID now. */ + origin_id = svn_fs_base__id_copy(pred_id, pool); + + /* If our filesystem version supports it, let's remember this + value from now on. */ + if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT) + { + args.origin_id = origin_id; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_set_node_origin, + &args, TRUE, subpool)); + } + + svn_pool_destroy(predidpool); + svn_pool_destroy(subpool); + } + + /* Okay. We have an origin node-revision-ID. Let's get a created + revision from it. */ + icr_args.id = origin_id; + SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_id_created_rev, &icr_args, + TRUE, pool)); + *revision = icr_args.revision; + return SVN_NO_ERROR; +} + + + +/* Mergeinfo Queries */ + + +/* Examine directory NODE's immediately children for mergeinfo. + + For those which have explicit mergeinfo, add their mergeinfo to + RESULT_CATALOG (allocated in RESULT_CATALOG's pool). + + For those which don't, but sit atop trees which contain mergeinfo + somewhere deeper, add them to *CHILDREN_ATOP_MERGEINFO_TREES, a + hash mapping dirent names to dag_node_t * objects, allocated + from that hash's pool. + + For those which neither have explicit mergeinfo nor sit atop trees + which contain mergeinfo, ignore them. + + Use TRAIL->pool for temporary allocations. */ + +struct get_mergeinfo_data_and_entries_baton +{ + svn_mergeinfo_catalog_t result_catalog; + apr_hash_t *children_atop_mergeinfo_trees; + dag_node_t *node; + const char *node_path; +}; + +static svn_error_t * +txn_body_get_mergeinfo_data_and_entries(void *baton, trail_t *trail) +{ + struct get_mergeinfo_data_and_entries_baton *args = baton; + dag_node_t *node = args->node; + apr_hash_t *entries; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(trail->pool); + apr_pool_t *result_pool = apr_hash_pool_get(args->result_catalog); + apr_pool_t *children_pool = + apr_hash_pool_get(args->children_atop_mergeinfo_trees); + + SVN_ERR_ASSERT(svn_fs_base__dag_node_kind(node) == svn_node_dir); + + SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool)); + for (hi = apr_hash_first(trail->pool, entries); hi; hi = apr_hash_next(hi)) + { + void *val; + svn_fs_dirent_t *dirent; + const svn_fs_id_t *child_id; + dag_node_t *child_node; + svn_boolean_t has_mergeinfo; + apr_int64_t kid_count; + + svn_pool_clear(iterpool); + apr_hash_this(hi, NULL, NULL, &val); + dirent = val; + child_id = dirent->id; + + /* Get the node for this child. */ + SVN_ERR(svn_fs_base__dag_get_node(&child_node, trail->fs, child_id, + trail, iterpool)); + + /* Query the child node's mergeinfo stats. */ + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &kid_count, + child_node, trail, + iterpool)); + + /* If the child has mergeinfo, add it to the result catalog. */ + if (has_mergeinfo) + { + apr_hash_t *plist; + svn_mergeinfo_t child_mergeinfo; + svn_string_t *pval; + svn_error_t *err; + + SVN_ERR(svn_fs_base__dag_get_proplist(&plist, child_node, + trail, iterpool)); + pval = svn_hash_gets(plist, SVN_PROP_MERGEINFO); + if (! pval) + { + svn_string_t *id_str = svn_fs_base__id_unparse(child_id, + iterpool); + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Node-revision '%s' claims to have " + "mergeinfo but doesn't"), + id_str->data); + } + /* Issue #3896: If syntactically invalid mergeinfo is present on + CHILD_NODE then treat it as if no mergeinfo is present rather + than raising a parse error. */ + err = svn_mergeinfo_parse(&child_mergeinfo, pval->data, + result_pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else + { + svn_hash_sets(args->result_catalog, + svn_fspath__join(args->node_path, dirent->name, + result_pool), + child_mergeinfo); + } + } + + /* If the child has descendants with mergeinfo -- that is, if + the count of descendants beneath it carrying mergeinfo, not + including itself, is non-zero -- then add it to the + children_atop_mergeinfo_trees hash to be crawled later. */ + if ((kid_count - (has_mergeinfo ? 1 : 0)) > 0) + { + if (svn_fs_base__dag_node_kind(child_node) != svn_node_dir) + { + svn_string_t *id_str = svn_fs_base__id_unparse(child_id, + iterpool); + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Node-revision '%s' claims to sit " + "atop a tree containing mergeinfo " + "but is not a directory"), + id_str->data); + } + svn_hash_sets(args->children_atop_mergeinfo_trees, + apr_pstrdup(children_pool, dirent->name), + svn_fs_base__dag_dup(child_node, children_pool)); + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +static svn_error_t * +crawl_directory_for_mergeinfo(svn_fs_t *fs, + dag_node_t *node, + const char *node_path, + svn_mergeinfo_catalog_t result_catalog, + apr_pool_t *pool) +{ + struct get_mergeinfo_data_and_entries_baton gmdae_args; + apr_hash_t *children_atop_mergeinfo_trees = apr_hash_make(pool); + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + /* Add mergeinfo for immediate children that have it, and fetch + immediate children that *don't* have it but sit atop trees that do. */ + gmdae_args.result_catalog = result_catalog; + gmdae_args.children_atop_mergeinfo_trees = children_atop_mergeinfo_trees; + gmdae_args.node = node; + gmdae_args.node_path = node_path; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_mergeinfo_data_and_entries, + &gmdae_args, FALSE, pool)); + + /* If no children sit atop trees with mergeinfo, we're done. + Otherwise, recurse on those children. */ + + if (apr_hash_count(children_atop_mergeinfo_trees) == 0) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, children_atop_mergeinfo_trees); + hi; + hi = apr_hash_next(hi)) + { + const void *key; + void *val; + svn_pool_clear(iterpool); + apr_hash_this(hi, &key, NULL, &val); + SVN_ERR(crawl_directory_for_mergeinfo(fs, val, + svn_fspath__join(node_path, key, + iterpool), + result_catalog, iterpool)); + } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +/* Calculate the mergeinfo for PATH under revision ROOT using + inheritance type INHERIT. Set *MERGEINFO to the mergeinfo, or to + NULL if there is none. Results are allocated in POOL; TRAIL->pool + is used for temporary allocations. */ + +struct get_mergeinfo_for_path_baton +{ + svn_mergeinfo_t *mergeinfo; + svn_fs_root_t *root; + const char *path; + svn_mergeinfo_inheritance_t inherit; + svn_boolean_t adjust_inherited_mergeinfo; + apr_pool_t *pool; +}; + +static svn_error_t * +txn_body_get_mergeinfo_for_path(void *baton, trail_t *trail) +{ + struct get_mergeinfo_for_path_baton *args = baton; + parent_path_t *parent_path, *nearest_ancestor; + apr_hash_t *proplist; + svn_string_t *mergeinfo_string; + apr_pool_t *iterpool; + dag_node_t *node = NULL; + + *(args->mergeinfo) = NULL; + + SVN_ERR(open_path(&parent_path, args->root, args->path, 0, + NULL, trail, trail->pool)); + + /* Init the nearest ancestor. */ + nearest_ancestor = parent_path; + if (args->inherit == svn_mergeinfo_nearest_ancestor) + { + if (! parent_path->parent) + return SVN_NO_ERROR; + nearest_ancestor = parent_path->parent; + } + + iterpool = svn_pool_create(trail->pool); + while (TRUE) + { + svn_boolean_t has_mergeinfo; + apr_int64_t count; + + svn_pool_clear(iterpool); + + node = nearest_ancestor->node; + SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &count, + node, trail, iterpool)); + if (has_mergeinfo) + break; + + /* No need to loop if we're looking for explicit mergeinfo. */ + if (args->inherit == svn_mergeinfo_explicit) + { + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + + nearest_ancestor = nearest_ancestor->parent; + + /* Run out? There's no mergeinfo. */ + if (! nearest_ancestor) + { + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, trail, trail->pool)); + mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); + if (! mergeinfo_string) + { + svn_string_t *id_str = + svn_fs_base__id_unparse(svn_fs_base__dag_get_id(node), trail->pool); + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Node-revision '%s' claims to have " + "mergeinfo but doesn't"), id_str->data); + } + + /* Parse the mergeinfo; store the result in ARGS->MERGEINFO. */ + { + /* Issue #3896: If a node has syntactically invalid mergeinfo, then + treat it as if no mergeinfo is present rather than raising a parse + error. */ + svn_error_t *err = svn_mergeinfo_parse(args->mergeinfo, + mergeinfo_string->data, + args->pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + svn_error_clear(err); + err = NULL; + args->mergeinfo = NULL; + } + return svn_error_trace(err); + } + } + + /* If our nearest ancestor is the very path we inquired about, we + can return the mergeinfo results directly. Otherwise, we're + inheriting the mergeinfo, so we need to a) remove non-inheritable + ranges and b) telescope the merged-from paths. */ + if (args->adjust_inherited_mergeinfo && (nearest_ancestor != parent_path)) + { + svn_mergeinfo_t tmp_mergeinfo; + + SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *args->mergeinfo, + NULL, SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, TRUE, + trail->pool, trail->pool)); + SVN_ERR(svn_fs__append_to_merged_froms(args->mergeinfo, tmp_mergeinfo, + parent_path_relpath( + parent_path, nearest_ancestor, + trail->pool), + args->pool)); + } + + return SVN_NO_ERROR; +} + +/* Set **NODE to the dag node for PATH in ROOT (allocated in POOL), + and query its mergeinfo stats, setting HAS_MERGEINFO and + CHILD_MERGEINFO_COUNT appropriately. */ + +struct get_node_mergeinfo_stats_baton +{ + dag_node_t *node; + svn_boolean_t has_mergeinfo; + apr_int64_t child_mergeinfo_count; + svn_fs_root_t *root; + const char *path; +}; + +static svn_error_t * +txn_body_get_node_mergeinfo_stats(void *baton, trail_t *trail) +{ + struct get_node_mergeinfo_stats_baton *args = baton; + + SVN_ERR(get_dag(&(args->node), args->root, args->path, + trail, trail->pool)); + return svn_fs_base__dag_get_mergeinfo_stats(&(args->has_mergeinfo), + &(args->child_mergeinfo_count), + args->node, trail, + trail->pool); +} + + +/* Get the mergeinfo for a set of paths, returned in + *MERGEINFO_CATALOG. Returned values are allocated in POOL, while + temporary values are allocated in a sub-pool. */ +static svn_error_t * +get_mergeinfos_for_paths(svn_fs_root_t *root, + svn_mergeinfo_catalog_t *mergeinfo_catalog, + const apr_array_header_t *paths, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_mergeinfo_catalog_t result_catalog = apr_hash_make(result_pool); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + for (i = 0; i < paths->nelts; i++) + { + svn_mergeinfo_t path_mergeinfo; + struct get_mergeinfo_for_path_baton gmfp_args; + const char *path = APR_ARRAY_IDX(paths, i, const char *); + + svn_pool_clear(iterpool); + + path = svn_fs__canonicalize_abspath(path, iterpool); + + /* Get the mergeinfo for PATH itself. */ + gmfp_args.mergeinfo = &path_mergeinfo; + gmfp_args.root = root; + gmfp_args.path = path; + gmfp_args.inherit = inherit; + gmfp_args.pool = result_pool; + gmfp_args.adjust_inherited_mergeinfo = adjust_inherited_mergeinfo; + SVN_ERR(svn_fs_base__retry_txn(root->fs, + txn_body_get_mergeinfo_for_path, + &gmfp_args, FALSE, iterpool)); + if (path_mergeinfo) + svn_hash_sets(result_catalog, apr_pstrdup(result_pool, path), + path_mergeinfo); + + /* If we're including descendants, do so. */ + if (include_descendants) + { + svn_boolean_t do_crawl; + struct get_node_mergeinfo_stats_baton gnms_args; + + /* Query the node and its mergeinfo stats. */ + gnms_args.root = root; + gnms_args.path = path; + SVN_ERR(svn_fs_base__retry_txn(root->fs, + txn_body_get_node_mergeinfo_stats, + &gnms_args, FALSE, iterpool)); + + /* Determine if there's anything worth crawling here. */ + if (svn_fs_base__dag_node_kind(gnms_args.node) != svn_node_dir) + do_crawl = FALSE; + else + do_crawl = ((gnms_args.child_mergeinfo_count > 1) + || ((gnms_args.child_mergeinfo_count == 1) + && (! gnms_args.has_mergeinfo))); + + /* If it's worth crawling, crawl. */ + if (do_crawl) + SVN_ERR(crawl_directory_for_mergeinfo(root->fs, gnms_args.node, + path, result_catalog, + iterpool)); + } + } + svn_pool_destroy(iterpool); + + *mergeinfo_catalog = result_catalog; + return SVN_NO_ERROR; +} + + +/* Implements svn_fs_get_mergeinfo. */ +static svn_error_t * +base_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, + svn_fs_root_t *root, + const apr_array_header_t *paths, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* Verify that our filesystem version supports mergeinfo stuff. */ + SVN_ERR(svn_fs_base__test_required_feature_format + (root->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); + + /* We require a revision root. */ + if (root->is_txn_root) + return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL); + + /* Retrieve a path -> mergeinfo mapping. */ + return get_mergeinfos_for_paths(root, catalog, paths, + inherit, include_descendants, + adjust_inherited_mergeinfo, + result_pool, scratch_pool); +} + + + +/* Creating root objects. */ + + +static root_vtable_t root_vtable = { + base_paths_changed, + base_check_path, + base_node_history, + base_node_id, + base_node_created_rev, + base_node_origin_rev, + base_node_created_path, + base_delete_node, + base_copied_from, + base_closest_copy, + base_node_prop, + base_node_proplist, + base_change_node_prop, + base_props_changed, + base_dir_entries, + base_make_dir, + base_copy, + base_revision_link, + base_file_length, + base_file_checksum, + base_file_contents, + NULL, + base_make_file, + base_apply_textdelta, + base_apply_text, + base_contents_changed, + base_get_file_delta_stream, + base_merge, + base_get_mergeinfo, +}; + + +/* Construct a new root object in FS, allocated from POOL. */ +static svn_fs_root_t * +make_root(svn_fs_t *fs, + apr_pool_t *pool) +{ + svn_fs_root_t *root = apr_pcalloc(pool, sizeof(*root)); + base_root_data_t *brd = apr_palloc(pool, sizeof(*brd)); + + root->fs = fs; + root->pool = pool; + + /* Init the node ID cache. */ + brd->node_cache = apr_hash_make(pool); + brd->node_cache_idx = 0; + root->vtable = &root_vtable; + root->fsap_data = brd; + + return root; +} + + +/* Construct a root object referring to the root of REVISION in FS, + whose root directory is ROOT_DIR. Create the new root in POOL. */ +static svn_fs_root_t * +make_revision_root(svn_fs_t *fs, + svn_revnum_t rev, + dag_node_t *root_dir, + apr_pool_t *pool) +{ + svn_fs_root_t *root = make_root(fs, pool); + base_root_data_t *brd = root->fsap_data; + + root->is_txn_root = FALSE; + root->rev = rev; + brd->root_dir = root_dir; + + return root; +} + + +/* Construct a root object referring to the root of the transaction + named TXN and based on revision BASE_REV in FS. FLAGS represents + the behavior of the transaction. Create the new root in POOL. */ +static svn_fs_root_t * +make_txn_root(svn_fs_t *fs, + const char *txn, + svn_revnum_t base_rev, + apr_uint32_t flags, + apr_pool_t *pool) +{ + svn_fs_root_t *root = make_root(fs, pool); + root->is_txn_root = TRUE; + root->txn = apr_pstrdup(root->pool, txn); + root->txn_flags = flags; + root->rev = base_rev; + + return root; +} diff --git a/subversion/libsvn_fs_base/tree.h b/subversion/libsvn_fs_base/tree.h new file mode 100644 index 0000000..2e81a17 --- /dev/null +++ b/subversion/libsvn_fs_base/tree.h @@ -0,0 +1,99 @@ +/* tree.h : internal interface to tree node functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_TREE_H +#define SVN_LIBSVN_FS_TREE_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "svn_props.h" + + + +/* These functions implement some of the calls in the FS loader + library's fs and txn vtables. */ + +svn_error_t *svn_fs_base__revision_root(svn_fs_root_t **root_p, svn_fs_t *fs, + svn_revnum_t rev, apr_pool_t *pool); + +svn_error_t *svn_fs_base__deltify(svn_fs_t *fs, svn_revnum_t rev, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__commit_txn(const char **conflict_p, + svn_revnum_t *new_rev, svn_fs_txn_t *txn, + apr_pool_t *pool); + +svn_error_t *svn_fs_base__txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn, + apr_pool_t *pool); + + + +/* Inserting and retrieving miscellany records in the fs */ + +/* Set the value of miscellaneous records KEY to VAL in FS. To remove + a value altogether, pass NULL for VAL. + + KEY and VAL should be NULL-terminated strings. */ +svn_error_t * +svn_fs_base__miscellaneous_set(svn_fs_t *fs, + const char *key, + const char *val, + apr_pool_t *pool); + +/* Retrieve the miscellany records for KEY into *VAL for FS, allocated + in POOL. If the fs doesn't support miscellany storage, or the value + does not exist, *VAL is set to NULL. + + KEY should be a NULL-terminated string. */ +svn_error_t * +svn_fs_base__miscellaneous_get(const char **val, + svn_fs_t *fs, + const char *key, + apr_pool_t *pool); + + + + + +/* Helper func: in the context of TRAIL, return the KIND of PATH in + head revision. If PATH doesn't exist, set *KIND to svn_node_none.*/ +svn_error_t *svn_fs_base__get_path_kind(svn_node_kind_t *kind, + const char *path, + trail_t *trail, + apr_pool_t *pool); + +/* Helper func: in the context of TRAIL, set *REV to the created-rev + of PATH in head revision. If PATH doesn't exist, set *REV to + SVN_INVALID_REVNUM. */ +svn_error_t *svn_fs_base__get_path_created_rev(svn_revnum_t *rev, + const char *path, + trail_t *trail, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_TREE_H */ diff --git a/subversion/libsvn_fs_base/util/fs_skels.c b/subversion/libsvn_fs_base/util/fs_skels.c new file mode 100644 index 0000000..f3466b9 --- /dev/null +++ b/subversion/libsvn_fs_base/util/fs_skels.c @@ -0,0 +1,1515 @@ +/* fs_skels.c --- conversion between fs native types and skeletons + * + * ==================================================================== + * 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_md5.h> +#include <apr_sha1.h> + +#include "svn_error.h" +#include "svn_string.h" +#include "svn_types.h" +#include "svn_time.h" + +#include "private/svn_skel.h" +#include "private/svn_dep_compat.h" +#include "private/svn_subr_private.h" + +#include "svn_checksum.h" +#include "fs_skels.h" +#include "../id.h" + + +static svn_error_t * +skel_err(const char *skel_type) +{ + return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL, + "Malformed%s%s skeleton", + skel_type ? " " : "", + skel_type ? skel_type : ""); +} + + + +/*** Validity Checking ***/ + +static svn_boolean_t +is_valid_checksum_skel(svn_skel_t *skel) +{ + if (svn_skel__list_length(skel) != 2) + return FALSE; + + if (svn_skel__matches_atom(skel->children, "md5") + && skel->children->next->is_atom) + return TRUE; + + if (svn_skel__matches_atom(skel->children, "sha1") + && skel->children->next->is_atom) + return TRUE; + + return FALSE; +} + + +static svn_boolean_t +is_valid_revision_skel(svn_skel_t *skel) +{ + int len = svn_skel__list_length(skel); + + if ((len == 2) + && svn_skel__matches_atom(skel->children, "revision") + && skel->children->next->is_atom) + return TRUE; + + return FALSE; +} + + +static svn_boolean_t +is_valid_transaction_skel(svn_skel_t *skel, transaction_kind_t *kind) +{ + int len = svn_skel__list_length(skel); + + if (len != 5) + return FALSE; + + /* Determine (and verify) the kind. */ + if (svn_skel__matches_atom(skel->children, "transaction")) + *kind = transaction_kind_normal; + else if (svn_skel__matches_atom(skel->children, "committed")) + *kind = transaction_kind_committed; + else if (svn_skel__matches_atom(skel->children, "dead")) + *kind = transaction_kind_dead; + else + return FALSE; + + if (skel->children->next->is_atom + && skel->children->next->next->is_atom + && (! skel->children->next->next->next->is_atom) + && (! skel->children->next->next->next->next->is_atom)) + return TRUE; + + return FALSE; +} + + +static svn_boolean_t +is_valid_rep_delta_chunk_skel(svn_skel_t *skel) +{ + int len; + svn_skel_t *window; + svn_skel_t *diff; + + /* check the delta skel. */ + if ((svn_skel__list_length(skel) != 2) + || (! skel->children->is_atom)) + return FALSE; + + /* check the window. */ + window = skel->children->next; + len = svn_skel__list_length(window); + if ((len < 3) || (len > 4)) + return FALSE; + if (! ((! window->children->is_atom) + && (window->children->next->is_atom) + && (window->children->next->next->is_atom))) + return FALSE; + if ((len == 4) + && (! window->children->next->next->next->is_atom)) + return FALSE; + + /* check the diff. ### currently we support only svndiff version + 0 delta data. */ + diff = window->children; + if ((svn_skel__list_length(diff) == 3) + && (svn_skel__matches_atom(diff->children, "svndiff")) + && ((svn_skel__matches_atom(diff->children->next, "0")) + || (svn_skel__matches_atom(diff->children->next, "1"))) + && (diff->children->next->next->is_atom)) + return TRUE; + + return FALSE; +} + + +static svn_boolean_t +is_valid_representation_skel(svn_skel_t *skel) +{ + int len = svn_skel__list_length(skel); + svn_skel_t *header; + int header_len; + + /* the rep has at least two items in it, a HEADER list, and at least + one piece of kind-specific data. */ + if (len < 2) + return FALSE; + + /* check the header. it must have KIND and TXN atoms, and + optionally 1 or 2 checksums (which is a list form). */ + header = skel->children; + header_len = svn_skel__list_length(header); + if (! (((header_len == 2) /* 2 means old repository, checksum absent */ + && (header->children->is_atom) + && (header->children->next->is_atom)) + || ((header_len == 3) /* 3 means md5 checksum present */ + && (header->children->is_atom) + && (header->children->next->is_atom) + && (is_valid_checksum_skel(header->children->next->next))) + || ((header_len == 4) /* 3 means md5 and sha1 checksums present */ + && (header->children->is_atom) + && (header->children->next->is_atom) + && (is_valid_checksum_skel(header->children->next->next)) + && (is_valid_checksum_skel(header->children->next->next->next))))) + return FALSE; + + /* check for fulltext rep. */ + if ((len == 2) + && (svn_skel__matches_atom(header->children, "fulltext"))) + return TRUE; + + /* check for delta rep. */ + if ((len >= 2) + && (svn_skel__matches_atom(header->children, "delta"))) + { + /* it's a delta rep. check the validity. */ + svn_skel_t *chunk = skel->children->next; + + /* loop over chunks, checking each one. */ + while (chunk) + { + if (! is_valid_rep_delta_chunk_skel(chunk)) + return FALSE; + chunk = chunk->next; + } + + /* all good on this delta rep. */ + return TRUE; + } + + return FALSE; +} + + +static svn_boolean_t +is_valid_node_revision_header_skel(svn_skel_t *skel, svn_skel_t **kind_p) +{ + int len = svn_skel__list_length(skel); + + if (len < 2) + return FALSE; + + /* set the *KIND_P pointer. */ + *kind_p = skel->children; + + /* check for valid lengths. */ + if (! ((len == 2) || (len == 3) || (len == 4) || (len == 6))) + return FALSE; + + /* got mergeinfo stuff? */ + if ((len > 4) + && (! (skel->children->next->next->next->next->is_atom + && skel->children->next->next->next->next->next->is_atom))) + return FALSE; + + /* got predecessor count? */ + if ((len > 3) + && (! skel->children->next->next->next->is_atom)) + return FALSE; + + /* got predecessor? */ + if ((len > 2) + && (! skel->children->next->next->is_atom)) + return FALSE; + + /* got the basics? */ + if (! (skel->children->is_atom + && skel->children->next->is_atom + && (skel->children->next->data[0] == '/'))) + return FALSE; + + return TRUE; +} + + +static svn_boolean_t +is_valid_node_revision_skel(svn_skel_t *skel) +{ + int len = svn_skel__list_length(skel); + svn_skel_t *header = skel->children; + svn_skel_t *kind; + + if (len < 1) + return FALSE; + + if (! is_valid_node_revision_header_skel(header, &kind)) + return FALSE; + + if (svn_skel__matches_atom(kind, "dir")) + { + if (! ((len == 3) + && header->next->is_atom + && header->next->next->is_atom)) + return FALSE; + } + else if (svn_skel__matches_atom(kind, "file")) + { + if (len < 3) + return FALSE; + + if (! header->next->is_atom) + return FALSE; + + /* As of SVN_FS_BASE__MIN_REP_SHARING_FORMAT version, the + DATA-KEY slot can be a 2-tuple. */ + if (! header->next->next->is_atom) + { + if (! ((svn_skel__list_length(header->next->next) == 2) + && header->next->next->children->is_atom + && header->next->next->children->len + && header->next->next->children->next->is_atom + && header->next->next->children->next->len)) + return FALSE; + } + + if ((len > 3) && (! header->next->next->next->is_atom)) + return FALSE; + + if (len > 4) + return FALSE; + } + + return TRUE; +} + + +static svn_boolean_t +is_valid_copy_skel(svn_skel_t *skel) +{ + return ((svn_skel__list_length(skel) == 4) + && (svn_skel__matches_atom(skel->children, "copy") + || svn_skel__matches_atom(skel->children, "soft-copy")) + && skel->children->next->is_atom + && skel->children->next->next->is_atom + && skel->children->next->next->next->is_atom); +} + + +static svn_boolean_t +is_valid_change_skel(svn_skel_t *skel, svn_fs_path_change_kind_t *kind) +{ + if ((svn_skel__list_length(skel) == 6) + && svn_skel__matches_atom(skel->children, "change") + && skel->children->next->is_atom + && skel->children->next->next->is_atom + && skel->children->next->next->next->is_atom + && skel->children->next->next->next->next->is_atom + && skel->children->next->next->next->next->next->is_atom) + { + svn_skel_t *kind_skel = skel->children->next->next->next; + + /* check the kind (and return it) */ + if (svn_skel__matches_atom(kind_skel, "reset")) + { + if (kind) + *kind = svn_fs_path_change_reset; + return TRUE; + } + if (svn_skel__matches_atom(kind_skel, "add")) + { + if (kind) + *kind = svn_fs_path_change_add; + return TRUE; + } + if (svn_skel__matches_atom(kind_skel, "delete")) + { + if (kind) + *kind = svn_fs_path_change_delete; + return TRUE; + } + if (svn_skel__matches_atom(kind_skel, "replace")) + { + if (kind) + *kind = svn_fs_path_change_replace; + return TRUE; + } + if (svn_skel__matches_atom(kind_skel, "modify")) + { + if (kind) + *kind = svn_fs_path_change_modify; + return TRUE; + } + } + return FALSE; +} + + +static svn_boolean_t +is_valid_lock_skel(svn_skel_t *skel) +{ + if ((svn_skel__list_length(skel) == 8) + && svn_skel__matches_atom(skel->children, "lock") + && skel->children->next->is_atom + && skel->children->next->next->is_atom + && skel->children->next->next->next->is_atom + && skel->children->next->next->next->next->is_atom + && skel->children->next->next->next->next->next->is_atom + && skel->children->next->next->next->next->next->next->is_atom + && skel->children->next->next->next->next->next->next->next->is_atom) + return TRUE; + + return FALSE; +} + + + +/*** Parsing (conversion from skeleton to native FS type) ***/ + +svn_error_t * +svn_fs_base__parse_revision_skel(revision_t **revision_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + revision_t *revision; + + /* Validate the skel. */ + if (! is_valid_revision_skel(skel)) + return skel_err("revision"); + + /* Create the returned structure */ + revision = apr_pcalloc(pool, sizeof(*revision)); + revision->txn_id = apr_pstrmemdup(pool, skel->children->next->data, + skel->children->next->len); + + /* Return the structure. */ + *revision_p = revision; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_transaction_skel(transaction_t **transaction_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + transaction_t *transaction; + transaction_kind_t kind; + svn_skel_t *root_id, *base_id_or_rev, *proplist, *copies; + int len; + + /* Validate the skel. */ + if (! is_valid_transaction_skel(skel, &kind)) + return skel_err("transaction"); + + root_id = skel->children->next; + base_id_or_rev = skel->children->next->next; + proplist = skel->children->next->next->next; + copies = skel->children->next->next->next->next; + + /* Create the returned structure */ + transaction = apr_pcalloc(pool, sizeof(*transaction)); + + /* KIND */ + transaction->kind = kind; + + /* REVISION or BASE-ID */ + if (kind == transaction_kind_committed) + { + /* Committed transactions have a revision number... */ + transaction->base_id = NULL; + transaction->revision = + SVN_STR_TO_REV(apr_pstrmemdup(pool, base_id_or_rev->data, + base_id_or_rev->len)); + if (! SVN_IS_VALID_REVNUM(transaction->revision)) + return skel_err("transaction"); + + } + else + { + /* ...where unfinished transactions have a base node-revision-id. */ + transaction->revision = SVN_INVALID_REVNUM; + transaction->base_id = svn_fs_base__id_parse(base_id_or_rev->data, + base_id_or_rev->len, pool); + } + + /* ROOT-ID */ + transaction->root_id = svn_fs_base__id_parse(root_id->data, + root_id->len, pool); + + /* PROPLIST */ + SVN_ERR(svn_skel__parse_proplist(&(transaction->proplist), + proplist, pool)); + + /* COPIES */ + if ((len = svn_skel__list_length(copies))) + { + const char *copy_id; + apr_array_header_t *txncopies; + svn_skel_t *cpy = copies->children; + + txncopies = apr_array_make(pool, len, sizeof(copy_id)); + while (cpy) + { + copy_id = apr_pstrmemdup(pool, cpy->data, cpy->len); + APR_ARRAY_PUSH(txncopies, const char *) = copy_id; + cpy = cpy->next; + } + transaction->copies = txncopies; + } + + /* Return the structure. */ + *transaction_p = transaction; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_representation_skel(representation_t **rep_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + representation_t *rep; + svn_skel_t *header_skel; + + /* Validate the skel. */ + if (! is_valid_representation_skel(skel)) + return skel_err("representation"); + header_skel = skel->children; + + /* Create the returned structure */ + rep = apr_pcalloc(pool, sizeof(*rep)); + + /* KIND */ + if (svn_skel__matches_atom(header_skel->children, "fulltext")) + rep->kind = rep_kind_fulltext; + else + rep->kind = rep_kind_delta; + + /* TXN */ + rep->txn_id = apr_pstrmemdup(pool, header_skel->children->next->data, + header_skel->children->next->len); + + /* MD5 */ + if (header_skel->children->next->next) + { + svn_skel_t *checksum_skel = header_skel->children->next->next; + rep->md5_checksum = + svn_checksum__from_digest_md5((const unsigned char *) + (checksum_skel->children->next->data), + pool); + + /* SHA1 */ + if (header_skel->children->next->next->next) + { + checksum_skel = header_skel->children->next->next->next; + rep->sha1_checksum = + svn_checksum__from_digest_sha1( + (const unsigned char *)(checksum_skel->children->next->data), + pool); + } + } + + /* KIND-SPECIFIC stuff */ + if (rep->kind == rep_kind_fulltext) + { + /* "fulltext"-specific. */ + rep->contents.fulltext.string_key + = apr_pstrmemdup(pool, + skel->children->next->data, + skel->children->next->len); + } + else + { + /* "delta"-specific. */ + svn_skel_t *chunk_skel = skel->children->next; + rep_delta_chunk_t *chunk; + apr_array_header_t *chunks; + + /* Alloc the chunk array. */ + chunks = apr_array_make(pool, svn_skel__list_length(skel) - 1, + sizeof(chunk)); + + /* Process the chunks. */ + while (chunk_skel) + { + svn_skel_t *window_skel = chunk_skel->children->next; + svn_skel_t *diff_skel = window_skel->children; + apr_int64_t val; + apr_uint64_t uval; + const char *str; + + /* Allocate a chunk and its window */ + chunk = apr_palloc(pool, sizeof(*chunk)); + + /* Populate the window */ + str = apr_pstrmemdup(pool, diff_skel->children->next->data, + diff_skel->children->next->len); + SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, 255, 10)); + chunk->version = (apr_byte_t)uval; + + chunk->string_key + = apr_pstrmemdup(pool, + diff_skel->children->next->next->data, + diff_skel->children->next->next->len); + + str = apr_pstrmemdup(pool, window_skel->children->next->data, + window_skel->children->next->len); + SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, APR_SIZE_MAX, 10)); + chunk->size = (apr_size_t)uval; + + chunk->rep_key + = apr_pstrmemdup(pool, + window_skel->children->next->next->data, + window_skel->children->next->next->len); + + str = apr_pstrmemdup(pool, chunk_skel->children->data, + chunk_skel->children->len); + SVN_ERR(svn_cstring_strtoi64(&val, str, 0, APR_INT64_MAX, 10)); + chunk->offset = (svn_filesize_t)val; + + /* Add this chunk to the array. */ + APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk; + + /* Next... */ + chunk_skel = chunk_skel->next; + } + + /* Add the chunks array to the representation. */ + rep->contents.delta.chunks = chunks; + } + + /* Return the structure. */ + *rep_p = rep; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + node_revision_t *noderev; + svn_skel_t *header_skel, *cur_skel; + + /* Validate the skel. */ + if (! is_valid_node_revision_skel(skel)) + return skel_err("node-revision"); + header_skel = skel->children; + + /* Create the returned structure */ + noderev = apr_pcalloc(pool, sizeof(*noderev)); + + /* KIND */ + if (svn_skel__matches_atom(header_skel->children, "dir")) + noderev->kind = svn_node_dir; + else + noderev->kind = svn_node_file; + + /* CREATED-PATH */ + noderev->created_path = apr_pstrmemdup(pool, + header_skel->children->next->data, + header_skel->children->next->len); + + /* PREDECESSOR-ID */ + if (header_skel->children->next->next) + { + cur_skel = header_skel->children->next->next; + if (cur_skel->len) + noderev->predecessor_id = svn_fs_base__id_parse(cur_skel->data, + cur_skel->len, pool); + + /* PREDECESSOR-COUNT */ + noderev->predecessor_count = -1; + if (cur_skel->next) + { + const char *str; + + cur_skel = cur_skel->next; + if (cur_skel->len) + { + str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len); + SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, str)); + } + + /* HAS-MERGEINFO and MERGEINFO-COUNT */ + if (cur_skel->next) + { + int val; + + cur_skel = cur_skel->next; + str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len); + SVN_ERR(svn_cstring_atoi(&val, str)); + noderev->has_mergeinfo = (val != 0); + + str = apr_pstrmemdup(pool, cur_skel->next->data, + cur_skel->next->len); + SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, str)); + } + } + } + + /* PROP-KEY */ + if (skel->children->next->len) + noderev->prop_key = apr_pstrmemdup(pool, skel->children->next->data, + skel->children->next->len); + + /* DATA-KEY */ + if (skel->children->next->next->is_atom) + { + /* This is a real data rep key. */ + if (skel->children->next->next->len) + noderev->data_key = apr_pstrmemdup(pool, + skel->children->next->next->data, + skel->children->next->next->len); + noderev->data_key_uniquifier = NULL; + } + else + { + /* This is a 2-tuple with a data rep key and a uniquifier. */ + noderev->data_key = + apr_pstrmemdup(pool, + skel->children->next->next->children->data, + skel->children->next->next->children->len); + noderev->data_key_uniquifier = + apr_pstrmemdup(pool, + skel->children->next->next->children->next->data, + skel->children->next->next->children->next->len); + } + + /* EDIT-DATA-KEY (optional, files only) */ + if ((noderev->kind == svn_node_file) + && skel->children->next->next->next + && skel->children->next->next->next->len) + noderev->edit_key + = apr_pstrmemdup(pool, skel->children->next->next->next->data, + skel->children->next->next->next->len); + + /* Return the structure. */ + *noderev_p = noderev; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_copy_skel(copy_t **copy_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + copy_t *copy; + + /* Validate the skel. */ + if (! is_valid_copy_skel(skel)) + return skel_err("copy"); + + /* Create the returned structure */ + copy = apr_pcalloc(pool, sizeof(*copy)); + + /* KIND */ + if (svn_skel__matches_atom(skel->children, "soft-copy")) + copy->kind = copy_kind_soft; + else + copy->kind = copy_kind_real; + + /* SRC-PATH */ + copy->src_path = apr_pstrmemdup(pool, + skel->children->next->data, + skel->children->next->len); + + /* SRC-TXN-ID */ + copy->src_txn_id = apr_pstrmemdup(pool, + skel->children->next->next->data, + skel->children->next->next->len); + + /* DST-NODE-ID */ + copy->dst_noderev_id + = svn_fs_base__id_parse(skel->children->next->next->next->data, + skel->children->next->next->next->len, pool); + + /* Return the structure. */ + *copy_p = copy; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_entries_skel(apr_hash_t **entries_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + apr_hash_t *entries = NULL; + int len = svn_skel__list_length(skel); + svn_skel_t *elt; + + if (! (len >= 0)) + return skel_err("entries"); + + if (len > 0) + { + /* Else, allocate a hash and populate it. */ + entries = apr_hash_make(pool); + + /* Check entries are well-formed as we go along. */ + for (elt = skel->children; elt; elt = elt->next) + { + const char *name; + svn_fs_id_t *id; + + /* ENTRY must be a list of two elements. */ + if (svn_skel__list_length(elt) != 2) + return skel_err("entries"); + + /* Get the entry's name and ID. */ + name = apr_pstrmemdup(pool, elt->children->data, + elt->children->len); + id = svn_fs_base__id_parse(elt->children->next->data, + elt->children->next->len, pool); + + /* Add the entry to the hash. */ + apr_hash_set(entries, name, elt->children->len, id); + } + } + + /* Return the structure. */ + *entries_p = entries; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_change_skel(change_t **change_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + change_t *change; + svn_fs_path_change_kind_t kind; + + /* Validate the skel. */ + if (! is_valid_change_skel(skel, &kind)) + return skel_err("change"); + + /* Create the returned structure */ + change = apr_pcalloc(pool, sizeof(*change)); + + /* PATH */ + change->path = apr_pstrmemdup(pool, skel->children->next->data, + skel->children->next->len); + + /* NODE-REV-ID */ + if (skel->children->next->next->len) + change->noderev_id = svn_fs_base__id_parse + (skel->children->next->next->data, skel->children->next->next->len, + pool); + + /* KIND */ + change->kind = kind; + + /* TEXT-MOD */ + if (skel->children->next->next->next->next->len) + change->text_mod = TRUE; + + /* PROP-MOD */ + if (skel->children->next->next->next->next->next->len) + change->prop_mod = TRUE; + + /* Return the structure. */ + *change_p = change; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__parse_lock_skel(svn_lock_t **lock_p, + svn_skel_t *skel, + apr_pool_t *pool) +{ + svn_lock_t *lock; + const char *timestr; + + /* Validate the skel. */ + if (! is_valid_lock_skel(skel)) + return skel_err("lock"); + + /* Create the returned structure */ + lock = apr_pcalloc(pool, sizeof(*lock)); + + /* PATH */ + lock->path = apr_pstrmemdup(pool, skel->children->next->data, + skel->children->next->len); + + /* LOCK-TOKEN */ + lock->token = apr_pstrmemdup(pool, + skel->children->next->next->data, + skel->children->next->next->len); + + /* OWNER */ + lock->owner = apr_pstrmemdup(pool, + skel->children->next->next->next->data, + skel->children->next->next->next->len); + + /* COMMENT (could be just an empty atom) */ + if (skel->children->next->next->next->next->len) + lock->comment = + apr_pstrmemdup(pool, + skel->children->next->next->next->next->data, + skel->children->next->next->next->next->len); + + /* XML_P */ + if (svn_skel__matches_atom + (skel->children->next->next->next->next->next, "1")) + lock->is_dav_comment = TRUE; + else + lock->is_dav_comment = FALSE; + + /* CREATION-DATE */ + timestr = apr_pstrmemdup + (pool, + skel->children->next->next->next->next->next->next->data, + skel->children->next->next->next->next->next->next->len); + SVN_ERR(svn_time_from_cstring(&(lock->creation_date), + timestr, pool)); + + /* EXPIRATION-DATE (could be just an empty atom) */ + if (skel->children->next->next->next->next->next->next->next->len) + { + timestr = + apr_pstrmemdup + (pool, + skel->children->next->next->next->next->next->next->next->data, + skel->children->next->next->next->next->next->next->next->len); + SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), + timestr, pool)); + } + + /* Return the structure. */ + *lock_p = lock; + return SVN_NO_ERROR; +} + + + +/*** Unparsing (conversion from native FS type to skeleton) ***/ + +svn_error_t * +svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p, + const revision_t *revision, + apr_pool_t *pool) +{ + svn_skel_t *skel; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + + /* TXN_ID */ + svn_skel__prepend(svn_skel__str_atom(revision->txn_id, pool), skel); + + /* "revision" */ + svn_skel__prepend(svn_skel__str_atom("revision", pool), skel); + + /* Validate and return the skel. */ + if (! is_valid_revision_skel(skel)) + return skel_err("revision"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p, + const transaction_t *transaction, + apr_pool_t *pool) +{ + svn_skel_t *skel; + svn_skel_t *proplist_skel, *copies_skel, *header_skel; + svn_string_t *id_str; + transaction_kind_t kind; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + + switch (transaction->kind) + { + case transaction_kind_committed: + header_skel = svn_skel__str_atom("committed", pool); + if ((transaction->base_id) + || (! SVN_IS_VALID_REVNUM(transaction->revision))) + return skel_err("transaction"); + break; + case transaction_kind_dead: + header_skel = svn_skel__str_atom("dead", pool); + if ((! transaction->base_id) + || (SVN_IS_VALID_REVNUM(transaction->revision))) + return skel_err("transaction"); + break; + case transaction_kind_normal: + header_skel = svn_skel__str_atom("transaction", pool); + if ((! transaction->base_id) + || (SVN_IS_VALID_REVNUM(transaction->revision))) + return skel_err("transaction"); + break; + default: + return skel_err("transaction"); + } + + + /* COPIES */ + copies_skel = svn_skel__make_empty_list(pool); + if (transaction->copies && transaction->copies->nelts) + { + int i; + for (i = transaction->copies->nelts - 1; i >= 0; i--) + { + svn_skel__prepend(svn_skel__str_atom( + APR_ARRAY_IDX(transaction->copies, i, + const char *), + pool), + copies_skel); + } + } + svn_skel__prepend(copies_skel, skel); + + /* PROPLIST */ + SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, + transaction->proplist, pool)); + svn_skel__prepend(proplist_skel, skel); + + /* REVISION or BASE-ID */ + if (transaction->kind == transaction_kind_committed) + { + /* Committed transactions have a revision number... */ + svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld", + transaction->revision), + pool), skel); + } + else + { + /* ...where other transactions have a base node revision ID. */ + id_str = svn_fs_base__id_unparse(transaction->base_id, pool); + svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool), + skel); + } + + /* ROOT-ID */ + id_str = svn_fs_base__id_unparse(transaction->root_id, pool); + svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool), skel); + + /* KIND (see above) */ + svn_skel__prepend(header_skel, skel); + + /* Validate and return the skel. */ + if (! is_valid_transaction_skel(skel, &kind)) + return skel_err("transaction"); + if (kind != transaction->kind) + return skel_err("transaction"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +/* Construct a skel representing CHECKSUM, allocated in POOL, and prepend + * it onto the existing skel SKEL. */ +static svn_error_t * +prepend_checksum(svn_skel_t *skel, + svn_checksum_t *checksum, + apr_pool_t *pool) +{ + svn_skel_t *checksum_skel = svn_skel__make_empty_list(pool); + + switch (checksum->kind) + { + case svn_checksum_md5: + svn_skel__prepend(svn_skel__mem_atom(checksum->digest, + APR_MD5_DIGESTSIZE, pool), + checksum_skel); + svn_skel__prepend(svn_skel__str_atom("md5", pool), checksum_skel); + break; + + case svn_checksum_sha1: + svn_skel__prepend(svn_skel__mem_atom(checksum->digest, + APR_SHA1_DIGESTSIZE, pool), + checksum_skel); + svn_skel__prepend(svn_skel__str_atom("sha1", pool), checksum_skel); + break; + + default: + return skel_err("checksum"); + } + svn_skel__prepend(checksum_skel, skel); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p, + const representation_t *rep, + int format, + apr_pool_t *pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(pool); + svn_skel_t *header_skel = svn_skel__make_empty_list(pool); + + /** Some parts of the header are common to all representations; do + those parts first. **/ + + /* SHA1 */ + if ((format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) && rep->sha1_checksum) + SVN_ERR(prepend_checksum(header_skel, rep->sha1_checksum, pool)); + + /* MD5 */ + { + svn_checksum_t *md5_checksum = rep->md5_checksum; + if (! md5_checksum) + md5_checksum = svn_checksum_create(svn_checksum_md5, pool); + SVN_ERR(prepend_checksum(header_skel, md5_checksum, pool)); + } + + /* TXN */ + if (rep->txn_id) + svn_skel__prepend(svn_skel__str_atom(rep->txn_id, pool), header_skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel); + + /** Do the kind-specific stuff. **/ + + if (rep->kind == rep_kind_fulltext) + { + /*** Fulltext Representation. ***/ + + /* STRING-KEY */ + if ((! rep->contents.fulltext.string_key) + || (! *rep->contents.fulltext.string_key)) + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + else + svn_skel__prepend(svn_skel__str_atom(rep->contents.fulltext.string_key, + pool), skel); + + /* "fulltext" */ + svn_skel__prepend(svn_skel__str_atom("fulltext", pool), header_skel); + + /* header */ + svn_skel__prepend(header_skel, skel); + } + else if (rep->kind == rep_kind_delta) + { + /*** Delta Representation. ***/ + int i; + apr_array_header_t *chunks = rep->contents.delta.chunks; + + /* Loop backwards through the windows, creating and prepending skels. */ + for (i = chunks->nelts; i > 0; i--) + { + svn_skel_t *window_skel = svn_skel__make_empty_list(pool); + svn_skel_t *chunk_skel = svn_skel__make_empty_list(pool); + svn_skel_t *diff_skel = svn_skel__make_empty_list(pool); + const char *size_str, *offset_str, *version_str; + rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i - 1, + rep_delta_chunk_t *); + + /* OFFSET */ + offset_str = apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, + chunk->offset); + + /* SIZE */ + size_str = apr_psprintf(pool, "%" APR_SIZE_T_FMT, chunk->size); + + /* VERSION */ + version_str = apr_psprintf(pool, "%d", chunk->version); + + /* DIFF */ + if ((! chunk->string_key) || (! *chunk->string_key)) + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), diff_skel); + else + svn_skel__prepend(svn_skel__str_atom(chunk->string_key, pool), + diff_skel); + svn_skel__prepend(svn_skel__str_atom(version_str, pool), diff_skel); + svn_skel__prepend(svn_skel__str_atom("svndiff", pool), diff_skel); + + /* REP-KEY */ + if ((! chunk->rep_key) || (! *(chunk->rep_key))) + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), + window_skel); + else + svn_skel__prepend(svn_skel__str_atom(chunk->rep_key, pool), + window_skel); + svn_skel__prepend(svn_skel__str_atom(size_str, pool), window_skel); + svn_skel__prepend(diff_skel, window_skel); + + /* window header. */ + svn_skel__prepend(window_skel, chunk_skel); + svn_skel__prepend(svn_skel__str_atom(offset_str, pool), + chunk_skel); + + /* Add this window item to the main skel. */ + svn_skel__prepend(chunk_skel, skel); + } + + /* "delta" */ + svn_skel__prepend(svn_skel__str_atom("delta", pool), header_skel); + + /* header */ + svn_skel__prepend(header_skel, skel); + } + else /* unknown kind */ + SVN_ERR_MALFUNCTION(); + + /* Validate and return the skel. */ + if (! is_valid_representation_skel(skel)) + return skel_err("representation"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p, + const node_revision_t *noderev, + int format, + apr_pool_t *pool) +{ + svn_skel_t *skel; + svn_skel_t *header_skel; + const char *num_str; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + header_skel = svn_skel__make_empty_list(pool); + + /* Store mergeinfo stuffs only if the schema level supports it. */ + if (format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT) + { + /* MERGEINFO-COUNT */ + num_str = apr_psprintf(pool, "%" APR_INT64_T_FMT, + noderev->mergeinfo_count); + svn_skel__prepend(svn_skel__str_atom(num_str, pool), header_skel); + + /* HAS-MERGEINFO */ + svn_skel__prepend(svn_skel__mem_atom(noderev->has_mergeinfo ? "1" : "0", + 1, pool), header_skel); + + /* PREDECESSOR-COUNT padding (only if we *don't* have a valid + value; if we do, we'll pick that up below) */ + if (noderev->predecessor_count == -1) + { + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel); + } + } + + /* PREDECESSOR-COUNT */ + if (noderev->predecessor_count != -1) + { + const char *count_str = apr_psprintf(pool, "%d", + noderev->predecessor_count); + svn_skel__prepend(svn_skel__str_atom(count_str, pool), header_skel); + } + + /* PREDECESSOR-ID */ + if (noderev->predecessor_id) + { + svn_string_t *id_str = svn_fs_base__id_unparse(noderev->predecessor_id, + pool); + svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool), + header_skel); + } + else + { + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel); + } + + /* CREATED-PATH */ + svn_skel__prepend(svn_skel__str_atom(noderev->created_path, pool), + header_skel); + + /* KIND */ + if (noderev->kind == svn_node_file) + svn_skel__prepend(svn_skel__str_atom("file", pool), header_skel); + else if (noderev->kind == svn_node_dir) + svn_skel__prepend(svn_skel__str_atom("dir", pool), header_skel); + else + SVN_ERR_MALFUNCTION(); + + /* ### do we really need to check *node->FOO_key ? if a key doesn't + ### exist, then the field should be NULL ... */ + + /* EDIT-DATA-KEY (optional) */ + if ((noderev->edit_key) && (*noderev->edit_key)) + svn_skel__prepend(svn_skel__str_atom(noderev->edit_key, pool), skel); + + /* DATA-KEY | (DATA-KEY DATA-KEY-UNIQID) */ + if ((noderev->data_key_uniquifier) && (*noderev->data_key_uniquifier)) + { + /* Build a 2-tuple with a rep key and uniquifier. */ + svn_skel_t *data_key_skel = svn_skel__make_empty_list(pool); + + /* DATA-KEY-UNIQID */ + svn_skel__prepend(svn_skel__str_atom(noderev->data_key_uniquifier, + pool), + data_key_skel); + + /* DATA-KEY */ + if ((noderev->data_key) && (*noderev->data_key)) + svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool), + data_key_skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), data_key_skel); + + /* Add our 2-tuple to the main skel. */ + svn_skel__prepend(data_key_skel, skel); + } + else + { + /* Just store the rep key (or empty placeholder) in the main skel. */ + if ((noderev->data_key) && (*noderev->data_key)) + svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + } + + /* PROP-KEY */ + if ((noderev->prop_key) && (*noderev->prop_key)) + svn_skel__prepend(svn_skel__str_atom(noderev->prop_key, pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* HEADER */ + svn_skel__prepend(header_skel, skel); + + /* Validate and return the skel. */ + if (! is_valid_node_revision_skel(skel)) + return skel_err("node-revision"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p, + const copy_t *copy, + apr_pool_t *pool) +{ + svn_skel_t *skel; + svn_string_t *tmp_str; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + + /* DST-NODE-ID */ + tmp_str = svn_fs_base__id_unparse(copy->dst_noderev_id, pool); + svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool), + skel); + + /* SRC-TXN-ID */ + if ((copy->src_txn_id) && (*copy->src_txn_id)) + svn_skel__prepend(svn_skel__str_atom(copy->src_txn_id, pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* SRC-PATH */ + if ((copy->src_path) && (*copy->src_path)) + svn_skel__prepend(svn_skel__str_atom(copy->src_path, pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* "copy" */ + if (copy->kind == copy_kind_real) + svn_skel__prepend(svn_skel__str_atom("copy", pool), skel); + else + svn_skel__prepend(svn_skel__str_atom("soft-copy", pool), skel); + + /* Validate and return the skel. */ + if (! is_valid_copy_skel(skel)) + return skel_err("copy"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p, + apr_hash_t *entries, + apr_pool_t *pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(pool); + apr_hash_index_t *hi; + + /* Create the skel. */ + if (entries) + { + /* Loop over hash entries */ + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + apr_ssize_t klen; + svn_fs_id_t *value; + svn_string_t *id_str; + svn_skel_t *entry_skel = svn_skel__make_empty_list(pool); + + apr_hash_this(hi, &key, &klen, &val); + value = val; + + /* VALUE */ + id_str = svn_fs_base__id_unparse(value, pool); + svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, + pool), + entry_skel); + + /* NAME */ + svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), entry_skel); + + /* Add entry to the entries skel. */ + svn_skel__prepend(entry_skel, skel); + } + } + + /* Return the skel. */ + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_change_skel(svn_skel_t **skel_p, + const change_t *change, + apr_pool_t *pool) +{ + svn_skel_t *skel; + svn_string_t *tmp_str; + svn_fs_path_change_kind_t kind; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + + /* PROP-MOD */ + if (change->prop_mod) + svn_skel__prepend(svn_skel__str_atom("1", pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* TEXT-MOD */ + if (change->text_mod) + svn_skel__prepend(svn_skel__str_atom("1", pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* KIND */ + switch (change->kind) + { + case svn_fs_path_change_reset: + svn_skel__prepend(svn_skel__str_atom("reset", pool), skel); + break; + case svn_fs_path_change_add: + svn_skel__prepend(svn_skel__str_atom("add", pool), skel); + break; + case svn_fs_path_change_delete: + svn_skel__prepend(svn_skel__str_atom("delete", pool), skel); + break; + case svn_fs_path_change_replace: + svn_skel__prepend(svn_skel__str_atom("replace", pool), skel); + break; + case svn_fs_path_change_modify: + default: + svn_skel__prepend(svn_skel__str_atom("modify", pool), skel); + break; + } + + /* NODE-REV-ID */ + if (change->noderev_id) + { + tmp_str = svn_fs_base__id_unparse(change->noderev_id, pool); + svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool), + skel); + } + else + { + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + } + + /* PATH */ + svn_skel__prepend(svn_skel__str_atom(change->path, pool), skel); + + /* "change" */ + svn_skel__prepend(svn_skel__str_atom("change", pool), skel); + + /* Validate and return the skel. */ + if (! is_valid_change_skel(skel, &kind)) + return skel_err("change"); + if (kind != change->kind) + return skel_err("change"); + *skel_p = skel; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p, + const svn_lock_t *lock, + apr_pool_t *pool) +{ + svn_skel_t *skel; + + /* Create the skel. */ + skel = svn_skel__make_empty_list(pool); + + /* EXP-DATE is optional. If not present, just use an empty atom. */ + if (lock->expiration_date) + svn_skel__prepend(svn_skel__str_atom( + svn_time_to_cstring(lock->expiration_date, pool), + pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* CREATION-DATE */ + svn_skel__prepend(svn_skel__str_atom( + svn_time_to_cstring(lock->creation_date, pool), + pool), skel); + + /* XML_P */ + if (lock->is_dav_comment) + svn_skel__prepend(svn_skel__str_atom("1", pool), skel); + else + svn_skel__prepend(svn_skel__str_atom("0", pool), skel); + + /* COMMENT */ + if (lock->comment) + svn_skel__prepend(svn_skel__str_atom(lock->comment, pool), skel); + else + svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel); + + /* OWNER */ + svn_skel__prepend(svn_skel__str_atom(lock->owner, pool), skel); + + /* LOCK-TOKEN */ + svn_skel__prepend(svn_skel__str_atom(lock->token, pool), skel); + + /* PATH */ + svn_skel__prepend(svn_skel__str_atom(lock->path, pool), skel); + + /* "lock" */ + svn_skel__prepend(svn_skel__str_atom("lock", pool), skel); + + /* Validate and return the skel. */ + if (! is_valid_lock_skel(skel)) + return skel_err("lock"); + + *skel_p = skel; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_base/util/fs_skels.h b/subversion/libsvn_fs_base/util/fs_skels.h new file mode 100644 index 0000000..63dab80 --- /dev/null +++ b/subversion/libsvn_fs_base/util/fs_skels.h @@ -0,0 +1,177 @@ +/* fs_skels.h : headers for conversion between fs native types and + * skeletons + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_FS_SKELS_H +#define SVN_LIBSVN_FS_FS_SKELS_H + +#define SVN_WANT_BDB +#include "svn_private_config.h" + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_fs.h" +#include "../fs.h" +#include "private/svn_skel.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** Parsing (conversion from skeleton to native FS type) ***/ + + +/* Parse a `REVISION' SKEL and set *REVISION_P to the newly allocated + result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_revision_skel(revision_t **revision_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `TRANSACTION' SKEL and set *TRANSACTION_P to the newly allocated + result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_transaction_skel(transaction_t **transaction_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `REPRESENTATION' SKEL and set *REP_P to the newly allocated + result. Use POOL for all allocations. */ + +svn_error_t * +svn_fs_base__parse_representation_skel(representation_t **rep_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `NODE-REVISION' SKEL and set *NODEREV_P to the newly allocated + result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `COPY' SKEL and set *COPY_P to the newly allocated result. Use + POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_copy_skel(copy_t **copy_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse an `ENTRIES' SKEL and set *ENTRIES_P to a new hash with const + char * names (the directory entry name) and svn_fs_id_t * values + (the node-id of the entry), or NULL if SKEL contains no entries. + Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_entries_skel(apr_hash_t **entries_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `CHANGE' SKEL and set *CHANGE_P to the newly allocated result. + Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_change_skel(change_t **change_p, + svn_skel_t *skel, + apr_pool_t *pool); + +/* Parse a `LOCK' SKEL and set *LOCK_P to the newly allocated result. Use + POOL for all allocations. */ +svn_error_t * +svn_fs_base__parse_lock_skel(svn_lock_t **lock_p, + svn_skel_t *skel, + apr_pool_t *pool); + + + +/*** Unparsing (conversion from native FS type to skeleton) ***/ + + +/* Unparse REVISION into a newly allocated `REVISION' skel and set *SKEL_P + to the result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p, + const revision_t *revision, + apr_pool_t *pool); + +/* Unparse TRANSACTION into a newly allocated `TRANSACTION' skel and set + *SKEL_P to the result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p, + const transaction_t *transaction, + apr_pool_t *pool); + +/* Unparse REP into a newly allocated `REPRESENTATION' skel and set *SKEL_P + to the result. Use POOL for all allocations. FORMAT is the format + version of the filesystem. */ +svn_error_t * +svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p, + const representation_t *rep, + int format, + apr_pool_t *pool); + +/* Unparse NODEREV into a newly allocated `NODE-REVISION' skel and set + *SKEL_P to the result. Use POOL for all allocations. FORMAT is the + format version of the filesystem. */ +svn_error_t * +svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p, + const node_revision_t *noderev, + int format, + apr_pool_t *pool); + +/* Unparse COPY into a newly allocated `COPY' skel and set *SKEL_P to the + result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p, + const copy_t *copy, + apr_pool_t *pool); + +/* Unparse an ENTRIES hash, which has const char * names (the entry + name) and svn_fs_id_t * values (the node-id of the entry) into a newly + allocated `ENTRIES' skel and set *SKEL_P to the result. Use POOL for all + allocations. */ +svn_error_t * +svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p, + apr_hash_t *entries, + apr_pool_t *pool); + +/* Unparse CHANGE into a newly allocated `CHANGE' skel and set *SKEL_P to + the result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__unparse_change_skel(svn_skel_t **skel_p, + const change_t *change, + apr_pool_t *pool); + +/* Unparse LOCK into a newly allocated `LOCK' skel and set *SKEL_P to the + result. Use POOL for all allocations. */ +svn_error_t * +svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p, + const svn_lock_t *lock, + apr_pool_t *pool); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_FS_SKELS_H */ diff --git a/subversion/libsvn_fs_base/uuid.c b/subversion/libsvn_fs_base/uuid.c new file mode 100644 index 0000000..c865df3 --- /dev/null +++ b/subversion/libsvn_fs_base/uuid.c @@ -0,0 +1,116 @@ +/* uuid.c : operations on repository uuids + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_pools.h" +#include "fs.h" +#include "trail.h" +#include "err.h" +#include "uuid.h" +#include "bdb/uuids-table.h" +#include "../libsvn_fs/fs-loader.h" + +#include "private/svn_fs_util.h" + + +struct get_uuid_args +{ + int idx; + const char **uuid; +}; + + +static svn_error_t * +txn_body_get_uuid(void *baton, trail_t *trail) +{ + struct get_uuid_args *args = baton; + return svn_fs_bdb__get_uuid(trail->fs, args->idx, args->uuid, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__populate_uuid(svn_fs_t *fs, + apr_pool_t *scratch_pool) +{ + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + /* We hit the database. */ + { + const char *uuid; + struct get_uuid_args args; + + args.idx = 1; + args.uuid = &uuid; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_uuid, &args, + FALSE, scratch_pool)); + + if (uuid) + { + /* Toss what we find into the cache. */ + fs->uuid = apr_pstrdup(fs->pool, uuid); + } + } + + return SVN_NO_ERROR; +} + + +struct set_uuid_args +{ + int idx; + const char *uuid; +}; + + +static svn_error_t * +txn_body_set_uuid(void *baton, trail_t *trail) +{ + struct set_uuid_args *args = baton; + return svn_fs_bdb__set_uuid(trail->fs, args->idx, args->uuid, + trail, trail->pool); +} + + +svn_error_t * +svn_fs_base__set_uuid(svn_fs_t *fs, + const char *uuid, + apr_pool_t *pool) +{ + struct set_uuid_args args; + + SVN_ERR(svn_fs__check_fs(fs, TRUE)); + + if (! uuid) + uuid = svn_uuid_generate(pool); + + args.idx = 1; + args.uuid = uuid; + SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_set_uuid, &args, TRUE, pool)); + + /* Toss our value into the cache. */ + if (uuid) + fs->uuid = apr_pstrdup(fs->pool, uuid); + + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_fs_base/uuid.h b/subversion/libsvn_fs_base/uuid.h new file mode 100644 index 0000000..453a390 --- /dev/null +++ b/subversion/libsvn_fs_base/uuid.h @@ -0,0 +1,49 @@ +/* uuid.h : internal interface to uuid functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_FS_UUID_H +#define SVN_LIBSVN_FS_UUID_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* Set FS->UUID to the value read from the database, allocated + in FS->POOL. Use SCRATCH_POOL for temporary allocations. */ +svn_error_t *svn_fs_base__populate_uuid(svn_fs_t *fs, + apr_pool_t *scratch_pool); + + +/* These functions implement some of the calls in the FS loader + library's fs vtable. */ + +svn_error_t *svn_fs_base__set_uuid(svn_fs_t *fs, const char *uuid, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_FS_UUID_H */ |