]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/fs.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / fs.c
1 /* fs.c --- creating, opening and closing filesystems
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include <apr_general.h>
28 #include <apr_pools.h>
29 #include <apr_file_io.h>
30 #include <apr_thread_mutex.h>
31
32 #include "svn_fs.h"
33 #include "svn_delta.h"
34 #include "svn_version.h"
35 #include "svn_pools.h"
36 #include "fs.h"
37 #include "fs_x.h"
38 #include "pack.h"
39 #include "recovery.h"
40 #include "hotcopy.h"
41 #include "verify.h"
42 #include "tree.h"
43 #include "lock.h"
44 #include "id.h"
45 #include "revprops.h"
46 #include "rep-cache.h"
47 #include "transaction.h"
48 #include "util.h"
49 #include "svn_private_config.h"
50 #include "private/svn_fs_util.h"
51
52 #include "../libsvn_fs/fs-loader.h"
53
54 /* A prefix for the pool userdata variables used to hold
55    per-filesystem shared data.  See fs_serialized_init. */
56 #define SVN_FSX_SHARED_USERDATA_PREFIX "svn-fsx-shared-"
57
58 \f
59
60 /* Initialize the part of FS that requires global serialization across all
61    instances.  The caller is responsible of ensuring that serialization.
62    Use COMMON_POOL for process-wide and SCRATCH_POOL for temporary
63    allocations. */
64 static svn_error_t *
65 x_serialized_init(svn_fs_t *fs,
66                   apr_pool_t *common_pool,
67                   apr_pool_t *scratch_pool)
68 {
69   svn_fs_x__data_t *ffd = fs->fsap_data;
70   const char *key;
71   void *val;
72   svn_fs_x__shared_data_t *ffsd;
73   apr_status_t status;
74
75   /* Note that we are allocating a small amount of long-lived data for
76      each separate repository opened during the lifetime of the
77      svn_fs_initialize pool.  It's unlikely that anyone will notice
78      the modest expenditure; the alternative is to allocate each structure
79      in a subpool, add a reference-count, and add a serialized destructor
80      to the FS vtable.  That's more machinery than it's worth.
81
82      Picking an appropriate key for the shared data is tricky, because,
83      unfortunately, a filesystem UUID is not really unique.  It is implicitly
84      shared between hotcopied (1), dump / loaded (2) or naively copied (3)
85      filesystems.  We tackle this problem by using a combination of the UUID
86      and an instance ID as the key.  This allows us to avoid key clashing
87      in (1) and (2).
88
89      Speaking of (3), there is not so much we can do about it, except maybe
90      provide a convenient way of fixing things.  Naively copied filesystems
91      have identical filesystem UUIDs *and* instance IDs.  With the key being
92      a combination of these two, clashes can be fixed by changing either of
93      them (or both), e.g. with svn_fs_set_uuid(). */
94
95
96   SVN_ERR_ASSERT(fs->uuid);
97   SVN_ERR_ASSERT(ffd->instance_id);
98
99   key = apr_pstrcat(scratch_pool, SVN_FSX_SHARED_USERDATA_PREFIX,
100                     fs->uuid, ":", ffd->instance_id, SVN_VA_NULL);
101   status = apr_pool_userdata_get(&val, key, common_pool);
102   if (status)
103     return svn_error_wrap_apr(status, _("Can't fetch FSX shared data"));
104   ffsd = val;
105
106   if (!ffsd)
107     {
108       ffsd = apr_pcalloc(common_pool, sizeof(*ffsd));
109       ffsd->common_pool = common_pool;
110
111       /* POSIX fcntl locks are per-process, so we need a mutex for
112          intra-process synchronization when grabbing the repository write
113          lock. */
114       SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
115                               SVN_FS_X__USE_LOCK_MUTEX, common_pool));
116
117       /* ... the pack lock ... */
118       SVN_ERR(svn_mutex__init(&ffsd->fs_pack_lock,
119                               SVN_FS_X__USE_LOCK_MUTEX, common_pool));
120
121       /* ... not to mention locking the txn-current file. */
122       SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
123                               SVN_FS_X__USE_LOCK_MUTEX, common_pool));
124
125       /* We also need a mutex for synchronizing access to the active
126          transaction list and free transaction pointer. */
127       SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock, TRUE, common_pool));
128
129       key = apr_pstrdup(common_pool, key);
130       status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
131       if (status)
132         return svn_error_wrap_apr(status, _("Can't store FSX shared data"));
133     }
134
135   ffd->shared = ffsd;
136
137   return SVN_NO_ERROR;
138 }
139
140 \f
141
142 /* This function is provided for Subversion 1.0.x compatibility.  It
143    has no effect for fsx backed Subversion filesystems.  It conforms
144    to the fs_library_vtable_t.bdb_set_errcall() API. */
145 static svn_error_t *
146 x_set_errcall(svn_fs_t *fs,
147               void (*db_errcall_fcn)(const char *errpfx, char *msg))
148 {
149
150   return SVN_NO_ERROR;
151 }
152
153 typedef struct x_freeze_baton_t {
154   svn_fs_t *fs;
155   svn_fs_freeze_func_t freeze_func;
156   void *freeze_baton;
157 } x_freeze_baton_t;
158
159 static svn_error_t *
160 x_freeze_body(void *baton,
161               apr_pool_t *scratch_pool)
162 {
163   x_freeze_baton_t *b = baton;
164   svn_boolean_t exists;
165
166   SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool));
167   if (exists)
168     SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs,
169                                           b->freeze_func, b->freeze_baton,
170                                           scratch_pool));
171   else
172     SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool));
173
174   return SVN_NO_ERROR;
175 }
176
177 static svn_error_t *
178 x_freeze_body2(void *baton,
179                apr_pool_t *scratch_pool)
180 {
181   x_freeze_baton_t *b = baton;
182   SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton,
183                                     scratch_pool));
184
185   return SVN_NO_ERROR;
186 }
187
188 static svn_error_t *
189 x_freeze(svn_fs_t *fs,
190          svn_fs_freeze_func_t freeze_func,
191          void *freeze_baton,
192          apr_pool_t *scratch_pool)
193 {
194   x_freeze_baton_t b;
195
196   b.fs = fs;
197   b.freeze_func = freeze_func;
198   b.freeze_baton = freeze_baton;
199
200   SVN_ERR(svn_fs__check_fs(fs, TRUE));
201   SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool));
202
203   return SVN_NO_ERROR;
204 }
205
206 static svn_error_t *
207 x_info(const void **fsx_info,
208        svn_fs_t *fs,
209        apr_pool_t *result_pool,
210        apr_pool_t *scratch_pool)
211 {
212   svn_fs_x__data_t *ffd = fs->fsap_data;
213   svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info));
214   info->fs_type = SVN_FS_TYPE_FSX;
215   info->shard_size = ffd->max_files_per_dir;
216   info->min_unpacked_rev = ffd->min_unpacked_rev;
217   *fsx_info = info;
218   return SVN_NO_ERROR;
219 }
220
221 /* Wrapper around svn_fs_x__revision_prop() adapting between function
222    signatures. */
223 static svn_error_t *
224 x_revision_prop(svn_string_t **value_p,
225                 svn_fs_t *fs,
226                 svn_revnum_t rev,
227                 const char *propname,
228                 apr_pool_t *pool)
229 {
230   apr_pool_t *scratch_pool = svn_pool_create(pool);
231   SVN_ERR(svn_fs_x__revision_prop(value_p, fs, rev, propname, pool,
232                                   scratch_pool));
233   svn_pool_destroy(scratch_pool);
234
235   return SVN_NO_ERROR;
236 }
237
238 /* Wrapper around svn_fs_x__get_revision_proplist() adapting between function
239    signatures. */
240 static svn_error_t *
241 x_revision_proplist(apr_hash_t **proplist_p,
242                     svn_fs_t *fs,
243                     svn_revnum_t rev,
244                     apr_pool_t *pool)
245 {
246   apr_pool_t *scratch_pool = svn_pool_create(pool);
247
248   /* No need to bypass the caches for r/o access to revprops. */
249   SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE,
250                                           pool, scratch_pool));
251   svn_pool_destroy(scratch_pool);
252
253   return SVN_NO_ERROR;
254 }
255
256 /* Wrapper around svn_fs_x__set_uuid() adapting between function
257    signatures. */
258 static svn_error_t *
259 x_set_uuid(svn_fs_t *fs,
260            const char *uuid,
261            apr_pool_t *scratch_pool)
262 {
263   /* Whenever we set a new UUID, imply that FS will also be a different
264    * instance (on formats that support this). */
265   return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, scratch_pool));
266 }
267
268 /* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */
269 static svn_error_t *
270 x_begin_txn(svn_fs_txn_t **txn_p,
271             svn_fs_t *fs,
272             svn_revnum_t rev,
273             apr_uint32_t flags,
274             apr_pool_t *pool)
275 {
276   apr_pool_t *scratch_pool = svn_pool_create(pool);
277   SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool));
278   svn_pool_destroy(scratch_pool);
279
280   return SVN_NO_ERROR;
281 }
282
283 \f
284
285 /* The vtable associated with a specific open filesystem. */
286 static fs_vtable_t fs_vtable = {
287   svn_fs_x__youngest_rev,
288   x_revision_prop,
289   x_revision_proplist,
290   svn_fs_x__change_rev_prop,
291   x_set_uuid,
292   svn_fs_x__revision_root,
293   x_begin_txn,
294   svn_fs_x__open_txn,
295   svn_fs_x__purge_txn,
296   svn_fs_x__list_transactions,
297   svn_fs_x__deltify,
298   svn_fs_x__lock,
299   svn_fs_x__generate_lock_token,
300   svn_fs_x__unlock,
301   svn_fs_x__get_lock,
302   svn_fs_x__get_locks,
303   svn_fs_x__info_format,
304   svn_fs_x__info_config_files,
305   x_info,
306   svn_fs_x__verify_root,
307   x_freeze,
308   x_set_errcall
309 };
310
311 \f
312 /* Creating a new filesystem. */
313
314 /* Set up vtable and fsap_data fields in FS. */
315 static svn_error_t *
316 initialize_fs_struct(svn_fs_t *fs)
317 {
318   svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
319   fs->vtable = &fs_vtable;
320   fs->fsap_data = ffd;
321   return SVN_NO_ERROR;
322 }
323
324 /* Reset vtable and fsap_data fields in FS such that the FS is basically
325  * closed now.  Note that FS must not hold locks when you call this. */
326 static void
327 uninitialize_fs_struct(svn_fs_t *fs)
328 {
329   fs->vtable = NULL;
330   fs->fsap_data = NULL;
331 }
332
333 /* This implements the fs_library_vtable_t.create() API.  Create a new
334    fsx-backed Subversion filesystem at path PATH and link it into
335    *FS.
336
337    Perform temporary allocations in SCRATCH_POOL, and fs-global allocations
338    in COMMON_POOL.  The latter must be serialized using COMMON_POOL_LOCK. */
339 static svn_error_t *
340 x_create(svn_fs_t *fs,
341          const char *path,
342          svn_mutex__t *common_pool_lock,
343          apr_pool_t *scratch_pool,
344          apr_pool_t *common_pool)
345 {
346   SVN_ERR(svn_fs__check_fs(fs, FALSE));
347
348   SVN_ERR(initialize_fs_struct(fs));
349
350   SVN_ERR(svn_fs_x__create(fs, path, scratch_pool));
351
352   SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool));
353   SVN_MUTEX__WITH_LOCK(common_pool_lock,
354                        x_serialized_init(fs, common_pool, scratch_pool));
355
356   return SVN_NO_ERROR;
357 }
358
359
360 \f
361 /* Gaining access to an existing filesystem.  */
362
363 /* This implements the fs_library_vtable_t.open() API.  Open an FSX
364    Subversion filesystem located at PATH, set *FS to point to the
365    correct vtable for the filesystem.  Use SCRATCH_POOL for any temporary
366    allocations, and COMMON_POOL for fs-global allocations.
367    The latter must be serialized using COMMON_POOL_LOCK.  */
368 static svn_error_t *
369 x_open(svn_fs_t *fs,
370        const char *path,
371        svn_mutex__t *common_pool_lock,
372        apr_pool_t *scratch_pool,
373        apr_pool_t *common_pool)
374 {
375   apr_pool_t *subpool = svn_pool_create(scratch_pool);
376
377   SVN_ERR(svn_fs__check_fs(fs, FALSE));
378
379   SVN_ERR(initialize_fs_struct(fs));
380
381   SVN_ERR(svn_fs_x__open(fs, path, subpool));
382
383   SVN_ERR(svn_fs_x__initialize_caches(fs, subpool));
384   SVN_MUTEX__WITH_LOCK(common_pool_lock,
385                        x_serialized_init(fs, common_pool, subpool));
386
387   svn_pool_destroy(subpool);
388
389   return SVN_NO_ERROR;
390 }
391
392 \f
393
394 /* This implements the fs_library_vtable_t.open_for_recovery() API. */
395 static svn_error_t *
396 x_open_for_recovery(svn_fs_t *fs,
397                     const char *path,
398                     svn_mutex__t *common_pool_lock,
399                     apr_pool_t *scratch_pool,
400                     apr_pool_t *common_pool)
401 {
402   svn_error_t * err;
403   svn_revnum_t youngest_rev;
404   apr_pool_t * subpool = svn_pool_create(scratch_pool);
405
406   /* Recovery for FSFS is currently limited to recreating the 'current'
407      file from the latest revision. */
408
409   /* The only thing we have to watch out for is that the 'current' file
410      might not exist or contain garbage.  So we'll try to read it here
411      and provide or replace the existing file if we couldn't read it.
412      (We'll also need it to exist later anyway as a source for the new
413      file's permissions). */
414
415   /* Use a partly-filled fs pointer first to create 'current'. */
416   fs->path = apr_pstrdup(fs->pool, path);
417
418   SVN_ERR(initialize_fs_struct(fs));
419
420   /* Figure out the repo format and check that we can even handle it. */
421   SVN_ERR(svn_fs_x__read_format_file(fs, subpool));
422
423   /* Now, read 'current' and try to patch it if necessary. */
424   err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool);
425   if (err)
426     {
427       const char *file_path;
428
429       /* 'current' file is missing or contains garbage.  Since we are trying
430        * to recover from whatever problem there is, being picky about the
431        * error code here won't do us much good.  If there is a persistent
432        * problem that we can't fix, it will show up when we try rewrite the
433        * file a few lines further below and we will report the failure back
434        * to the caller.
435        *
436        * Start recovery with HEAD = 0. */
437       svn_error_clear(err);
438       file_path = svn_fs_x__path_current(fs, subpool);
439
440       /* Best effort to ensure the file exists and is valid.
441        * This may fail for r/o filesystems etc. */
442       SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
443       SVN_ERR(svn_io_file_create_empty(file_path, subpool));
444       SVN_ERR(svn_fs_x__write_current(fs, 0, subpool));
445     }
446
447   uninitialize_fs_struct(fs);
448   svn_pool_destroy(subpool);
449
450   /* Now open the filesystem properly by calling the vtable method directly. */
451   return x_open(fs, path, common_pool_lock, scratch_pool, common_pool);
452 }
453
454 \f
455
456 /* This implements the fs_library_vtable_t.upgrade_fs() API. */
457 static svn_error_t *
458 x_upgrade(svn_fs_t *fs,
459           const char *path,
460           svn_fs_upgrade_notify_t notify_func,
461           void *notify_baton,
462           svn_cancel_func_t cancel_func,
463           void *cancel_baton,
464           svn_mutex__t *common_pool_lock,
465           apr_pool_t *scratch_pool,
466           apr_pool_t *common_pool)
467 {
468   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
469   return svn_fs_x__upgrade(fs, notify_func, notify_baton,
470                            cancel_func, cancel_baton, scratch_pool);
471 }
472
473 static svn_error_t *
474 x_verify(svn_fs_t *fs,
475          const char *path,
476          svn_revnum_t start,
477          svn_revnum_t end,
478          svn_fs_progress_notify_func_t notify_func,
479          void *notify_baton,
480          svn_cancel_func_t cancel_func,
481          void *cancel_baton,
482          svn_mutex__t *common_pool_lock,
483          apr_pool_t *scratch_pool,
484          apr_pool_t *common_pool)
485 {
486   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
487   return svn_fs_x__verify(fs, start, end, notify_func, notify_baton,
488                           cancel_func, cancel_baton, scratch_pool);
489 }
490
491 static svn_error_t *
492 x_pack(svn_fs_t *fs,
493        const char *path,
494        svn_fs_pack_notify_t notify_func,
495        void *notify_baton,
496        svn_cancel_func_t cancel_func,
497        void *cancel_baton,
498        svn_mutex__t *common_pool_lock,
499        apr_pool_t *scratch_pool,
500        apr_pool_t *common_pool)
501 {
502   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
503   return svn_fs_x__pack(fs, notify_func, notify_baton,
504                         cancel_func, cancel_baton, scratch_pool);
505 }
506
507
508 \f
509
510 /* This implements the fs_library_vtable_t.hotcopy() API.  Copy a
511    possibly live Subversion filesystem SRC_FS from SRC_PATH to a
512    DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
513    re-copy data which already exists in DST_FS.
514    The CLEAN_LOGS argument is ignored and included for Subversion
515    1.0.x compatibility.  The NOTIFY_FUNC and NOTIFY_BATON arguments
516    are also currently ignored.
517    Perform all temporary allocations in SCRATCH_POOL. */
518 static svn_error_t *
519 x_hotcopy(svn_fs_t *src_fs,
520           svn_fs_t *dst_fs,
521           const char *src_path,
522           const char *dst_path,
523           svn_boolean_t clean_logs,
524           svn_boolean_t incremental,
525           svn_fs_hotcopy_notify_t notify_func,
526           void *notify_baton,
527           svn_cancel_func_t cancel_func,
528           void *cancel_baton,
529           svn_mutex__t *common_pool_lock,
530           apr_pool_t *scratch_pool,
531           apr_pool_t *common_pool)
532 {
533   /* Open the source repo as usual. */
534   SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool,
535                  common_pool));
536   if (cancel_func)
537     SVN_ERR(cancel_func(cancel_baton));
538
539   /* Test target repo when in INCREMENTAL mode, initialize it when not.
540    * For this, we need our FS internal data structures to be temporarily
541    * available. */
542   SVN_ERR(initialize_fs_struct(dst_fs));
543   SVN_ERR(svn_fs_x__hotcopy_prepare_target(src_fs, dst_fs, dst_path,
544                                            incremental, scratch_pool));
545   uninitialize_fs_struct(dst_fs);
546
547   /* Now, the destination repo should open just fine. */
548   SVN_ERR(x_open(dst_fs, dst_path, common_pool_lock, scratch_pool,
549                  common_pool));
550   if (cancel_func)
551     SVN_ERR(cancel_func(cancel_baton));
552
553   /* Now, we may copy data as needed ... */
554   return svn_fs_x__hotcopy(src_fs, dst_fs, incremental,
555                            notify_func, notify_baton,
556                            cancel_func, cancel_baton, scratch_pool);
557 }
558
559 \f
560
561 /* This function is included for Subversion 1.0.x compatibility.  It
562    has no effect for fsx backed Subversion filesystems.  It conforms
563    to the fs_library_vtable_t.bdb_logfiles() API. */
564 static svn_error_t *
565 x_logfiles(apr_array_header_t **logfiles,
566            const char *path,
567            svn_boolean_t only_unused,
568            apr_pool_t *pool)
569 {
570   /* A no-op for FSX. */
571   *logfiles = apr_array_make(pool, 0, sizeof(const char *));
572
573   return SVN_NO_ERROR;
574 }
575
576
577
578 \f
579
580 /* Delete the filesystem located at path PATH.  Perform any temporary
581    allocations in SCRATCH_POOL. */
582 static svn_error_t *
583 x_delete_fs(const char *path,
584             apr_pool_t *scratch_pool)
585 {
586   /* Remove everything. */
587   return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL,
588                                             scratch_pool));
589 }
590
591 static const svn_version_t *
592 x_version(void)
593 {
594   SVN_VERSION_BODY;
595 }
596
597 static const char *
598 x_get_description(void)
599 {
600   return _("Module for working with an experimental (FSX) repository.");
601 }
602
603 static svn_error_t *
604 x_set_svn_fs_open(svn_fs_t *fs,
605                   svn_error_t *(*svn_fs_open_)(svn_fs_t **,
606                                                const char *,
607                                                apr_hash_t *,
608                                                apr_pool_t *,
609                                                apr_pool_t *))
610 {
611   svn_fs_x__data_t *ffd = fs->fsap_data;
612   ffd->svn_fs_open_ = svn_fs_open_;
613   return SVN_NO_ERROR;
614 }
615
616 static void *
617 x_info_dup(const void *fsx_info_void,
618            apr_pool_t *result_pool)
619 {
620   /* All fields are either ints or static strings. */
621   const svn_fs_fsx_info_t *fsx_info = fsx_info_void;
622   return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info));
623 }
624
625 \f
626 /* Base FS library vtable, used by the FS loader library. */
627
628 static fs_library_vtable_t library_vtable = {
629   x_version,
630   x_create,
631   x_open,
632   x_open_for_recovery,
633   x_upgrade,
634   x_verify,
635   x_delete_fs,
636   x_hotcopy,
637   x_get_description,
638   svn_fs_x__recover,
639   x_pack,
640   x_logfiles,
641   NULL /* parse_id */,
642   x_set_svn_fs_open,
643   x_info_dup
644 };
645
646 svn_error_t *
647 svn_fs_x__init(const svn_version_t *loader_version,
648                fs_library_vtable_t **vtable,
649                apr_pool_t* common_pool)
650 {
651   static const svn_version_checklist_t checklist[] =
652     {
653       { "svn_subr",  svn_subr_version },
654       { "svn_delta", svn_delta_version },
655       { "svn_fs_util", svn_fs_util__version },
656       { NULL, NULL }
657     };
658
659   /* Simplified version check to make sure we can safely use the
660      VTABLE parameter. The FS loader does a more exhaustive check. */
661   if (loader_version->major != SVN_VER_MAJOR)
662     return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
663                              _("Unsupported FS loader version (%d) for fsx"),
664                              loader_version->major);
665   SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal));
666
667   *vtable = &library_vtable;
668   return SVN_NO_ERROR;
669 }