fs.c revision 362181
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 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. */ 64static svn_error_t * 65x_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 140svn_error_t * 141svn_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 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. */ 157static svn_error_t * 158x_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 165typedef 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 171static svn_error_t * 172x_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 189static svn_error_t * 190x_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 200static svn_error_t * 201x_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 218static svn_error_t * 219x_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 233static svn_error_t * 234x_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. */ 243static svn_error_t * 244x_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. */ 261static svn_error_t * 262x_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. */ 273static svn_error_t * 274x_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 288 289/* The vtable associated with a specific open filesystem. */ 290static 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 NULL /* ioctl */ 315}; 316 317 318/* Creating a new filesystem. */ 319 320/* Set up vtable and fsap_data fields in FS. */ 321static svn_error_t * 322initialize_fs_struct(svn_fs_t *fs) 323{ 324 svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd)); 325 ffd->revprop_generation = -1; 326 ffd->flush_to_disk = TRUE; 327 328 fs->vtable = &fs_vtable; 329 fs->fsap_data = ffd; 330 return SVN_NO_ERROR; 331} 332 333/* Reset vtable and fsap_data fields in FS such that the FS is basically 334 * closed now. Note that FS must not hold locks when you call this. */ 335static void 336uninitialize_fs_struct(svn_fs_t *fs) 337{ 338 fs->vtable = NULL; 339 fs->fsap_data = NULL; 340} 341 342/* This implements the fs_library_vtable_t.create() API. Create a new 343 fsx-backed Subversion filesystem at path PATH and link it into 344 *FS. 345 346 Perform temporary allocations in SCRATCH_POOL, and fs-global allocations 347 in COMMON_POOL. The latter must be serialized using COMMON_POOL_LOCK. */ 348static svn_error_t * 349x_create(svn_fs_t *fs, 350 const char *path, 351 svn_mutex__t *common_pool_lock, 352 apr_pool_t *scratch_pool, 353 apr_pool_t *common_pool) 354{ 355 SVN_ERR(svn_fs__check_fs(fs, FALSE)); 356 357 SVN_ERR(initialize_fs_struct(fs)); 358 359 SVN_ERR(svn_fs_x__create(fs, path, scratch_pool)); 360 361 SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool)); 362 SVN_MUTEX__WITH_LOCK(common_pool_lock, 363 x_serialized_init(fs, common_pool, scratch_pool)); 364 365 return SVN_NO_ERROR; 366} 367 368 369 370/* Gaining access to an existing filesystem. */ 371 372/* This implements the fs_library_vtable_t.open() API. Open an FSX 373 Subversion filesystem located at PATH, set *FS to point to the 374 correct vtable for the filesystem. Use SCRATCH_POOL for any temporary 375 allocations, and COMMON_POOL for fs-global allocations. 376 The latter must be serialized using COMMON_POOL_LOCK. */ 377static svn_error_t * 378x_open(svn_fs_t *fs, 379 const char *path, 380 svn_mutex__t *common_pool_lock, 381 apr_pool_t *scratch_pool, 382 apr_pool_t *common_pool) 383{ 384 apr_pool_t *subpool = svn_pool_create(scratch_pool); 385 386 SVN_ERR(svn_fs__check_fs(fs, FALSE)); 387 388 SVN_ERR(initialize_fs_struct(fs)); 389 390 SVN_ERR(svn_fs_x__open(fs, path, subpool)); 391 392 SVN_ERR(svn_fs_x__initialize_caches(fs, subpool)); 393 SVN_MUTEX__WITH_LOCK(common_pool_lock, 394 x_serialized_init(fs, common_pool, subpool)); 395 396 svn_pool_destroy(subpool); 397 398 return SVN_NO_ERROR; 399} 400 401 402 403/* This implements the fs_library_vtable_t.open_for_recovery() API. */ 404static svn_error_t * 405x_open_for_recovery(svn_fs_t *fs, 406 const char *path, 407 svn_mutex__t *common_pool_lock, 408 apr_pool_t *scratch_pool, 409 apr_pool_t *common_pool) 410{ 411 svn_error_t * err; 412 svn_revnum_t youngest_rev; 413 apr_pool_t * subpool = svn_pool_create(scratch_pool); 414 415 /* Recovery for FSFS is currently limited to recreating the 'current' 416 file from the latest revision. */ 417 418 /* The only thing we have to watch out for is that the 'current' file 419 might not exist or contain garbage. So we'll try to read it here 420 and provide or replace the existing file if we couldn't read it. 421 (We'll also need it to exist later anyway as a source for the new 422 file's permissions). */ 423 424 /* Use a partly-filled fs pointer first to create 'current'. */ 425 fs->path = apr_pstrdup(fs->pool, path); 426 427 SVN_ERR(initialize_fs_struct(fs)); 428 429 /* Figure out the repo format and check that we can even handle it. */ 430 SVN_ERR(svn_fs_x__read_format_file(fs, subpool)); 431 432 /* Now, read 'current' and try to patch it if necessary. */ 433 err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool); 434 if (err) 435 { 436 const char *file_path; 437 438 /* 'current' file is missing or contains garbage. Since we are trying 439 * to recover from whatever problem there is, being picky about the 440 * error code here won't do us much good. If there is a persistent 441 * problem that we can't fix, it will show up when we try rewrite the 442 * file a few lines further below and we will report the failure back 443 * to the caller. 444 * 445 * Start recovery with HEAD = 0. */ 446 svn_error_clear(err); 447 file_path = svn_fs_x__path_current(fs, subpool); 448 449 /* Best effort to ensure the file exists and is valid. 450 * This may fail for r/o filesystems etc. */ 451 SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool)); 452 SVN_ERR(svn_io_file_create_empty(file_path, subpool)); 453 SVN_ERR(svn_fs_x__write_current(fs, 0, subpool)); 454 } 455 456 uninitialize_fs_struct(fs); 457 svn_pool_destroy(subpool); 458 459 /* Now open the filesystem properly by calling the vtable method directly. */ 460 return x_open(fs, path, common_pool_lock, scratch_pool, common_pool); 461} 462 463 464 465/* This implements the fs_library_vtable_t.upgrade_fs() API. */ 466static svn_error_t * 467x_upgrade(svn_fs_t *fs, 468 const char *path, 469 svn_fs_upgrade_notify_t notify_func, 470 void *notify_baton, 471 svn_cancel_func_t cancel_func, 472 void *cancel_baton, 473 svn_mutex__t *common_pool_lock, 474 apr_pool_t *scratch_pool, 475 apr_pool_t *common_pool) 476{ 477 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 478 return svn_fs_x__upgrade(fs, notify_func, notify_baton, 479 cancel_func, cancel_baton, scratch_pool); 480} 481 482static svn_error_t * 483x_verify(svn_fs_t *fs, 484 const char *path, 485 svn_revnum_t start, 486 svn_revnum_t end, 487 svn_fs_progress_notify_func_t notify_func, 488 void *notify_baton, 489 svn_cancel_func_t cancel_func, 490 void *cancel_baton, 491 svn_mutex__t *common_pool_lock, 492 apr_pool_t *scratch_pool, 493 apr_pool_t *common_pool) 494{ 495 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 496 return svn_fs_x__verify(fs, start, end, notify_func, notify_baton, 497 cancel_func, cancel_baton, scratch_pool); 498} 499 500static svn_error_t * 501x_pack(svn_fs_t *fs, 502 const char *path, 503 svn_fs_pack_notify_t notify_func, 504 void *notify_baton, 505 svn_cancel_func_t cancel_func, 506 void *cancel_baton, 507 svn_mutex__t *common_pool_lock, 508 apr_pool_t *scratch_pool, 509 apr_pool_t *common_pool) 510{ 511 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 512 return svn_fs_x__pack(fs, 0, notify_func, notify_baton, 513 cancel_func, cancel_baton, scratch_pool); 514} 515 516 517 518 519/* This implements the fs_library_vtable_t.hotcopy() API. Copy a 520 possibly live Subversion filesystem SRC_FS from SRC_PATH to a 521 DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to 522 re-copy data which already exists in DST_FS. 523 The CLEAN_LOGS argument is ignored and included for Subversion 524 1.0.x compatibility. The NOTIFY_FUNC and NOTIFY_BATON arguments 525 are also currently ignored. 526 Perform all temporary allocations in SCRATCH_POOL. */ 527static svn_error_t * 528x_hotcopy(svn_fs_t *src_fs, 529 svn_fs_t *dst_fs, 530 const char *src_path, 531 const char *dst_path, 532 svn_boolean_t clean_logs, 533 svn_boolean_t incremental, 534 svn_fs_hotcopy_notify_t notify_func, 535 void *notify_baton, 536 svn_cancel_func_t cancel_func, 537 void *cancel_baton, 538 svn_mutex__t *common_pool_lock, 539 apr_pool_t *scratch_pool, 540 apr_pool_t *common_pool) 541{ 542 /* Open the source repo as usual. */ 543 SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool, 544 common_pool)); 545 if (cancel_func) 546 SVN_ERR(cancel_func(cancel_baton)); 547 548 SVN_ERR(svn_fs__check_fs(dst_fs, FALSE)); 549 SVN_ERR(initialize_fs_struct(dst_fs)); 550 551 /* In INCREMENTAL mode, svn_fs_x__hotcopy() will open DST_FS. 552 Otherwise, it's not an FS yet --- possibly just an empty dir --- so 553 can't be opened. 554 */ 555 return svn_fs_x__hotcopy(src_fs, dst_fs, src_path, dst_path, 556 incremental, notify_func, notify_baton, 557 cancel_func, cancel_baton, common_pool_lock, 558 scratch_pool, common_pool); 559} 560 561 562 563/* This function is included for Subversion 1.0.x compatibility. It 564 has no effect for fsx backed Subversion filesystems. It conforms 565 to the fs_library_vtable_t.bdb_logfiles() API. */ 566static svn_error_t * 567x_logfiles(apr_array_header_t **logfiles, 568 const char *path, 569 svn_boolean_t only_unused, 570 apr_pool_t *pool) 571{ 572 /* A no-op for FSX. */ 573 *logfiles = apr_array_make(pool, 0, sizeof(const char *)); 574 575 return SVN_NO_ERROR; 576} 577 578 579 580 581 582/* Delete the filesystem located at path PATH. Perform any temporary 583 allocations in SCRATCH_POOL. */ 584static svn_error_t * 585x_delete_fs(const char *path, 586 apr_pool_t *scratch_pool) 587{ 588 /* Remove everything. */ 589 return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL, 590 scratch_pool)); 591} 592 593static const svn_version_t * 594x_version(void) 595{ 596 SVN_VERSION_BODY; 597} 598 599static const char * 600x_get_description(void) 601{ 602 return _("Module for working with an experimental (FSX) repository."); 603} 604 605static svn_error_t * 606x_set_svn_fs_open(svn_fs_t *fs, 607 svn_error_t *(*svn_fs_open_)(svn_fs_t **, 608 const char *, 609 apr_hash_t *, 610 apr_pool_t *, 611 apr_pool_t *)) 612{ 613 svn_fs_x__data_t *ffd = fs->fsap_data; 614 ffd->svn_fs_open_ = svn_fs_open_; 615 return SVN_NO_ERROR; 616} 617 618static void * 619x_info_dup(const void *fsx_info_void, 620 apr_pool_t *result_pool) 621{ 622 /* All fields are either ints or static strings. */ 623 const svn_fs_fsx_info_t *fsx_info = fsx_info_void; 624 return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info)); 625} 626 627 628/* Base FS library vtable, used by the FS loader library. */ 629 630static fs_library_vtable_t library_vtable = { 631 x_version, 632 x_create, 633 x_open, 634 x_open_for_recovery, 635 x_upgrade, 636 x_verify, 637 x_delete_fs, 638 x_hotcopy, 639 x_get_description, 640 svn_fs_x__recover, 641 x_pack, 642 x_logfiles, 643 NULL /* parse_id */, 644 x_set_svn_fs_open, 645 x_info_dup, 646 NULL /* ioctl */ 647}; 648 649svn_error_t * 650svn_fs_x__init(const svn_version_t *loader_version, 651 fs_library_vtable_t **vtable, 652 apr_pool_t* common_pool) 653{ 654 static const svn_version_checklist_t checklist[] = 655 { 656 { "svn_subr", svn_subr_version }, 657 { "svn_delta", svn_delta_version }, 658 { "svn_fs_util", svn_fs_util__version }, 659 { NULL, NULL } 660 }; 661 662 /* Simplified version check to make sure we can safely use the 663 VTABLE parameter. The FS loader does a more exhaustive check. */ 664 if (loader_version->major != SVN_VER_MAJOR) 665 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, 666 _("Unsupported FS loader version (%d) for fsx"), 667 loader_version->major); 668 SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal)); 669 670 SVN_ERR(svn_fs_x__batch_fsync_init(common_pool)); 671 672 *vtable = &library_vtable; 673 return SVN_NO_ERROR; 674} 675