summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/fs.c')
-rw-r--r--subversion/libsvn_fs_x/fs.c669
1 files changed, 669 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/fs.c b/subversion/libsvn_fs_x/fs.c
new file mode 100644
index 0000000..abc564d
--- /dev/null
+++ b/subversion/libsvn_fs_x/fs.c
@@ -0,0 +1,669 @@
+/* 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 <apr_thread_mutex.h>
+
+#include "svn_fs.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "svn_pools.h"
+#include "fs.h"
+#include "fs_x.h"
+#include "pack.h"
+#include "recovery.h"
+#include "hotcopy.h"
+#include "verify.h"
+#include "tree.h"
+#include "lock.h"
+#include "id.h"
+#include "revprops.h"
+#include "rep-cache.h"
+#include "transaction.h"
+#include "util.h"
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+/* A prefix for the pool userdata variables used to hold
+ per-filesystem shared data. See fs_serialized_init. */
+#define SVN_FSX_SHARED_USERDATA_PREFIX "svn-fsx-shared-"
+
+
+
+/* Initialize the part of FS that requires global serialization across all
+ instances. The caller is responsible of ensuring that serialization.
+ Use COMMON_POOL for process-wide and SCRATCH_POOL for temporary
+ allocations. */
+static svn_error_t *
+x_serialized_init(svn_fs_t *fs,
+ apr_pool_t *common_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ const char *key;
+ void *val;
+ svn_fs_x__shared_data_t *ffsd;
+ apr_status_t status;
+
+ /* Note that we are allocating a small amount of long-lived data for
+ each separate repository opened during the lifetime of the
+ svn_fs_initialize pool. It's unlikely that anyone will notice
+ the modest expenditure; the alternative is to allocate each structure
+ in a subpool, add a reference-count, and add a serialized destructor
+ to the FS vtable. That's more machinery than it's worth.
+
+ Picking an appropriate key for the shared data is tricky, because,
+ unfortunately, a filesystem UUID is not really unique. It is implicitly
+ shared between hotcopied (1), dump / loaded (2) or naively copied (3)
+ filesystems. We tackle this problem by using a combination of the UUID
+ and an instance ID as the key. This allows us to avoid key clashing
+ in (1) and (2).
+
+ Speaking of (3), there is not so much we can do about it, except maybe
+ provide a convenient way of fixing things. Naively copied filesystems
+ have identical filesystem UUIDs *and* instance IDs. With the key being
+ a combination of these two, clashes can be fixed by changing either of
+ them (or both), e.g. with svn_fs_set_uuid(). */
+
+
+ SVN_ERR_ASSERT(fs->uuid);
+ SVN_ERR_ASSERT(ffd->instance_id);
+
+ key = apr_pstrcat(scratch_pool, SVN_FSX_SHARED_USERDATA_PREFIX,
+ fs->uuid, ":", ffd->instance_id, SVN_VA_NULL);
+ status = apr_pool_userdata_get(&val, key, common_pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't fetch FSX shared data"));
+ ffsd = val;
+
+ if (!ffsd)
+ {
+ ffsd = apr_pcalloc(common_pool, sizeof(*ffsd));
+ ffsd->common_pool = common_pool;
+
+ /* POSIX fcntl locks are per-process, so we need a mutex for
+ intra-process synchronization when grabbing the repository write
+ lock. */
+ SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
+ SVN_FS_X__USE_LOCK_MUTEX, common_pool));
+
+ /* ... the pack lock ... */
+ SVN_ERR(svn_mutex__init(&ffsd->fs_pack_lock,
+ SVN_FS_X__USE_LOCK_MUTEX, common_pool));
+
+ /* ... not to mention locking the txn-current file. */
+ SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
+ SVN_FS_X__USE_LOCK_MUTEX, common_pool));
+
+ /* We also need a mutex for synchronizing access to the active
+ transaction list and free transaction pointer. */
+ SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock, TRUE, common_pool));
+
+ key = apr_pstrdup(common_pool, key);
+ status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't store FSX shared data"));
+ }
+
+ ffd->shared = ffsd;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* This function is provided for Subversion 1.0.x compatibility. It
+ has no effect for fsx backed Subversion filesystems. It conforms
+ to the fs_library_vtable_t.bdb_set_errcall() API. */
+static svn_error_t *
+x_set_errcall(svn_fs_t *fs,
+ void (*db_errcall_fcn)(const char *errpfx, char *msg))
+{
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct x_freeze_baton_t {
+ svn_fs_t *fs;
+ svn_fs_freeze_func_t freeze_func;
+ void *freeze_baton;
+} x_freeze_baton_t;
+
+static svn_error_t *
+x_freeze_body(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ x_freeze_baton_t *b = baton;
+ svn_boolean_t exists;
+
+ SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool));
+ if (exists)
+ SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs,
+ b->freeze_func, b->freeze_baton,
+ scratch_pool));
+ else
+ SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+x_freeze_body2(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ x_freeze_baton_t *b = baton;
+ SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+x_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *scratch_pool)
+{
+ x_freeze_baton_t b;
+
+ b.fs = fs;
+ b.freeze_func = freeze_func;
+ b.freeze_baton = freeze_baton;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+x_info(const void **fsx_info,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info));
+ info->fs_type = SVN_FS_TYPE_FSX;
+ info->shard_size = ffd->max_files_per_dir;
+ info->min_unpacked_rev = ffd->min_unpacked_rev;
+ *fsx_info = info;
+ return SVN_NO_ERROR;
+}
+
+/* Wrapper around svn_fs_x__revision_prop() adapting between function
+ signatures. */
+static svn_error_t *
+x_revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ SVN_ERR(svn_fs_x__revision_prop(value_p, fs, rev, propname, pool,
+ scratch_pool));
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrapper around svn_fs_x__get_revision_proplist() adapting between function
+ signatures. */
+static svn_error_t *
+x_revision_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ /* No need to bypass the caches for r/o access to revprops. */
+ SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrapper around svn_fs_x__set_uuid() adapting between function
+ signatures. */
+static svn_error_t *
+x_set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *scratch_pool)
+{
+ /* Whenever we set a new UUID, imply that FS will also be a different
+ * instance (on formats that support this). */
+ return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, scratch_pool));
+}
+
+/* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */
+static svn_error_t *
+x_begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The vtable associated with a specific open filesystem. */
+static fs_vtable_t fs_vtable = {
+ svn_fs_x__youngest_rev,
+ x_revision_prop,
+ x_revision_proplist,
+ svn_fs_x__change_rev_prop,
+ x_set_uuid,
+ svn_fs_x__revision_root,
+ x_begin_txn,
+ svn_fs_x__open_txn,
+ svn_fs_x__purge_txn,
+ svn_fs_x__list_transactions,
+ svn_fs_x__deltify,
+ svn_fs_x__lock,
+ svn_fs_x__generate_lock_token,
+ svn_fs_x__unlock,
+ svn_fs_x__get_lock,
+ svn_fs_x__get_locks,
+ svn_fs_x__info_format,
+ svn_fs_x__info_config_files,
+ x_info,
+ svn_fs_x__verify_root,
+ x_freeze,
+ x_set_errcall
+};
+
+
+/* Creating a new filesystem. */
+
+/* Set up vtable and fsap_data fields in FS. */
+static svn_error_t *
+initialize_fs_struct(svn_fs_t *fs)
+{
+ svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
+ fs->vtable = &fs_vtable;
+ fs->fsap_data = ffd;
+ return SVN_NO_ERROR;
+}
+
+/* Reset vtable and fsap_data fields in FS such that the FS is basically
+ * closed now. Note that FS must not hold locks when you call this. */
+static void
+uninitialize_fs_struct(svn_fs_t *fs)
+{
+ fs->vtable = NULL;
+ fs->fsap_data = NULL;
+}
+
+/* This implements the fs_library_vtable_t.create() API. Create a new
+ fsx-backed Subversion filesystem at path PATH and link it into
+ *FS.
+
+ Perform temporary allocations in SCRATCH_POOL, and fs-global allocations
+ in COMMON_POOL. The latter must be serialized using COMMON_POOL_LOCK. */
+static svn_error_t *
+x_create(svn_fs_t *fs,
+ const char *path,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
+ SVN_ERR(initialize_fs_struct(fs));
+
+ SVN_ERR(svn_fs_x__create(fs, path, scratch_pool));
+
+ SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool));
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ x_serialized_init(fs, common_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Gaining access to an existing filesystem. */
+
+/* This implements the fs_library_vtable_t.open() API. Open an FSX
+ Subversion filesystem located at PATH, set *FS to point to the
+ correct vtable for the filesystem. Use SCRATCH_POOL for any temporary
+ allocations, and COMMON_POOL for fs-global allocations.
+ The latter must be serialized using COMMON_POOL_LOCK. */
+static svn_error_t *
+x_open(svn_fs_t *fs,
+ const char *path,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
+ SVN_ERR(initialize_fs_struct(fs));
+
+ SVN_ERR(svn_fs_x__open(fs, path, subpool));
+
+ SVN_ERR(svn_fs_x__initialize_caches(fs, subpool));
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ x_serialized_init(fs, common_pool, subpool));
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* This implements the fs_library_vtable_t.open_for_recovery() API. */
+static svn_error_t *
+x_open_for_recovery(svn_fs_t *fs,
+ const char *path,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ svn_error_t * err;
+ svn_revnum_t youngest_rev;
+ apr_pool_t * subpool = svn_pool_create(scratch_pool);
+
+ /* Recovery for FSFS is currently limited to recreating the 'current'
+ file from the latest revision. */
+
+ /* The only thing we have to watch out for is that the 'current' file
+ might not exist or contain garbage. So we'll try to read it here
+ and provide or replace the existing file if we couldn't read it.
+ (We'll also need it to exist later anyway as a source for the new
+ file's permissions). */
+
+ /* Use a partly-filled fs pointer first to create 'current'. */
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ SVN_ERR(initialize_fs_struct(fs));
+
+ /* Figure out the repo format and check that we can even handle it. */
+ SVN_ERR(svn_fs_x__read_format_file(fs, subpool));
+
+ /* Now, read 'current' and try to patch it if necessary. */
+ err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool);
+ if (err)
+ {
+ const char *file_path;
+
+ /* 'current' file is missing or contains garbage. Since we are trying
+ * to recover from whatever problem there is, being picky about the
+ * error code here won't do us much good. If there is a persistent
+ * problem that we can't fix, it will show up when we try rewrite the
+ * file a few lines further below and we will report the failure back
+ * to the caller.
+ *
+ * Start recovery with HEAD = 0. */
+ svn_error_clear(err);
+ file_path = svn_fs_x__path_current(fs, subpool);
+
+ /* Best effort to ensure the file exists and is valid.
+ * This may fail for r/o filesystems etc. */
+ SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
+ SVN_ERR(svn_io_file_create_empty(file_path, subpool));
+ SVN_ERR(svn_fs_x__write_current(fs, 0, subpool));
+ }
+
+ uninitialize_fs_struct(fs);
+ svn_pool_destroy(subpool);
+
+ /* Now open the filesystem properly by calling the vtable method directly. */
+ return x_open(fs, path, common_pool_lock, scratch_pool, common_pool);
+}
+
+
+
+/* This implements the fs_library_vtable_t.upgrade_fs() API. */
+static svn_error_t *
+x_upgrade(svn_fs_t *fs,
+ const char *path,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
+ return svn_fs_x__upgrade(fs, notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool);
+}
+
+static svn_error_t *
+x_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,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
+ return svn_fs_x__verify(fs, start, end, notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool);
+}
+
+static svn_error_t *
+x_pack(svn_fs_t *fs,
+ const char *path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
+ return svn_fs_x__pack(fs, notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool);
+}
+
+
+
+
+/* This implements the fs_library_vtable_t.hotcopy() API. Copy a
+ possibly live Subversion filesystem SRC_FS from SRC_PATH to a
+ DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
+ re-copy data which already exists in DST_FS.
+ The CLEAN_LOGS argument is ignored and included for Subversion
+ 1.0.x compatibility. The NOTIFY_FUNC and NOTIFY_BATON arguments
+ are also currently ignored.
+ Perform all temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+x_hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_fs_hotcopy_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *common_pool)
+{
+ /* Open the source repo as usual. */
+ SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool,
+ common_pool));
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Test target repo when in INCREMENTAL mode, initialize it when not.
+ * For this, we need our FS internal data structures to be temporarily
+ * available. */
+ SVN_ERR(initialize_fs_struct(dst_fs));
+ SVN_ERR(svn_fs_x__hotcopy_prepare_target(src_fs, dst_fs, dst_path,
+ incremental, scratch_pool));
+ uninitialize_fs_struct(dst_fs);
+
+ /* Now, the destination repo should open just fine. */
+ SVN_ERR(x_open(dst_fs, dst_path, common_pool_lock, scratch_pool,
+ common_pool));
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Now, we may copy data as needed ... */
+ return svn_fs_x__hotcopy(src_fs, dst_fs, incremental,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool);
+}
+
+
+
+/* This function is included for Subversion 1.0.x compatibility. It
+ has no effect for fsx backed Subversion filesystems. It conforms
+ to the fs_library_vtable_t.bdb_logfiles() API. */
+static svn_error_t *
+x_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ /* A no-op for FSX. */
+ *logfiles = apr_array_make(pool, 0, sizeof(const char *));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+
+/* Delete the filesystem located at path PATH. Perform any temporary
+ allocations in SCRATCH_POOL. */
+static svn_error_t *
+x_delete_fs(const char *path,
+ apr_pool_t *scratch_pool)
+{
+ /* Remove everything. */
+ return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL,
+ scratch_pool));
+}
+
+static const svn_version_t *
+x_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+static const char *
+x_get_description(void)
+{
+ return _("Module for working with an experimental (FSX) repository.");
+}
+
+static svn_error_t *
+x_set_svn_fs_open(svn_fs_t *fs,
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **,
+ const char *,
+ apr_hash_t *,
+ apr_pool_t *,
+ apr_pool_t *))
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ ffd->svn_fs_open_ = svn_fs_open_;
+ return SVN_NO_ERROR;
+}
+
+static void *
+x_info_dup(const void *fsx_info_void,
+ apr_pool_t *result_pool)
+{
+ /* All fields are either ints or static strings. */
+ const svn_fs_fsx_info_t *fsx_info = fsx_info_void;
+ return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info));
+}
+
+
+/* Base FS library vtable, used by the FS loader library. */
+
+static fs_library_vtable_t library_vtable = {
+ x_version,
+ x_create,
+ x_open,
+ x_open_for_recovery,
+ x_upgrade,
+ x_verify,
+ x_delete_fs,
+ x_hotcopy,
+ x_get_description,
+ svn_fs_x__recover,
+ x_pack,
+ x_logfiles,
+ NULL /* parse_id */,
+ x_set_svn_fs_open,
+ x_info_dup
+};
+
+svn_error_t *
+svn_fs_x__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 },
+ { "svn_fs_util", svn_fs_util__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 fsx"),
+ loader_version->major);
+ SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal));
+
+ *vtable = &library_vtable;
+ return SVN_NO_ERROR;
+}