]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_x/fs.c
Upgrade Unbound to 1.6.4. More to follow.
[FreeBSD/FreeBSD.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
31 #include "svn_fs.h"
32 #include "svn_delta.h"
33 #include "svn_version.h"
34 #include "svn_pools.h"
35 #include "batch_fsync.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 svn_error_t *
141 svn_fs_x__initialize_shared_data(svn_fs_t *fs,
142                                  svn_mutex__t *common_pool_lock,
143                                  apr_pool_t *scratch_pool,
144                                  apr_pool_t *common_pool)
145 {
146   SVN_MUTEX__WITH_LOCK(common_pool_lock,
147                        x_serialized_init(fs, common_pool, scratch_pool));
148
149   return SVN_NO_ERROR;
150 }
151
152 \f
153
154 /* This function is provided for Subversion 1.0.x compatibility.  It
155    has no effect for fsx backed Subversion filesystems.  It conforms
156    to the fs_library_vtable_t.bdb_set_errcall() API. */
157 static svn_error_t *
158 x_set_errcall(svn_fs_t *fs,
159               void (*db_errcall_fcn)(const char *errpfx, char *msg))
160 {
161
162   return SVN_NO_ERROR;
163 }
164
165 typedef struct x_freeze_baton_t {
166   svn_fs_t *fs;
167   svn_fs_freeze_func_t freeze_func;
168   void *freeze_baton;
169 } x_freeze_baton_t;
170
171 static svn_error_t *
172 x_freeze_body(void *baton,
173               apr_pool_t *scratch_pool)
174 {
175   x_freeze_baton_t *b = baton;
176   svn_boolean_t exists;
177
178   SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool));
179   if (exists)
180     SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs,
181                                           b->freeze_func, b->freeze_baton,
182                                           scratch_pool));
183   else
184     SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool));
185
186   return SVN_NO_ERROR;
187 }
188
189 static svn_error_t *
190 x_freeze_body2(void *baton,
191                apr_pool_t *scratch_pool)
192 {
193   x_freeze_baton_t *b = baton;
194   SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton,
195                                     scratch_pool));
196
197   return SVN_NO_ERROR;
198 }
199
200 static svn_error_t *
201 x_freeze(svn_fs_t *fs,
202          svn_fs_freeze_func_t freeze_func,
203          void *freeze_baton,
204          apr_pool_t *scratch_pool)
205 {
206   x_freeze_baton_t b;
207
208   b.fs = fs;
209   b.freeze_func = freeze_func;
210   b.freeze_baton = freeze_baton;
211
212   SVN_ERR(svn_fs__check_fs(fs, TRUE));
213   SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool));
214
215   return SVN_NO_ERROR;
216 }
217
218 static svn_error_t *
219 x_info(const void **fsx_info,
220        svn_fs_t *fs,
221        apr_pool_t *result_pool,
222        apr_pool_t *scratch_pool)
223 {
224   svn_fs_x__data_t *ffd = fs->fsap_data;
225   svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info));
226   info->fs_type = SVN_FS_TYPE_FSX;
227   info->shard_size = ffd->max_files_per_dir;
228   info->min_unpacked_rev = ffd->min_unpacked_rev;
229   *fsx_info = info;
230   return SVN_NO_ERROR;
231 }
232
233 static svn_error_t *
234 x_refresh_revprops(svn_fs_t *fs,
235                    apr_pool_t *scratch_pool)
236 {
237   svn_fs_x__invalidate_revprop_generation(fs);
238   return SVN_NO_ERROR;
239 }
240
241 /* Wrapper around svn_fs_x__get_revision_proplist() adapting between function
242    signatures. */
243 static svn_error_t *
244 x_revision_proplist(apr_hash_t **proplist_p,
245                     svn_fs_t *fs,
246                     svn_revnum_t rev,
247                     svn_boolean_t refresh,
248                     apr_pool_t *result_pool,
249                     apr_pool_t *scratch_pool)
250 {
251   /* No need to bypass the caches for r/o access to revprops. */
252   SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE,
253                                           refresh, result_pool,
254                                           scratch_pool));
255
256   return SVN_NO_ERROR;
257 }
258
259 /* Wrapper around svn_fs_x__set_uuid() adapting between function
260    signatures. */
261 static svn_error_t *
262 x_set_uuid(svn_fs_t *fs,
263            const char *uuid,
264            apr_pool_t *scratch_pool)
265 {
266   /* Whenever we set a new UUID, imply that FS will also be a different
267    * instance (on formats that support this). */
268   return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, TRUE,
269                                             scratch_pool));
270 }
271
272 /* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */
273 static svn_error_t *
274 x_begin_txn(svn_fs_txn_t **txn_p,
275             svn_fs_t *fs,
276             svn_revnum_t rev,
277             apr_uint32_t flags,
278             apr_pool_t *pool)
279 {
280   apr_pool_t *scratch_pool = svn_pool_create(pool);
281   SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool));
282   svn_pool_destroy(scratch_pool);
283
284   return SVN_NO_ERROR;
285 }
286
287 \f
288
289 /* The vtable associated with a specific open filesystem. */
290 static fs_vtable_t fs_vtable = {
291   svn_fs_x__youngest_rev,
292   x_refresh_revprops,
293   svn_fs_x__revision_prop,
294   x_revision_proplist,
295   svn_fs_x__change_rev_prop,
296   x_set_uuid,
297   svn_fs_x__revision_root,
298   x_begin_txn,
299   svn_fs_x__open_txn,
300   svn_fs_x__purge_txn,
301   svn_fs_x__list_transactions,
302   svn_fs_x__deltify,
303   svn_fs_x__lock,
304   svn_fs_x__generate_lock_token,
305   svn_fs_x__unlock,
306   svn_fs_x__get_lock,
307   svn_fs_x__get_locks,
308   svn_fs_x__info_format,
309   svn_fs_x__info_config_files,
310   x_info,
311   svn_fs_x__verify_root,
312   x_freeze,
313   x_set_errcall
314 };
315
316 \f
317 /* Creating a new filesystem. */
318
319 /* Set up vtable and fsap_data fields in FS. */
320 static svn_error_t *
321 initialize_fs_struct(svn_fs_t *fs)
322 {
323   svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
324   ffd->revprop_generation = -1;
325   ffd->flush_to_disk = TRUE;
326
327   fs->vtable = &fs_vtable;
328   fs->fsap_data = ffd;
329   return SVN_NO_ERROR;
330 }
331
332 /* Reset vtable and fsap_data fields in FS such that the FS is basically
333  * closed now.  Note that FS must not hold locks when you call this. */
334 static void
335 uninitialize_fs_struct(svn_fs_t *fs)
336 {
337   fs->vtable = NULL;
338   fs->fsap_data = NULL;
339 }
340
341 /* This implements the fs_library_vtable_t.create() API.  Create a new
342    fsx-backed Subversion filesystem at path PATH and link it into
343    *FS.
344
345    Perform temporary allocations in SCRATCH_POOL, and fs-global allocations
346    in COMMON_POOL.  The latter must be serialized using COMMON_POOL_LOCK. */
347 static svn_error_t *
348 x_create(svn_fs_t *fs,
349          const char *path,
350          svn_mutex__t *common_pool_lock,
351          apr_pool_t *scratch_pool,
352          apr_pool_t *common_pool)
353 {
354   SVN_ERR(svn_fs__check_fs(fs, FALSE));
355
356   SVN_ERR(initialize_fs_struct(fs));
357
358   SVN_ERR(svn_fs_x__create(fs, path, scratch_pool));
359
360   SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool));
361   SVN_MUTEX__WITH_LOCK(common_pool_lock,
362                        x_serialized_init(fs, common_pool, scratch_pool));
363
364   return SVN_NO_ERROR;
365 }
366
367
368 \f
369 /* Gaining access to an existing filesystem.  */
370
371 /* This implements the fs_library_vtable_t.open() API.  Open an FSX
372    Subversion filesystem located at PATH, set *FS to point to the
373    correct vtable for the filesystem.  Use SCRATCH_POOL for any temporary
374    allocations, and COMMON_POOL for fs-global allocations.
375    The latter must be serialized using COMMON_POOL_LOCK.  */
376 static svn_error_t *
377 x_open(svn_fs_t *fs,
378        const char *path,
379        svn_mutex__t *common_pool_lock,
380        apr_pool_t *scratch_pool,
381        apr_pool_t *common_pool)
382 {
383   apr_pool_t *subpool = svn_pool_create(scratch_pool);
384
385   SVN_ERR(svn_fs__check_fs(fs, FALSE));
386
387   SVN_ERR(initialize_fs_struct(fs));
388
389   SVN_ERR(svn_fs_x__open(fs, path, subpool));
390
391   SVN_ERR(svn_fs_x__initialize_caches(fs, subpool));
392   SVN_MUTEX__WITH_LOCK(common_pool_lock,
393                        x_serialized_init(fs, common_pool, subpool));
394
395   svn_pool_destroy(subpool);
396
397   return SVN_NO_ERROR;
398 }
399
400 \f
401
402 /* This implements the fs_library_vtable_t.open_for_recovery() API. */
403 static svn_error_t *
404 x_open_for_recovery(svn_fs_t *fs,
405                     const char *path,
406                     svn_mutex__t *common_pool_lock,
407                     apr_pool_t *scratch_pool,
408                     apr_pool_t *common_pool)
409 {
410   svn_error_t * err;
411   svn_revnum_t youngest_rev;
412   apr_pool_t * subpool = svn_pool_create(scratch_pool);
413
414   /* Recovery for FSFS is currently limited to recreating the 'current'
415      file from the latest revision. */
416
417   /* The only thing we have to watch out for is that the 'current' file
418      might not exist or contain garbage.  So we'll try to read it here
419      and provide or replace the existing file if we couldn't read it.
420      (We'll also need it to exist later anyway as a source for the new
421      file's permissions). */
422
423   /* Use a partly-filled fs pointer first to create 'current'. */
424   fs->path = apr_pstrdup(fs->pool, path);
425
426   SVN_ERR(initialize_fs_struct(fs));
427
428   /* Figure out the repo format and check that we can even handle it. */
429   SVN_ERR(svn_fs_x__read_format_file(fs, subpool));
430
431   /* Now, read 'current' and try to patch it if necessary. */
432   err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool);
433   if (err)
434     {
435       const char *file_path;
436
437       /* 'current' file is missing or contains garbage.  Since we are trying
438        * to recover from whatever problem there is, being picky about the
439        * error code here won't do us much good.  If there is a persistent
440        * problem that we can't fix, it will show up when we try rewrite the
441        * file a few lines further below and we will report the failure back
442        * to the caller.
443        *
444        * Start recovery with HEAD = 0. */
445       svn_error_clear(err);
446       file_path = svn_fs_x__path_current(fs, subpool);
447
448       /* Best effort to ensure the file exists and is valid.
449        * This may fail for r/o filesystems etc. */
450       SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
451       SVN_ERR(svn_io_file_create_empty(file_path, subpool));
452       SVN_ERR(svn_fs_x__write_current(fs, 0, subpool));
453     }
454
455   uninitialize_fs_struct(fs);
456   svn_pool_destroy(subpool);
457
458   /* Now open the filesystem properly by calling the vtable method directly. */
459   return x_open(fs, path, common_pool_lock, scratch_pool, common_pool);
460 }
461
462 \f
463
464 /* This implements the fs_library_vtable_t.upgrade_fs() API. */
465 static svn_error_t *
466 x_upgrade(svn_fs_t *fs,
467           const char *path,
468           svn_fs_upgrade_notify_t notify_func,
469           void *notify_baton,
470           svn_cancel_func_t cancel_func,
471           void *cancel_baton,
472           svn_mutex__t *common_pool_lock,
473           apr_pool_t *scratch_pool,
474           apr_pool_t *common_pool)
475 {
476   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
477   return svn_fs_x__upgrade(fs, notify_func, notify_baton,
478                            cancel_func, cancel_baton, scratch_pool);
479 }
480
481 static svn_error_t *
482 x_verify(svn_fs_t *fs,
483          const char *path,
484          svn_revnum_t start,
485          svn_revnum_t end,
486          svn_fs_progress_notify_func_t notify_func,
487          void *notify_baton,
488          svn_cancel_func_t cancel_func,
489          void *cancel_baton,
490          svn_mutex__t *common_pool_lock,
491          apr_pool_t *scratch_pool,
492          apr_pool_t *common_pool)
493 {
494   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
495   return svn_fs_x__verify(fs, start, end, notify_func, notify_baton,
496                           cancel_func, cancel_baton, scratch_pool);
497 }
498
499 static svn_error_t *
500 x_pack(svn_fs_t *fs,
501        const char *path,
502        svn_fs_pack_notify_t notify_func,
503        void *notify_baton,
504        svn_cancel_func_t cancel_func,
505        void *cancel_baton,
506        svn_mutex__t *common_pool_lock,
507        apr_pool_t *scratch_pool,
508        apr_pool_t *common_pool)
509 {
510   SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
511   return svn_fs_x__pack(fs, 0, notify_func, notify_baton,
512                         cancel_func, cancel_baton, scratch_pool);
513 }
514
515
516 \f
517
518 /* This implements the fs_library_vtable_t.hotcopy() API.  Copy a
519    possibly live Subversion filesystem SRC_FS from SRC_PATH to a
520    DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
521    re-copy data which already exists in DST_FS.
522    The CLEAN_LOGS argument is ignored and included for Subversion
523    1.0.x compatibility.  The NOTIFY_FUNC and NOTIFY_BATON arguments
524    are also currently ignored.
525    Perform all temporary allocations in SCRATCH_POOL. */
526 static svn_error_t *
527 x_hotcopy(svn_fs_t *src_fs,
528           svn_fs_t *dst_fs,
529           const char *src_path,
530           const char *dst_path,
531           svn_boolean_t clean_logs,
532           svn_boolean_t incremental,
533           svn_fs_hotcopy_notify_t notify_func,
534           void *notify_baton,
535           svn_cancel_func_t cancel_func,
536           void *cancel_baton,
537           svn_mutex__t *common_pool_lock,
538           apr_pool_t *scratch_pool,
539           apr_pool_t *common_pool)
540 {
541   /* Open the source repo as usual. */
542   SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool,
543                  common_pool));
544   if (cancel_func)
545     SVN_ERR(cancel_func(cancel_baton));
546
547   SVN_ERR(svn_fs__check_fs(dst_fs, FALSE));
548   SVN_ERR(initialize_fs_struct(dst_fs));
549
550   /* In INCREMENTAL mode, svn_fs_x__hotcopy() will open DST_FS.
551      Otherwise, it's not an FS yet --- possibly just an empty dir --- so
552      can't be opened.
553    */
554   return svn_fs_x__hotcopy(src_fs, dst_fs, src_path, dst_path,
555                             incremental, notify_func, notify_baton,
556                             cancel_func, cancel_baton, common_pool_lock,
557                             scratch_pool, common_pool);
558 }
559
560 \f
561
562 /* This function is included for Subversion 1.0.x compatibility.  It
563    has no effect for fsx backed Subversion filesystems.  It conforms
564    to the fs_library_vtable_t.bdb_logfiles() API. */
565 static svn_error_t *
566 x_logfiles(apr_array_header_t **logfiles,
567            const char *path,
568            svn_boolean_t only_unused,
569            apr_pool_t *pool)
570 {
571   /* A no-op for FSX. */
572   *logfiles = apr_array_make(pool, 0, sizeof(const char *));
573
574   return SVN_NO_ERROR;
575 }
576
577
578
579 \f
580
581 /* Delete the filesystem located at path PATH.  Perform any temporary
582    allocations in SCRATCH_POOL. */
583 static svn_error_t *
584 x_delete_fs(const char *path,
585             apr_pool_t *scratch_pool)
586 {
587   /* Remove everything. */
588   return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL,
589                                             scratch_pool));
590 }
591
592 static const svn_version_t *
593 x_version(void)
594 {
595   SVN_VERSION_BODY;
596 }
597
598 static const char *
599 x_get_description(void)
600 {
601   return _("Module for working with an experimental (FSX) repository.");
602 }
603
604 static svn_error_t *
605 x_set_svn_fs_open(svn_fs_t *fs,
606                   svn_error_t *(*svn_fs_open_)(svn_fs_t **,
607                                                const char *,
608                                                apr_hash_t *,
609                                                apr_pool_t *,
610                                                apr_pool_t *))
611 {
612   svn_fs_x__data_t *ffd = fs->fsap_data;
613   ffd->svn_fs_open_ = svn_fs_open_;
614   return SVN_NO_ERROR;
615 }
616
617 static void *
618 x_info_dup(const void *fsx_info_void,
619            apr_pool_t *result_pool)
620 {
621   /* All fields are either ints or static strings. */
622   const svn_fs_fsx_info_t *fsx_info = fsx_info_void;
623   return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info));
624 }
625
626 \f
627 /* Base FS library vtable, used by the FS loader library. */
628
629 static fs_library_vtable_t library_vtable = {
630   x_version,
631   x_create,
632   x_open,
633   x_open_for_recovery,
634   x_upgrade,
635   x_verify,
636   x_delete_fs,
637   x_hotcopy,
638   x_get_description,
639   svn_fs_x__recover,
640   x_pack,
641   x_logfiles,
642   NULL /* parse_id */,
643   x_set_svn_fs_open,
644   x_info_dup
645 };
646
647 svn_error_t *
648 svn_fs_x__init(const svn_version_t *loader_version,
649                fs_library_vtable_t **vtable,
650                apr_pool_t* common_pool)
651 {
652   static const svn_version_checklist_t checklist[] =
653     {
654       { "svn_subr",  svn_subr_version },
655       { "svn_delta", svn_delta_version },
656       { "svn_fs_util", svn_fs_util__version },
657       { NULL, NULL }
658     };
659
660   /* Simplified version check to make sure we can safely use the
661      VTABLE parameter. The FS loader does a more exhaustive check. */
662   if (loader_version->major != SVN_VER_MAJOR)
663     return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
664                              _("Unsupported FS loader version (%d) for fsx"),
665                              loader_version->major);
666   SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal));
667
668   SVN_ERR(svn_fs_x__batch_fsync_init(common_pool));
669
670   *vtable = &library_vtable;
671   return SVN_NO_ERROR;
672 }