diff options
author | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
commit | e20a507113ff1126aeb4a97b806390ea377fe292 (patch) | |
tree | 0260b3a40387d7f994fbadaf22f1e9d3c080b09f /subversion/libsvn_fs/fs-loader.c | |
parent | c64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff) |
New upstream version 1.10.2
Diffstat (limited to 'subversion/libsvn_fs/fs-loader.c')
-rw-r--r-- | subversion/libsvn_fs/fs-loader.c | 2223 |
1 files changed, 2223 insertions, 0 deletions
diff --git a/subversion/libsvn_fs/fs-loader.c b/subversion/libsvn_fs/fs-loader.c new file mode 100644 index 0000000..f898d8a --- /dev/null +++ b/subversion/libsvn_fs/fs-loader.c @@ -0,0 +1,2223 @@ +/* + * fs_loader.c: Front-end to the various FS back ends + * + * ==================================================================== + * 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.h> +#include <apr_atomic.h> +#include <apr_hash.h> +#include <apr_uuid.h> +#include <apr_strings.h> + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_ctype.h" +#include "svn_types.h" +#include "svn_dso.h" +#include "svn_version.h" +#include "svn_fs.h" +#include "svn_path.h" +#include "svn_xml.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_sorts.h" + +#include "private/svn_atomic.h" +#include "private/svn_fs_private.h" +#include "private/svn_fs_util.h" +#include "private/svn_fspath.h" +#include "private/svn_utf_private.h" +#include "private/svn_mutex.h" +#include "private/svn_subr_private.h" + +#include "fs-loader.h" + +/* This is defined by configure on platforms which use configure, but + we need to define a fallback for Windows. */ +#ifndef DEFAULT_FS_TYPE +#define DEFAULT_FS_TYPE "fsfs" +#endif + +#define FS_TYPE_FILENAME "fs-type" + +/* If a FS backend does not implement the PATHS_CHANGED vtable function, + it will get emulated. However, if this macro is defined to non-null + then the API will always be emulated when feasible, i.e. the calls + get "re-directed" to the new API implementation. */ +#ifndef SVN_FS_EMULATE_PATHS_CHANGED +#define SVN_FS_EMULATE_PATHS_CHANGED FALSE +#endif + +/* If a FS backend does not implement the REPORT_CHANGES vtable function, + it will get emulated. However, if this macro is defined to non-null + then the API will always be emulated when feasible, i.e. the calls + get "re-directed" to the old API implementation. */ +#ifndef SVN_FS_EMULATE_REPORT_CHANGES +#define SVN_FS_EMULATE_REPORT_CHANGES FALSE +#endif + +/* A pool common to all FS objects. See the documentation on the + open/create functions in fs-loader.h and for svn_fs_initialize(). */ +static apr_pool_t *common_pool = NULL; +static svn_mutex__t *common_pool_lock = NULL; +static svn_atomic_t common_pool_initialized = FALSE; + + +/* --- Utility functions for the loader --- */ + +struct fs_type_defn { + const char *fs_type; + const char *fsap_name; + fs_init_func_t initfunc; + void * volatile vtable; /* fs_library_vtable_t */ + struct fs_type_defn *next; +}; + +static struct fs_type_defn base_defn = + { + SVN_FS_TYPE_BDB, "base", +#ifdef SVN_LIBSVN_FS_LINKS_FS_BASE + svn_fs_base__init, +#else + NULL, +#endif + NULL, + NULL /* End of static list: this needs to be reset to NULL if the + common_pool used when setting it has been cleared. */ + }; + +static struct fs_type_defn fsx_defn = + { + SVN_FS_TYPE_FSX, "x", +#ifdef SVN_LIBSVN_FS_LINKS_FS_X + svn_fs_x__init, +#else + NULL, +#endif + NULL, + &base_defn + }; + +static struct fs_type_defn fsfs_defn = + { + SVN_FS_TYPE_FSFS, "fs", +#ifdef SVN_LIBSVN_FS_LINKS_FS_FS + svn_fs_fs__init, +#else + NULL, +#endif + NULL, + &fsx_defn + }; + +static struct fs_type_defn *fs_modules = &fsfs_defn; + + +static svn_error_t * +load_module(fs_init_func_t *initfunc, const char *name, apr_pool_t *pool) +{ + *initfunc = NULL; + +#if defined(SVN_USE_DSO) && APR_HAS_DSO + { + apr_dso_handle_t *dso; + apr_dso_handle_sym_t symbol; + const char *libname; + const char *funcname; + apr_status_t status; + const char *p; + + /* Demand a simple alphanumeric name so that the generated DSO + name is sensible. */ + for (p = name; *p; ++p) + if (!svn_ctype_isalnum(*p)) + return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL, + _("Invalid name for FS type '%s'"), + name); + + libname = apr_psprintf(pool, "libsvn_fs_%s-" SVN_DSO_SUFFIX_FMT, + name, SVN_VER_MAJOR, SVN_SOVERSION); + funcname = apr_psprintf(pool, "svn_fs_%s__init", name); + + /* Find/load the specified library. If we get an error, assume + the library doesn't exist. The library will be unloaded when + pool is destroyed. */ + SVN_ERR(svn_dso_load(&dso, libname)); + if (! dso) + return SVN_NO_ERROR; + + /* find the initialization routine */ + status = apr_dso_sym(&symbol, dso, funcname); + if (status) + return svn_error_wrap_apr(status, _("'%s' does not define '%s()'"), + libname, funcname); + + *initfunc = (fs_init_func_t) symbol; + } +#endif /* APR_HAS_DSO */ + + return SVN_NO_ERROR; +} + +/* Fetch a library vtable by a pointer into the library definitions array. */ +static svn_error_t * +get_library_vtable_direct(fs_library_vtable_t **vtable, + struct fs_type_defn *fst, + apr_pool_t *pool) +{ + fs_init_func_t initfunc = NULL; + const svn_version_t *my_version = svn_fs_version(); + const svn_version_t *fs_version; + + /* most times, we get lucky */ + *vtable = svn_atomic_casptr(&fst->vtable, NULL, NULL); + if (*vtable) + return SVN_NO_ERROR; + + /* o.k. the first access needs to actually load the module, find the + vtable and check for version compatibility. */ + initfunc = fst->initfunc; + if (! initfunc) + SVN_ERR(load_module(&initfunc, fst->fsap_name, pool)); + + if (! initfunc) + return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL, + _("Failed to load module for FS type '%s'"), + fst->fs_type); + + { + /* Per our API compatibility rules, we cannot ensure that + svn_fs_initialize is called by the application. If not, we + cannot create the common pool and lock in a thread-safe fashion, + nor can we clean up the common pool if libsvn_fs is dynamically + unloaded. This function makes a best effort by creating the + common pool as a child of the global pool; the window of failure + due to thread collision is small. */ + SVN_ERR(svn_fs_initialize(NULL)); + + /* Invoke the FS module's initfunc function with the common + pool protected by a lock. */ + SVN_MUTEX__WITH_LOCK(common_pool_lock, + initfunc(my_version, vtable, common_pool)); + } + fs_version = (*vtable)->get_version(); + if (!svn_ver_equal(my_version, fs_version)) + return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, + _("Mismatched FS module version for '%s':" + " found %d.%d.%d%s," + " expected %d.%d.%d%s"), + fst->fs_type, + my_version->major, my_version->minor, + my_version->patch, my_version->tag, + fs_version->major, fs_version->minor, + fs_version->patch, fs_version->tag); + + /* the vtable will not change. Remember it */ + svn_atomic_casptr(&fst->vtable, *vtable, NULL); + + return SVN_NO_ERROR; +} + +#if defined(SVN_USE_DSO) && APR_HAS_DSO +/* Return *FST for the third party FS_TYPE */ +static svn_error_t * +get_or_allocate_third(struct fs_type_defn **fst, + const char *fs_type) +{ + while (*fst) + { + if (strcmp(fs_type, (*fst)->fs_type) == 0) + return SVN_NO_ERROR; + fst = &(*fst)->next; + } + + *fst = apr_palloc(common_pool, sizeof(struct fs_type_defn)); + (*fst)->fs_type = apr_pstrdup(common_pool, fs_type); + (*fst)->fsap_name = (*fst)->fs_type; + (*fst)->initfunc = NULL; + (*fst)->vtable = NULL; + (*fst)->next = NULL; + + return SVN_NO_ERROR; +} +#endif + +/* Fetch a library *VTABLE by FS_TYPE. + Use POOL for temporary allocations. */ +static svn_error_t * +get_library_vtable(fs_library_vtable_t **vtable, const char *fs_type, + apr_pool_t *pool) +{ + struct fs_type_defn **fst; + svn_boolean_t known = FALSE; + + /* There are three FS module definitions known at compile time. We + want to check these without any locking overhead even when + dynamic third party modules are enabled. The third party modules + cannot be checked until the lock is held. */ + for (fst = &fs_modules; *fst; fst = &(*fst)->next) + { + if (strcmp(fs_type, (*fst)->fs_type) == 0) + { + known = TRUE; + break; + } + else if (!(*fst)->next) + { + break; + } + } + +#if defined(SVN_USE_DSO) && APR_HAS_DSO + /* Third party FS modules that are unknown at compile time. + + A third party FS is identified by the file fs-type containing a + third party name, say "foo". The loader will load the DSO with + the name "libsvn_fs_foo" and use the entry point with the name + "svn_fs_foo__init". + + Note: the BDB and FSFS modules don't follow this naming scheme + and this allows them to be used to test the third party loader. + Change the content of fs-type to "base" in a BDB filesystem or to + "fs" in an FSFS filesystem and they will be loaded as third party + modules. */ + if (!known) + { + fst = &(*fst)->next; + /* Best-effort init, see get_library_vtable_direct. */ + SVN_ERR(svn_fs_initialize(NULL)); + SVN_MUTEX__WITH_LOCK(common_pool_lock, + get_or_allocate_third(fst, fs_type)); + known = TRUE; + } +#endif + if (!known) + return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL, + _("Unknown FS type '%s'"), fs_type); + return get_library_vtable_direct(vtable, *fst, pool); +} + +svn_error_t * +svn_fs_type(const char **fs_type, const char *path, apr_pool_t *pool) +{ + const char *filename; + char buf[128]; + svn_error_t *err; + apr_file_t *file; + apr_size_t len; + + /* Read the fsap-name file to get the FSAP name, or assume the (old) + default. For old repositories I suppose we could check some + other file, DB_CONFIG or strings say, but for now just check the + directory exists. */ + filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool); + err = svn_io_file_open(&file, filename, APR_READ|APR_BUFFERED, 0, pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_node_kind_t kind; + svn_error_t *err2 = svn_io_check_path(path, &kind, pool); + if (err2) + { + svn_error_clear(err2); + return err; + } + if (kind == svn_node_dir) + { + svn_error_clear(err); + *fs_type = SVN_FS_TYPE_BDB; + return SVN_NO_ERROR; + } + return err; + } + else if (err) + return err; + + len = sizeof(buf); + SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); + SVN_ERR(svn_io_file_close(file, pool)); + *fs_type = apr_pstrdup(pool, buf); + + return SVN_NO_ERROR; +} + +/* Fetch the library vtable for an existing FS. */ +static svn_error_t * +fs_library_vtable(fs_library_vtable_t **vtable, const char *path, + apr_pool_t *pool) +{ + const char *fs_type; + + SVN_ERR(svn_fs_type(&fs_type, path, pool)); + + /* Fetch the library vtable by name, now that we've chosen one. */ + SVN_ERR(get_library_vtable(vtable, fs_type, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +write_fs_type(const char *path, const char *fs_type, apr_pool_t *pool) +{ + const char *filename; + apr_file_t *file; + + filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool); + SVN_ERR(svn_io_file_open(&file, filename, + APR_WRITE|APR_CREATE|APR_TRUNCATE|APR_BUFFERED, + APR_OS_DEFAULT, pool)); + SVN_ERR(svn_io_file_write_full(file, fs_type, strlen(fs_type), NULL, + pool)); + SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool)); + return svn_error_trace(svn_io_file_close(file, pool)); +} + + +/* --- Functions for operating on filesystems by pathname --- */ + +static apr_status_t uninit(void *data) +{ + common_pool = NULL; + common_pool_lock = NULL; + common_pool_initialized = 0; + + return APR_SUCCESS; +} + +static svn_error_t * +synchronized_initialize(void *baton, apr_pool_t *pool) +{ + common_pool = svn_pool_create(pool); + base_defn.next = NULL; + SVN_ERR(svn_mutex__init(&common_pool_lock, TRUE, common_pool)); + + /* ### This won't work if POOL is NULL and libsvn_fs is loaded as a DSO + ### (via libsvn_ra_local say) since the global common_pool will live + ### longer than the DSO, which gets unloaded when the pool used to + ### load it is cleared, and so when the handler runs it will refer to + ### a function that no longer exists. libsvn_ra_local attempts to + ### work around this by explicitly calling svn_fs_initialize. */ + apr_pool_cleanup_register(common_pool, NULL, uninit, apr_pool_cleanup_null); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_initialize(apr_pool_t *pool) +{ +#if defined(SVN_USE_DSO) && APR_HAS_DSO + /* Ensure that DSO subsystem is initialized early as possible if + we're going to use it. */ + SVN_ERR(svn_dso_initialize2()); +#endif + /* Protect against multiple calls. */ + return svn_error_trace(svn_atomic__init_once(&common_pool_initialized, + synchronized_initialize, + NULL, pool)); +} + +/* A default warning handling function. */ +static void +default_warning_func(void *baton, svn_error_t *err) +{ + /* The one unforgiveable sin is to fail silently. Dumping to stderr + or /dev/tty is not acceptable default behavior for server + processes, since those may both be equivalent to /dev/null. + + That said, be a good citizen and print something anyway, in case it goes + somewhere, and our caller hasn't overridden the abort() call. + */ + if (svn_error_get_malfunction_handler() + == svn_error_abort_on_malfunction) + /* ### TODO: extend the malfunction API such that non-abort()ing consumers + ### also get the information on ERR. */ + svn_handle_error2(err, stderr, FALSE /* fatal */, "svn: fs-loader: "); + SVN_ERR_MALFUNCTION_NO_RETURN(); +} + +svn_error_t * +svn_fs__path_valid(const char *path, apr_pool_t *pool) +{ + char *c; + + /* UTF-8 encoded string without NULs. */ + if (! svn_utf__cstring_is_valid(path)) + { + return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Path '%s' is not in UTF-8"), path); + } + + /* No "." or ".." elements. */ + if (svn_path_is_backpath_present(path) + || svn_path_is_dotpath_present(path)) + { + return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Path '%s' contains '.' or '..' element"), + path); + } + + /* Raise an error if PATH contains a newline because svn:mergeinfo and + friends can't handle them. Issue #4340 describes a similar problem + in the FSFS code itself. + */ + c = strchr(path, '\n'); + if (c) + { + return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Invalid control character '0x%02x' in path '%s'"), + (unsigned char)*c, svn_path_illegal_path_escape(path, pool)); + } + + /* That's good enough. */ + return SVN_NO_ERROR; +} + +/* Allocate svn_fs_t structure. */ +static svn_fs_t * +fs_new(apr_hash_t *fs_config, apr_pool_t *pool) +{ + svn_fs_t *fs = apr_palloc(pool, sizeof(*fs)); + fs->pool = pool; + fs->path = NULL; + fs->warning = default_warning_func; + fs->warning_baton = NULL; + fs->config = fs_config; + fs->access_ctx = NULL; + fs->vtable = NULL; + fs->fsap_data = NULL; + fs->uuid = NULL; + return fs; +} + +svn_fs_t * +svn_fs_new(apr_hash_t *fs_config, apr_pool_t *pool) +{ + return fs_new(fs_config, pool); +} + +void +svn_fs_set_warning_func(svn_fs_t *fs, svn_fs_warning_callback_t warning, + void *warning_baton) +{ + fs->warning = warning; + fs->warning_baton = warning_baton; +} + +svn_error_t * +svn_fs_create2(svn_fs_t **fs_p, + const char *path, + apr_hash_t *fs_config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + fs_library_vtable_t *vtable; + + const char *fs_type = svn_hash__get_cstring(fs_config, + SVN_FS_CONFIG_FS_TYPE, + DEFAULT_FS_TYPE); + SVN_ERR(get_library_vtable(&vtable, fs_type, scratch_pool)); + + /* Create the FS directory and write out the fsap-name file. */ + SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(write_fs_type(path, fs_type, scratch_pool)); + + /* Perform the actual creation. */ + *fs_p = fs_new(fs_config, result_pool); + + SVN_ERR(vtable->create(*fs_p, path, common_pool_lock, scratch_pool, + common_pool)); + SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open2)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_open2(svn_fs_t **fs_p, const char *path, apr_hash_t *fs_config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + fs_library_vtable_t *vtable; + + SVN_ERR(fs_library_vtable(&vtable, path, scratch_pool)); + *fs_p = fs_new(fs_config, result_pool); + SVN_ERR(vtable->open_fs(*fs_p, path, common_pool_lock, scratch_pool, + common_pool)); + SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open2)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_upgrade2(const char *path, + svn_fs_upgrade_notify_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + fs_library_vtable_t *vtable; + svn_fs_t *fs; + + SVN_ERR(fs_library_vtable(&vtable, path, scratch_pool)); + fs = fs_new(NULL, scratch_pool); + + SVN_ERR(vtable->upgrade_fs(fs, path, + notify_func, notify_baton, + cancel_func, cancel_baton, + common_pool_lock, + scratch_pool, common_pool)); + return SVN_NO_ERROR; +} + +/* A warning handling function that does not abort on errors, + but just lets them be returned normally. */ +static void +verify_fs_warning_func(void *baton, svn_error_t *err) +{ +} + +svn_error_t * +svn_fs_verify(const char *path, + apr_hash_t *fs_config, + svn_revnum_t start, + svn_revnum_t end, + svn_fs_progress_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + svn_fs_t *fs; + + SVN_ERR(fs_library_vtable(&vtable, path, pool)); + fs = fs_new(fs_config, pool); + svn_fs_set_warning_func(fs, verify_fs_warning_func, NULL); + + SVN_ERR(vtable->verify_fs(fs, path, start, end, + notify_func, notify_baton, + cancel_func, cancel_baton, + common_pool_lock, + pool, common_pool)); + return SVN_NO_ERROR; +} + +const char * +svn_fs_path(svn_fs_t *fs, apr_pool_t *pool) +{ + return apr_pstrdup(pool, fs->path); +} + +apr_hash_t * +svn_fs_config(svn_fs_t *fs, apr_pool_t *pool) +{ + if (fs->config) + return apr_hash_copy(pool, fs->config); + + return NULL; +} + +svn_error_t * +svn_fs_delete_fs(const char *path, apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + + SVN_ERR(fs_library_vtable(&vtable, path, pool)); + return svn_error_trace(vtable->delete_fs(path, pool)); +} + +svn_error_t * +svn_fs_hotcopy3(const char *src_path, const char *dst_path, + svn_boolean_t clean, svn_boolean_t incremental, + svn_fs_hotcopy_notify_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + fs_library_vtable_t *vtable; + const char *src_fs_type; + svn_fs_t *src_fs; + svn_fs_t *dst_fs; + const char *dst_fs_type; + svn_node_kind_t dst_kind; + + if (strcmp(src_path, dst_path) == 0) + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Hotcopy source and destination are equal")); + + SVN_ERR(svn_fs_type(&src_fs_type, src_path, scratch_pool)); + SVN_ERR(get_library_vtable(&vtable, src_fs_type, scratch_pool)); + src_fs = fs_new(NULL, scratch_pool); + dst_fs = fs_new(NULL, scratch_pool); + + SVN_ERR(svn_io_check_path(dst_path, &dst_kind, scratch_pool)); + if (dst_kind == svn_node_file) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("'%s' already exists and is a file"), + svn_dirent_local_style(dst_path, + scratch_pool)); + if (dst_kind == svn_node_unknown) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("'%s' already exists and has an unknown " + "node kind"), + svn_dirent_local_style(dst_path, + scratch_pool)); + if (dst_kind == svn_node_dir) + { + svn_node_kind_t type_file_kind; + + SVN_ERR(svn_io_check_path(svn_dirent_join(dst_path, + FS_TYPE_FILENAME, + scratch_pool), + &type_file_kind, scratch_pool)); + if (type_file_kind != svn_node_none) + { + SVN_ERR(svn_fs_type(&dst_fs_type, dst_path, scratch_pool)); + if (strcmp(src_fs_type, dst_fs_type) != 0) + return svn_error_createf( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("The filesystem type of the hotcopy source " + "('%s') does not match the filesystem " + "type of the hotcopy destination ('%s')"), + src_fs_type, dst_fs_type); + } + } + + SVN_ERR(vtable->hotcopy(src_fs, dst_fs, src_path, dst_path, clean, + incremental, notify_func, notify_baton, + cancel_func, cancel_baton, common_pool_lock, + scratch_pool, common_pool)); + return svn_error_trace(write_fs_type(dst_path, src_fs_type, scratch_pool)); +} + +svn_error_t * +svn_fs_pack(const char *path, + svn_fs_pack_notify_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + svn_fs_t *fs; + + SVN_ERR(fs_library_vtable(&vtable, path, pool)); + fs = fs_new(NULL, pool); + + SVN_ERR(vtable->pack_fs(fs, path, notify_func, notify_baton, + cancel_func, cancel_baton, common_pool_lock, + pool, common_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_recover(const char *path, + svn_cancel_func_t cancel_func, void *cancel_baton, + apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + svn_fs_t *fs; + + SVN_ERR(fs_library_vtable(&vtable, path, pool)); + fs = fs_new(NULL, pool); + + SVN_ERR(vtable->open_fs_for_recovery(fs, path, common_pool_lock, + pool, common_pool)); + return svn_error_trace(vtable->recover(fs, cancel_func, cancel_baton, + pool)); +} + +svn_error_t * +svn_fs_verify_root(svn_fs_root_t *root, + apr_pool_t *scratch_pool) +{ + svn_fs_t *fs = root->fs; + SVN_ERR(fs->vtable->verify_root(root, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_freeze(svn_fs_t *fs, + svn_fs_freeze_func_t freeze_func, + void *freeze_baton, + apr_pool_t *pool) +{ + SVN_ERR(fs->vtable->freeze(fs, freeze_func, freeze_baton, pool)); + + return SVN_NO_ERROR; +} + + +/* --- Berkeley-specific functions --- */ + +svn_error_t * +svn_fs_create_berkeley(svn_fs_t *fs, const char *path) +{ + fs_library_vtable_t *vtable; + + SVN_ERR(get_library_vtable(&vtable, SVN_FS_TYPE_BDB, fs->pool)); + + /* Create the FS directory and write out the fsap-name file. */ + SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, fs->pool)); + SVN_ERR(write_fs_type(path, SVN_FS_TYPE_BDB, fs->pool)); + + /* Perform the actual creation. */ + SVN_ERR(vtable->create(fs, path, common_pool_lock, fs->pool, common_pool)); + SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open2)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_open_berkeley(svn_fs_t *fs, const char *path) +{ + fs_library_vtable_t *vtable; + + SVN_ERR(fs_library_vtable(&vtable, path, fs->pool)); + SVN_ERR(vtable->open_fs(fs, path, common_pool_lock, fs->pool, common_pool)); + SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open2)); + + return SVN_NO_ERROR; +} + +const char * +svn_fs_berkeley_path(svn_fs_t *fs, apr_pool_t *pool) +{ + return svn_fs_path(fs, pool); +} + +svn_error_t * +svn_fs_delete_berkeley(const char *path, apr_pool_t *pool) +{ + return svn_error_trace(svn_fs_delete_fs(path, pool)); +} + +svn_error_t * +svn_fs_hotcopy_berkeley(const char *src_path, const char *dest_path, + svn_boolean_t clean_logs, apr_pool_t *pool) +{ + return svn_error_trace(svn_fs_hotcopy3(src_path, dest_path, clean_logs, + FALSE, NULL, NULL, NULL, NULL, + pool)); +} + +svn_error_t * +svn_fs_berkeley_recover(const char *path, apr_pool_t *pool) +{ + return svn_error_trace(svn_fs_recover(path, NULL, NULL, pool)); +} + +svn_error_t * +svn_fs_set_berkeley_errcall(svn_fs_t *fs, + void (*handler)(const char *errpfx, char *msg)) +{ + return svn_error_trace(fs->vtable->bdb_set_errcall(fs, handler)); +} + +svn_error_t * +svn_fs_berkeley_logfiles(apr_array_header_t **logfiles, + const char *path, + svn_boolean_t only_unused, + apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + + SVN_ERR(fs_library_vtable(&vtable, path, pool)); + return svn_error_trace(vtable->bdb_logfiles(logfiles, path, only_unused, + pool)); +} + + +/* --- Transaction functions --- */ + +svn_error_t * +svn_fs_begin_txn2(svn_fs_txn_t **txn_p, svn_fs_t *fs, svn_revnum_t rev, + apr_uint32_t flags, apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->begin_txn(txn_p, fs, rev, flags, pool)); +} + + +svn_error_t * +svn_fs_commit_txn(const char **conflict_p, svn_revnum_t *new_rev, + svn_fs_txn_t *txn, apr_pool_t *pool) +{ + svn_error_t *err; + + *new_rev = SVN_INVALID_REVNUM; + if (conflict_p) + *conflict_p = NULL; + + err = txn->vtable->commit(conflict_p, new_rev, txn, pool); + +#ifdef SVN_DEBUG + /* Check postconditions. */ + if (conflict_p) + { + SVN_ERR_ASSERT_E(! (SVN_IS_VALID_REVNUM(*new_rev) && *conflict_p != NULL), + err); + SVN_ERR_ASSERT_E((*conflict_p != NULL) + == (err && err->apr_err == SVN_ERR_FS_CONFLICT), + err); + } +#endif + + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool) +{ + return svn_error_trace(txn->vtable->abort(txn, pool)); +} + +svn_error_t * +svn_fs_purge_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->purge_txn(fs, txn_id, pool)); +} + +svn_error_t * +svn_fs_txn_name(const char **name_p, svn_fs_txn_t *txn, apr_pool_t *pool) +{ + *name_p = apr_pstrdup(pool, txn->id); + return SVN_NO_ERROR; +} + +svn_revnum_t +svn_fs_txn_base_revision(svn_fs_txn_t *txn) +{ + return txn->base_rev; +} + +svn_error_t * +svn_fs_open_txn(svn_fs_txn_t **txn, svn_fs_t *fs, const char *name, + apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->open_txn(txn, fs, name, pool)); +} + +svn_error_t * +svn_fs_list_transactions(apr_array_header_t **names_p, svn_fs_t *fs, + apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->list_transactions(names_p, fs, pool)); +} + +static svn_boolean_t +is_internal_txn_prop(const char *name) +{ + return strcmp(name, SVN_FS__PROP_TXN_CHECK_LOCKS) == 0 || + strcmp(name, SVN_FS__PROP_TXN_CHECK_OOD) == 0 || + strcmp(name, SVN_FS__PROP_TXN_CLIENT_DATE) == 0; +} + +svn_error_t * +svn_fs_txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn, + const char *propname, apr_pool_t *pool) +{ + if (is_internal_txn_prop(propname)) + { + *value_p = NULL; + return SVN_NO_ERROR; + } + + return svn_error_trace(txn->vtable->get_prop(value_p, txn, propname, pool)); +} + +svn_error_t * +svn_fs_txn_proplist(apr_hash_t **table_p, svn_fs_txn_t *txn, apr_pool_t *pool) +{ + SVN_ERR(txn->vtable->get_proplist(table_p, txn, pool)); + + /* Don't give away internal transaction properties. */ + svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL); + svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CHECK_OOD, NULL); + svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CLIENT_DATE, NULL); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_change_txn_prop(svn_fs_txn_t *txn, const char *name, + const svn_string_t *value, apr_pool_t *pool) +{ + if (is_internal_txn_prop(name)) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Attempt to modify internal transaction " + "property '%s'"), name); + + return svn_error_trace(txn->vtable->change_prop(txn, name, value, pool)); +} + +svn_error_t * +svn_fs_change_txn_props(svn_fs_txn_t *txn, const apr_array_header_t *props, + apr_pool_t *pool) +{ + int i; + + for (i = 0; i < props->nelts; ++i) + { + svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); + + if (is_internal_txn_prop(prop->name)) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Attempt to modify internal transaction " + "property '%s'"), prop->name); + } + + return svn_error_trace(txn->vtable->change_props(txn, props, pool)); +} + + +/* --- Root functions --- */ + +svn_error_t * +svn_fs_revision_root(svn_fs_root_t **root_p, svn_fs_t *fs, svn_revnum_t rev, + apr_pool_t *pool) +{ + /* We create a subpool for each root object to allow us to implement + svn_fs_close_root. */ + apr_pool_t *subpool = svn_pool_create(pool); + return svn_error_trace(fs->vtable->revision_root(root_p, fs, rev, subpool)); +} + +svn_error_t * +svn_fs_txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn, apr_pool_t *pool) +{ + /* We create a subpool for each root object to allow us to implement + svn_fs_close_root. */ + apr_pool_t *subpool = svn_pool_create(pool); + return svn_error_trace(txn->vtable->root(root_p, txn, subpool)); +} + +void +svn_fs_close_root(svn_fs_root_t *root) +{ + svn_pool_destroy(root->pool); +} + +svn_fs_t * +svn_fs_root_fs(svn_fs_root_t *root) +{ + return root->fs; +} + +svn_boolean_t +svn_fs_is_txn_root(svn_fs_root_t *root) +{ + return root->is_txn_root; +} + +svn_boolean_t +svn_fs_is_revision_root(svn_fs_root_t *root) +{ + return !root->is_txn_root; +} + +const char * +svn_fs_txn_root_name(svn_fs_root_t *root, apr_pool_t *pool) +{ + return root->is_txn_root ? apr_pstrdup(pool, root->txn) : NULL; +} + +svn_revnum_t +svn_fs_txn_root_base_revision(svn_fs_root_t *root) +{ + return root->is_txn_root ? root->rev : SVN_INVALID_REVNUM; +} + +svn_revnum_t +svn_fs_revision_root_revision(svn_fs_root_t *root) +{ + return root->is_txn_root ? SVN_INVALID_REVNUM : root->rev; +} + +svn_error_t * +svn_fs_path_change_get(svn_fs_path_change3_t **change, + svn_fs_path_change_iterator_t *iterator) +{ + return iterator->vtable->get(change, iterator); +} + +svn_error_t * +svn_fs_paths_changed2(apr_hash_t **changed_paths_p, + svn_fs_root_t *root, + apr_pool_t *pool) +{ + svn_boolean_t emulate = !root->vtable->paths_changed + || SVN_FS_EMULATE_PATHS_CHANGED; + + if (emulate) + { + apr_pool_t *scratch_pool = svn_pool_create(pool); + apr_hash_t *changes = svn_hash__make(pool); + + svn_fs_path_change_iterator_t *iterator; + svn_fs_path_change3_t *change; + + SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, + scratch_pool)); + + SVN_ERR(svn_fs_path_change_get(&change, iterator)); + while (change) + { + svn_fs_path_change2_t *copy; + const svn_fs_id_t *id_copy; + const char *change_path = change->path.data; + svn_fs_root_t *change_root = root; + + /* Copy CHANGE to old API struct. */ + if (change->change_kind == svn_fs_path_change_delete) + SVN_ERR(svn_fs__get_deleted_node(&change_root, &change_path, + change_root, change_path, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_fs_node_id(&id_copy, change_root, change_path, pool)); + + copy = svn_fs_path_change2_create(id_copy, change->change_kind, + pool); + copy->copyfrom_known = change->copyfrom_known; + if ( copy->copyfrom_known + && SVN_IS_VALID_REVNUM(change->copyfrom_rev)) + { + copy->copyfrom_rev = change->copyfrom_rev; + copy->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path); + } + copy->mergeinfo_mod = change->mergeinfo_mod; + copy->node_kind = change->node_kind; + copy->prop_mod = change->prop_mod; + copy->text_mod = change->text_mod; + + svn_hash_sets(changes, apr_pstrmemdup(pool, change->path.data, + change->path.len), copy); + + /* Next change. */ + SVN_ERR(svn_fs_path_change_get(&change, iterator)); + } + svn_pool_destroy(scratch_pool); + + *changed_paths_p = changes; + } + else + { + SVN_ERR(root->vtable->paths_changed(changed_paths_p, root, pool)); + } + + return SVN_NO_ERROR; +} + +/* Implement svn_fs_path_change_iterator_t on top of svn_fs_paths_changed2. */ + +/* The iterator builds upon a hash iterator, which in turn operates on the + full prefetched changes list. */ +typedef struct fsap_iterator_data_t +{ + apr_hash_index_t *hi; + + /* For efficicency such that we don't need to dynamically allocate + yet another copy of that data. */ + svn_fs_path_change3_t change; +} fsap_iterator_data_t; + +static svn_error_t * +changes_iterator_get(svn_fs_path_change3_t **change, + svn_fs_path_change_iterator_t *iterator) +{ + fsap_iterator_data_t *data = iterator->fsap_data; + + if (data->hi) + { + const char *path = apr_hash_this_key(data->hi); + svn_fs_path_change2_t *entry = apr_hash_this_val(data->hi); + + data->change.path.data = path; + data->change.path.len = apr_hash_this_key_len(data->hi); + data->change.change_kind = entry->change_kind; + data->change.node_kind = entry->node_kind; + data->change.text_mod = entry->text_mod; + data->change.prop_mod = entry->prop_mod; + data->change.mergeinfo_mod = entry->mergeinfo_mod; + data->change.copyfrom_known = entry->copyfrom_known; + data->change.copyfrom_rev = entry->copyfrom_rev; + data->change.copyfrom_path = entry->copyfrom_path; + + *change = &data->change; + data->hi = apr_hash_next(data->hi); + } + else + { + *change = NULL; + } + + return SVN_NO_ERROR; +} + +static changes_iterator_vtable_t iterator_vtable = +{ + changes_iterator_get +}; + +svn_error_t * +svn_fs_paths_changed3(svn_fs_path_change_iterator_t **iterator, + svn_fs_root_t *root, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t emulate = !root->vtable->report_changes + || ( SVN_FS_EMULATE_REPORT_CHANGES + && root->vtable->paths_changed); + + if (emulate) + { + svn_fs_path_change_iterator_t *result; + fsap_iterator_data_t *data; + + apr_hash_t *changes; + SVN_ERR(root->vtable->paths_changed(&changes, root, result_pool)); + + data = apr_pcalloc(result_pool, sizeof(*data)); + data->hi = apr_hash_first(result_pool, changes); + + result = apr_pcalloc(result_pool, sizeof(*result)); + result->fsap_data = data; + result->vtable = &iterator_vtable; + + *iterator = result; + } + else + { + SVN_ERR(root->vtable->report_changes(iterator, root, result_pool, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_check_path(svn_node_kind_t *kind_p, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->check_path(kind_p, root, path, pool)); +} + +svn_error_t * +svn_fs_node_history2(svn_fs_history_t **history_p, svn_fs_root_t *root, + const char *path, apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(root->vtable->node_history(history_p, root, path, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_is_dir(svn_boolean_t *is_dir, svn_fs_root_t *root, const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + + SVN_ERR(root->vtable->check_path(&kind, root, path, pool)); + *is_dir = (kind == svn_node_dir); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_is_file(svn_boolean_t *is_file, svn_fs_root_t *root, const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + + SVN_ERR(root->vtable->check_path(&kind, root, path, pool)); + *is_file = (kind == svn_node_file); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_node_id(const svn_fs_id_t **id_p, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_id(id_p, root, path, pool)); +} + +svn_error_t * +svn_fs_node_relation(svn_fs_node_relation_t *relation, + svn_fs_root_t *root_a, const char *path_a, + svn_fs_root_t *root_b, const char *path_b, + apr_pool_t *scratch_pool) +{ + /* Different repository types? */ + if (root_a->fs != root_b->fs) + { + *relation = svn_fs_node_unrelated; + return SVN_NO_ERROR; + } + + return svn_error_trace(root_a->vtable->node_relation(relation, + root_a, path_a, + root_b, path_b, + scratch_pool)); +} + +svn_error_t * +svn_fs_node_created_rev(svn_revnum_t *revision, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_created_rev(revision, root, path, + pool)); +} + +svn_error_t * +svn_fs_node_origin_rev(svn_revnum_t *revision, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_origin_rev(revision, root, path, + pool)); +} + +svn_error_t * +svn_fs_node_created_path(const char **created_path, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_created_path(created_path, root, + path, pool)); +} + +svn_error_t * +svn_fs_node_prop(svn_string_t **value_p, svn_fs_root_t *root, + const char *path, const char *propname, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_prop(value_p, root, path, + propname, pool)); +} + +svn_error_t * +svn_fs_node_proplist(apr_hash_t **table_p, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->node_proplist(table_p, root, path, + pool)); +} + +svn_error_t * +svn_fs_node_has_props(svn_boolean_t *has_props, + svn_fs_root_t *root, + const char *path, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(root->vtable->node_has_props(has_props, root, path, + scratch_pool)); +} + +svn_error_t * +svn_fs_change_node_prop(svn_fs_root_t *root, const char *path, + const char *name, const svn_string_t *value, + apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->change_node_prop(root, path, name, + value, pool)); +} + +svn_error_t * +svn_fs_props_different(svn_boolean_t *changed_p, svn_fs_root_t *root1, + const char *path1, svn_fs_root_t *root2, + const char *path2, apr_pool_t *scratch_pool) +{ + return svn_error_trace(root1->vtable->props_changed(changed_p, + root1, path1, + root2, path2, + TRUE, scratch_pool)); +} + +svn_error_t * +svn_fs_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) +{ + return svn_error_trace(root1->vtable->props_changed(changed_p, + root1, path1, + root2, path2, + FALSE, pool)); +} + +svn_error_t * +svn_fs_copied_from(svn_revnum_t *rev_p, const char **path_p, + svn_fs_root_t *root, const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->copied_from(rev_p, path_p, root, path, + pool)); +} + +svn_error_t * +svn_fs_closest_copy(svn_fs_root_t **root_p, const char **path_p, + svn_fs_root_t *root, const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->closest_copy(root_p, path_p, + root, path, pool)); +} + +svn_error_t * +svn_fs_get_mergeinfo3(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, + svn_fs_mergeinfo_receiver_t receiver, + void *baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(root->vtable->get_mergeinfo( + root, paths, inherit, include_descendants, adjust_inherited_mergeinfo, + receiver, baton, scratch_pool)); +} + +/* Baton type to be used with mergeinfo_receiver(). It provides some of + * the parameters passed to svn_fs__get_mergeinfo_for_path. */ +typedef struct mergeinfo_receiver_baton_t +{ + svn_mergeinfo_t *mergeinfo; + apr_pool_t *result_pool; +} mergeinfo_receiver_baton_t; + +static svn_error_t * +mergeinfo_receiver(const char *path, + svn_mergeinfo_t mergeinfo, + void *baton, + apr_pool_t *scratch_pool) +{ + mergeinfo_receiver_baton_t *b = baton; + *b->mergeinfo = svn_mergeinfo_dup(mergeinfo, b->result_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs__get_mergeinfo_for_path(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 *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *paths + = apr_array_make(scratch_pool, 1, sizeof(const char *)); + + mergeinfo_receiver_baton_t baton; + baton.mergeinfo = mergeinfo; + baton.result_pool = result_pool; + + APR_ARRAY_PUSH(paths, const char *) = path; + + *mergeinfo = NULL; + SVN_ERR(svn_fs_get_mergeinfo3(root, paths, + inherit, FALSE /*include_descendants*/, + adjust_inherited_mergeinfo, + mergeinfo_receiver, &baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_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) +{ + return svn_error_trace(target_root->vtable->merge(conflict_p, + source_root, source_path, + target_root, target_path, + ancestor_root, + ancestor_path, pool)); +} + +svn_error_t * +svn_fs_dir_entries(apr_hash_t **entries_p, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->dir_entries(entries_p, root, path, + pool)); +} + +svn_error_t * +svn_fs_dir_optimal_order(apr_array_header_t **ordered_p, + svn_fs_root_t *root, + apr_hash_t *entries, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(root->vtable->dir_optimal_order(ordered_p, root, + entries, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_make_dir(svn_fs_root_t *root, const char *path, apr_pool_t *pool) +{ + SVN_ERR(svn_fs__path_valid(path, pool)); + return svn_error_trace(root->vtable->make_dir(root, path, pool)); +} + +svn_error_t * +svn_fs_delete(svn_fs_root_t *root, const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->delete_node(root, path, pool)); +} + +svn_error_t * +svn_fs_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) +{ + SVN_ERR(svn_fs__path_valid(to_path, pool)); + return svn_error_trace(to_root->vtable->copy(from_root, from_path, + to_root, to_path, pool)); +} + +svn_error_t * +svn_fs_revision_link(svn_fs_root_t *from_root, svn_fs_root_t *to_root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(to_root->vtable->revision_link(from_root, to_root, + path, pool)); +} + +svn_error_t * +svn_fs_file_length(svn_filesize_t *length_p, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->file_length(length_p, root, path, + pool)); +} + +svn_error_t * +svn_fs_file_checksum(svn_checksum_t **checksum, + svn_checksum_kind_t kind, + svn_fs_root_t *root, + const char *path, + svn_boolean_t force, + apr_pool_t *pool) +{ + SVN_ERR(root->vtable->file_checksum(checksum, kind, root, path, pool)); + + if (force && (*checksum == NULL || (*checksum)->kind != kind)) + { + svn_stream_t *contents; + + SVN_ERR(svn_fs_file_contents(&contents, root, path, pool)); + SVN_ERR(svn_stream_contents_checksum(checksum, contents, kind, + pool, pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_file_contents(svn_stream_t **contents, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + return svn_error_trace(root->vtable->file_contents(contents, root, path, + pool)); +} + +svn_error_t * +svn_fs_try_process_file_contents(svn_boolean_t *success, + svn_fs_root_t *root, + const char *path, + svn_fs_process_contents_func_t processor, + void* baton, + apr_pool_t *pool) +{ + /* if the FS doesn't implement this function, report a "failed" attempt */ + if (root->vtable->try_process_file_contents == NULL) + { + *success = FALSE; + return SVN_NO_ERROR; + } + + return svn_error_trace(root->vtable->try_process_file_contents( + success, + root, path, + processor, baton, pool)); +} + +svn_error_t * +svn_fs_make_file(svn_fs_root_t *root, const char *path, apr_pool_t *pool) +{ + SVN_ERR(svn_fs__path_valid(path, pool)); + return svn_error_trace(root->vtable->make_file(root, path, pool)); +} + +svn_error_t * +svn_fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p, + void **contents_baton_p, svn_fs_root_t *root, + const char *path, const char *base_checksum, + const char *result_checksum, apr_pool_t *pool) +{ + svn_checksum_t *base, *result; + + /* TODO: If we ever rev this API, we should make the supplied checksums + svn_checksum_t structs. */ + SVN_ERR(svn_checksum_parse_hex(&base, svn_checksum_md5, base_checksum, + pool)); + SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum, + pool)); + + return svn_error_trace(root->vtable->apply_textdelta(contents_p, + contents_baton_p, + root, + path, + base, + result, + pool)); +} + +svn_error_t * +svn_fs_apply_text(svn_stream_t **contents_p, svn_fs_root_t *root, + const char *path, const char *result_checksum, + apr_pool_t *pool) +{ + svn_checksum_t *result; + + /* TODO: If we ever rev this API, we should make the supplied checksum an + svn_checksum_t struct. */ + SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum, + pool)); + + return svn_error_trace(root->vtable->apply_text(contents_p, root, path, + result, pool)); +} + +svn_error_t * +svn_fs_contents_different(svn_boolean_t *changed_p, svn_fs_root_t *root1, + const char *path1, svn_fs_root_t *root2, + const char *path2, apr_pool_t *scratch_pool) +{ + return svn_error_trace(root1->vtable->contents_changed(changed_p, + root1, path1, + root2, path2, + TRUE, + scratch_pool)); +} + +svn_error_t * +svn_fs_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) +{ + return svn_error_trace(root1->vtable->contents_changed(changed_p, + root1, path1, + root2, path2, + FALSE, pool)); +} + +svn_error_t * +svn_fs_youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->youngest_rev(youngest_p, fs, pool)); +} + +svn_error_t * +svn_fs_info_format(int *fs_format, + svn_version_t **supports_version, + svn_fs_t *fs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->info_format(fs_format, supports_version, + fs, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_fs_info_config_files(apr_array_header_t **files, + svn_fs_t *fs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->info_config_files(files, fs, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_deltify_revision(svn_fs_t *fs, svn_revnum_t revision, apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->deltify(fs, revision, pool)); +} + +svn_error_t * +svn_fs_refresh_revision_props(svn_fs_t *fs, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->refresh_revprops(fs, scratch_pool)); +} + +svn_error_t * +svn_fs_revision_prop2(svn_string_t **value_p, + svn_fs_t *fs, + svn_revnum_t rev, + const char *propname, + svn_boolean_t refresh, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->revision_prop(value_p, fs, rev, + propname, refresh, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_revision_proplist2(apr_hash_t **table_p, + svn_fs_t *fs, + svn_revnum_t rev, + svn_boolean_t refresh, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->revision_proplist(table_p, fs, rev, + refresh, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_change_rev_prop2(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) +{ + return svn_error_trace(fs->vtable->change_rev_prop(fs, rev, name, + old_value_p, + value, pool)); +} + +svn_error_t * +svn_fs_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) +{ + return svn_error_trace(target_root->vtable->get_file_delta_stream( + stream_p, + source_root, source_path, + target_root, target_path, pool)); +} + +svn_error_t * +svn_fs__get_deleted_node(svn_fs_root_t **node_root, + const char **node_path, + svn_fs_root_t *root, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *parent_path, *name; + svn_fs_root_t *copy_root; + const char *copy_path; + + /* History traversal does not work with transaction roots. + * Therefore, do it "by hand". */ + + /* If the parent got copied in ROOT, PATH got copied with it. + * Otherwise, we will find the node at PATH in the revision prior to ROOT. + */ + svn_fspath__split(&parent_path, &name, path, scratch_pool); + SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, parent_path, + scratch_pool)); + + /* Copied in ROOT? */ + if ( copy_root + && ( svn_fs_revision_root_revision(copy_root) + == svn_fs_revision_root_revision(root))) + { + svn_revnum_t copyfrom_rev; + const char *copyfrom_path; + const char *rel_path; + SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, + copy_root, copy_path, scratch_pool)); + + SVN_ERR(svn_fs_revision_root(node_root, svn_fs_root_fs(root), + copyfrom_rev, result_pool)); + rel_path = svn_fspath__skip_ancestor(copy_path, path); + *node_path = svn_fspath__join(copyfrom_path, rel_path, result_pool); + } + else + { + svn_revnum_t revision; + svn_revnum_t previous_rev; + + /* Determine the latest revision before ROOT. */ + revision = svn_fs_revision_root_revision(root); + if (SVN_IS_VALID_REVNUM(revision)) + previous_rev = revision - 1; + else + previous_rev = svn_fs_txn_root_base_revision(root); + + SVN_ERR(svn_fs_revision_root(node_root, svn_fs_root_fs(root), + previous_rev, result_pool)); + *node_path = apr_pstrdup(result_pool, path); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_get_uuid(svn_fs_t *fs, const char **uuid, apr_pool_t *pool) +{ + /* If you change this, consider changing svn_fs__identifier(). */ + *uuid = apr_pstrdup(pool, fs->uuid); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_set_uuid(svn_fs_t *fs, const char *uuid, apr_pool_t *pool) +{ + if (! uuid) + { + uuid = svn_uuid_generate(pool); + } + else + { + apr_uuid_t parsed_uuid; + apr_status_t apr_err = apr_uuid_parse(&parsed_uuid, uuid); + if (apr_err) + return svn_error_createf(SVN_ERR_BAD_UUID, NULL, + _("Malformed UUID '%s'"), uuid); + } + return svn_error_trace(fs->vtable->set_uuid(fs, uuid, pool)); +} + +svn_error_t * +svn_fs_lock_many(svn_fs_t *fs, + apr_hash_t *targets, + const char *comment, + svn_boolean_t is_dav_comment, + apr_time_t expiration_date, + svn_boolean_t steal_lock, + svn_fs_lock_callback_t lock_callback, + void *lock_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_hash_t *ok_targets = apr_hash_make(scratch_pool); + svn_error_t *err, *cb_err = SVN_NO_ERROR; + + /* Enforce that the comment be xml-escapable. */ + if (comment) + if (! svn_xml_is_xml_safe(comment, strlen(comment))) + return svn_error_create(SVN_ERR_XML_UNESCAPABLE_DATA, NULL, + _("Lock comment contains illegal characters")); + + if (expiration_date < 0) + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Negative expiration date passed to svn_fs_lock")); + + /* Enforce that the token be an XML-safe URI. */ + for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) + { + const svn_fs_lock_target_t *target = apr_hash_this_val(hi); + + err = SVN_NO_ERROR; + if (target->token) + { + const char *c; + + + if (strncmp(target->token, "opaquelocktoken:", 16)) + err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, + _("Lock token URI '%s' has bad scheme; " + "expected '%s'"), + target->token, "opaquelocktoken"); + + if (!err) + { + for (c = target->token; *c && !err; c++) + if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c)) + err = svn_error_createf( + SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, + _("Lock token '%s' is not ASCII or is a " + "control character at byte %u"), + target->token, + (unsigned)(c - target->token)); + + /* strlen(token) == c - token. */ + if (!err && !svn_xml_is_xml_safe(target->token, + c - target->token)) + err = svn_error_createf( + SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, + _("Lock token URI '%s' is not XML-safe"), + target->token); + } + } + + if (err) + { + if (!cb_err && lock_callback) + cb_err = lock_callback(lock_baton, apr_hash_this_key(hi), + NULL, err, scratch_pool); + svn_error_clear(err); + } + else + svn_hash_sets(ok_targets, apr_hash_this_key(hi), target); + } + + if (!apr_hash_count(ok_targets)) + return svn_error_trace(cb_err); + + err = fs->vtable->lock(fs, ok_targets, comment, is_dav_comment, + expiration_date, steal_lock, + lock_callback, lock_baton, + result_pool, scratch_pool); + + if (err && cb_err) + svn_error_compose(err, cb_err); + else if (!err) + err = cb_err; + + return svn_error_trace(err); +} + +struct lock_baton_t { + const svn_lock_t *lock; + svn_error_t *fs_err; +}; + +/* Implements svn_fs_lock_callback_t. Used by svn_fs_lock and + svn_fs_unlock to record the lock and error from svn_fs_lock_many + and svn_fs_unlock_many. */ +static svn_error_t * +lock_cb(void *lock_baton, + const char *path, + const svn_lock_t *lock, + svn_error_t *fs_err, + apr_pool_t *pool) +{ + struct lock_baton_t *b = lock_baton; + + b->lock = lock; + b->fs_err = svn_error_dup(fs_err); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_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) +{ + apr_hash_t *targets = apr_hash_make(pool); + svn_fs_lock_target_t target; + svn_error_t *err; + struct lock_baton_t baton = {0}; + + target.token = token; + target.current_rev = current_rev; + svn_hash_sets(targets, path, &target); + + err = svn_fs_lock_many(fs, targets, comment, is_dav_comment, + expiration_date, steal_lock, lock_cb, &baton, + pool, pool); + + if (baton.lock) + *lock = (svn_lock_t*)baton.lock; + + if (err && baton.fs_err) + svn_error_compose(err, baton.fs_err); + else if (!err) + err = baton.fs_err; + + return svn_error_trace(err); +} + +svn_error_t * +svn_fs_generate_lock_token(const char **token, svn_fs_t *fs, apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->generate_lock_token(token, fs, pool)); +} + +svn_fs_lock_target_t * +svn_fs_lock_target_create(const char *token, + svn_revnum_t current_rev, + apr_pool_t *result_pool) +{ + svn_fs_lock_target_t *target = apr_palloc(result_pool, sizeof(*target)); + + target->token = token; + target->current_rev = current_rev; + + return target; +} + +void +svn_fs_lock_target_set_token(svn_fs_lock_target_t *target, + const char *token) +{ + target->token = token; +} + +svn_error_t * +svn_fs_unlock_many(svn_fs_t *fs, + apr_hash_t *targets, + svn_boolean_t break_lock, + svn_fs_lock_callback_t lock_callback, + void *lock_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(fs->vtable->unlock(fs, targets, break_lock, + lock_callback, lock_baton, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_fs_unlock(svn_fs_t *fs, const char *path, const char *token, + svn_boolean_t break_lock, apr_pool_t *pool) +{ + apr_hash_t *targets = apr_hash_make(pool); + svn_error_t *err; + struct lock_baton_t baton = {0}; + + if (!token) + token = ""; + svn_hash_sets(targets, path, token); + + err = svn_fs_unlock_many(fs, targets, break_lock, lock_cb, &baton, + pool, pool); + + if (err && baton.fs_err) + svn_error_compose(err, baton.fs_err); + else if (!err) + err = baton.fs_err; + + return svn_error_trace(err); +} + +svn_error_t * +svn_fs_get_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path, + apr_pool_t *pool) +{ + return svn_error_trace(fs->vtable->get_lock(lock, fs, path, pool)); +} + +svn_error_t * +svn_fs_get_locks2(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) +{ + SVN_ERR_ASSERT((depth == svn_depth_empty) || + (depth == svn_depth_files) || + (depth == svn_depth_immediates) || + (depth == svn_depth_infinity)); + return svn_error_trace(fs->vtable->get_locks(fs, path, depth, + get_locks_func, + get_locks_baton, pool)); +} + + +/* --- History functions --- */ + +svn_error_t * +svn_fs_history_prev2(svn_fs_history_t **prev_history_p, + svn_fs_history_t *history, svn_boolean_t cross_copies, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) +{ + return svn_error_trace(history->vtable->prev(prev_history_p, history, + cross_copies, result_pool, + scratch_pool)); +} + +svn_error_t * +svn_fs_history_location(const char **path, svn_revnum_t *revision, + svn_fs_history_t *history, apr_pool_t *pool) +{ + return svn_error_trace(history->vtable->location(path, revision, history, + pool)); +} + + +/* --- Node-ID functions --- */ + +svn_fs_id_t * +svn_fs_parse_id(const char *data, apr_size_t len, apr_pool_t *pool) +{ + fs_library_vtable_t *vtable; + svn_error_t *err; + + err = get_library_vtable(&vtable, SVN_FS_TYPE_BDB, pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + return vtable->parse_id(data, len, pool); +} + +svn_string_t * +svn_fs_unparse_id(const svn_fs_id_t *id, apr_pool_t *pool) +{ + return id->vtable->unparse(id, pool); +} + +svn_boolean_t +svn_fs_check_related(const svn_fs_id_t *a, const svn_fs_id_t *b) +{ + return (a->vtable->compare(a, b) != svn_fs_node_unrelated); +} + +int +svn_fs_compare_ids(const svn_fs_id_t *a, const svn_fs_id_t *b) +{ + switch (a->vtable->compare(a, b)) + { + case svn_fs_node_unchanged: + return 0; + case svn_fs_node_common_ancestor: + return 1; + default: + return -1; + } +} + +svn_error_t * +svn_fs_print_modules(svn_stringbuf_t *output, + apr_pool_t *pool) +{ + struct fs_type_defn *defn = fs_modules; + fs_library_vtable_t *vtable; + apr_pool_t *iterpool = svn_pool_create(pool); + + while (defn) + { + char *line; + svn_error_t *err; + + svn_pool_clear(iterpool); + + err = get_library_vtable_direct(&vtable, defn, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_UNKNOWN_FS_TYPE) + { + svn_error_clear(err); + defn = defn->next; + continue; + } + else + return err; + } + + line = apr_psprintf(iterpool, "* fs_%s : %s\n", + defn->fsap_name, vtable->get_description()); + svn_stringbuf_appendcstr(output, line); + defn = defn->next; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_fs_path_change2_t * +svn_fs_path_change2_create(const svn_fs_id_t *node_rev_id, + svn_fs_path_change_kind_t change_kind, + apr_pool_t *pool) +{ + return svn_fs__path_change_create_internal(node_rev_id, change_kind, pool); +} + +svn_fs_path_change3_t * +svn_fs_path_change3_create(svn_fs_path_change_kind_t change_kind, + apr_pool_t *result_pool) +{ + return svn_fs__path_change_create_internal2(change_kind, result_pool); +} + +svn_fs_path_change3_t * +svn_fs_path_change3_dup(svn_fs_path_change3_t *change, + apr_pool_t *result_pool) +{ + svn_fs_path_change3_t *copy = apr_pmemdup(result_pool, change, + sizeof(*copy)); + + copy->path.data = apr_pstrmemdup(result_pool, copy->path.data, + copy->path.len); + if (copy->copyfrom_path) + copy->copyfrom_path = apr_pstrdup(result_pool, change->copyfrom_path); + + return copy; +} + +/* Return the library version number. */ +const svn_version_t * +svn_fs_version(void) +{ + SVN_VERSION_BODY; +} + + +/** info **/ +svn_error_t * +svn_fs_info(const svn_fs_info_placeholder_t **info_p, + svn_fs_t *fs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (fs->vtable->info_fsap) + { + SVN_ERR(fs->vtable->info_fsap((const void **)info_p, fs, + result_pool, scratch_pool)); + } + else + { + svn_fs_info_placeholder_t *info = apr_palloc(result_pool, sizeof(*info)); + /* ### Ask the disk(!), since svn_fs_t doesn't cache the answer. */ + SVN_ERR(svn_fs_type(&info->fs_type, fs->path, result_pool)); + *info_p = info; + } + return SVN_NO_ERROR; +} + +void * +svn_fs_info_dup(const void *info_void, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_fs_info_placeholder_t *info = info_void; + fs_library_vtable_t *vtable; + + SVN_ERR(get_library_vtable(&vtable, info->fs_type, scratch_pool)); + + if (vtable->info_fsap_dup) + return vtable->info_fsap_dup(info_void, result_pool); + else + return apr_pmemdup(result_pool, info, sizeof(*info)); +} + |