]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs/fs-loader.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs / fs-loader.c
1 /*
2  * fs_loader.c:  Front-end to the various FS back ends
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24 \f
25 #include <string.h>
26 #include <apr.h>
27 #include <apr_atomic.h>
28 #include <apr_hash.h>
29 #include <apr_md5.h>
30 #include <apr_thread_mutex.h>
31 #include <apr_uuid.h>
32 #include <apr_strings.h>
33
34 #include "svn_private_config.h"
35 #include "svn_hash.h"
36 #include "svn_ctype.h"
37 #include "svn_types.h"
38 #include "svn_dso.h"
39 #include "svn_version.h"
40 #include "svn_fs.h"
41 #include "svn_path.h"
42 #include "svn_xml.h"
43 #include "svn_pools.h"
44 #include "svn_string.h"
45 #include "svn_sorts.h"
46
47 #include "private/svn_atomic.h"
48 #include "private/svn_fs_private.h"
49 #include "private/svn_fs_util.h"
50 #include "private/svn_utf_private.h"
51 #include "private/svn_mutex.h"
52 #include "private/svn_subr_private.h"
53
54 #include "fs-loader.h"
55
56 /* This is defined by configure on platforms which use configure, but
57    we need to define a fallback for Windows. */
58 #ifndef DEFAULT_FS_TYPE
59 #define DEFAULT_FS_TYPE "fsfs"
60 #endif
61
62 #define FS_TYPE_FILENAME "fs-type"
63
64 /* A pool common to all FS objects.  See the documentation on the
65    open/create functions in fs-loader.h and for svn_fs_initialize(). */
66 static apr_pool_t *common_pool = NULL;
67 static svn_mutex__t *common_pool_lock = NULL;
68 static svn_atomic_t common_pool_initialized = FALSE;
69
70 \f
71 /* --- Utility functions for the loader --- */
72
73 struct fs_type_defn {
74   const char *fs_type;
75   const char *fsap_name;
76   fs_init_func_t initfunc;
77   fs_library_vtable_t *vtable;
78   struct fs_type_defn *next;
79 };
80
81 static struct fs_type_defn base_defn =
82   {
83     SVN_FS_TYPE_BDB, "base",
84 #ifdef SVN_LIBSVN_FS_LINKS_FS_BASE
85     svn_fs_base__init,
86 #else
87     NULL,
88 #endif
89     NULL,
90     NULL /* End of static list: this needs to be reset to NULL if the
91             common_pool used when setting it has been cleared. */
92   };
93
94 static struct fs_type_defn fsx_defn =
95   {
96     SVN_FS_TYPE_FSX, "x",
97 #ifdef SVN_LIBSVN_FS_LINKS_FS_X
98     svn_fs_x__init,
99 #else
100     NULL,
101 #endif
102     NULL,
103     &base_defn
104   };
105
106 static struct fs_type_defn fsfs_defn =
107   {
108     SVN_FS_TYPE_FSFS, "fs",
109 #ifdef SVN_LIBSVN_FS_LINKS_FS_FS
110     svn_fs_fs__init,
111 #else
112     NULL,
113 #endif
114     NULL,
115     &fsx_defn
116   };
117
118 static struct fs_type_defn *fs_modules = &fsfs_defn;
119
120
121 static svn_error_t *
122 load_module(fs_init_func_t *initfunc, const char *name, apr_pool_t *pool)
123 {
124   *initfunc = NULL;
125
126 #if defined(SVN_USE_DSO) && APR_HAS_DSO
127   {
128     apr_dso_handle_t *dso;
129     apr_dso_handle_sym_t symbol;
130     const char *libname;
131     const char *funcname;
132     apr_status_t status;
133     const char *p;
134
135     /* Demand a simple alphanumeric name so that the generated DSO
136        name is sensible. */
137     for (p = name; *p; ++p)
138       if (!svn_ctype_isalnum(*p))
139         return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
140                                  _("Invalid name for FS type '%s'"),
141                                  name);
142
143     libname = apr_psprintf(pool, "libsvn_fs_%s-%d.so.%d",
144                            name, SVN_VER_MAJOR, SVN_SOVERSION);
145     funcname = apr_psprintf(pool, "svn_fs_%s__init", name);
146
147     /* Find/load the specified library.  If we get an error, assume
148        the library doesn't exist.  The library will be unloaded when
149        pool is destroyed. */
150     SVN_ERR(svn_dso_load(&dso, libname));
151     if (! dso)
152       return SVN_NO_ERROR;
153
154     /* find the initialization routine */
155     status = apr_dso_sym(&symbol, dso, funcname);
156     if (status)
157       return svn_error_wrap_apr(status, _("'%s' does not define '%s()'"),
158                                 libname, funcname);
159
160     *initfunc = (fs_init_func_t) symbol;
161   }
162 #endif /* APR_HAS_DSO */
163
164   return SVN_NO_ERROR;
165 }
166
167 /* Fetch a library vtable by a pointer into the library definitions array. */
168 static svn_error_t *
169 get_library_vtable_direct(fs_library_vtable_t **vtable,
170                           struct fs_type_defn *fst,
171                           apr_pool_t *pool)
172 {
173   fs_init_func_t initfunc = NULL;
174   const svn_version_t *my_version = svn_fs_version();
175   const svn_version_t *fs_version;
176
177   /* most times, we get lucky */
178   *vtable = apr_atomic_casptr((volatile void **)&fst->vtable, NULL, NULL);
179   if (*vtable)
180     return SVN_NO_ERROR;
181
182   /* o.k. the first access needs to actually load the module, find the
183      vtable and check for version compatibility. */
184   initfunc = fst->initfunc;
185   if (! initfunc)
186     SVN_ERR(load_module(&initfunc, fst->fsap_name, pool));
187
188   if (! initfunc)
189     return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
190                              _("Failed to load module for FS type '%s'"),
191                              fst->fs_type);
192
193   {
194     /* Per our API compatibility rules, we cannot ensure that
195        svn_fs_initialize is called by the application.  If not, we
196        cannot create the common pool and lock in a thread-safe fashion,
197        nor can we clean up the common pool if libsvn_fs is dynamically
198        unloaded.  This function makes a best effort by creating the
199        common pool as a child of the global pool; the window of failure
200        due to thread collision is small. */
201     SVN_ERR(svn_fs_initialize(NULL));
202
203     /* Invoke the FS module's initfunc function with the common
204        pool protected by a lock. */
205     SVN_MUTEX__WITH_LOCK(common_pool_lock,
206                          initfunc(my_version, vtable, common_pool));
207   }
208   fs_version = (*vtable)->get_version();
209   if (!svn_ver_equal(my_version, fs_version))
210     return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
211                              _("Mismatched FS module version for '%s':"
212                                " found %d.%d.%d%s,"
213                                " expected %d.%d.%d%s"),
214                              fst->fs_type,
215                              my_version->major, my_version->minor,
216                              my_version->patch, my_version->tag,
217                              fs_version->major, fs_version->minor,
218                              fs_version->patch, fs_version->tag);
219
220   /* the vtable will not change.  Remember it */
221   apr_atomic_casptr((volatile void **)&fst->vtable, *vtable, NULL);
222
223   return SVN_NO_ERROR;
224 }
225
226 #if defined(SVN_USE_DSO) && APR_HAS_DSO
227 /* Return *FST for the third party FS_TYPE */
228 static svn_error_t *
229 get_or_allocate_third(struct fs_type_defn **fst,
230                       const char *fs_type)
231 {
232   while (*fst)
233     {
234       if (strcmp(fs_type, (*fst)->fs_type) == 0)
235         return SVN_NO_ERROR;
236       fst = &(*fst)->next;
237     }
238
239   *fst = apr_palloc(common_pool, sizeof(struct fs_type_defn));
240   (*fst)->fs_type = apr_pstrdup(common_pool, fs_type);
241   (*fst)->fsap_name = (*fst)->fs_type;
242   (*fst)->initfunc = NULL;
243   (*fst)->vtable = NULL;
244   (*fst)->next = NULL;
245
246   return SVN_NO_ERROR;
247 }
248 #endif
249
250 /* Fetch a library *VTABLE by FS_TYPE.
251    Use POOL for temporary allocations. */
252 static svn_error_t *
253 get_library_vtable(fs_library_vtable_t **vtable, const char *fs_type,
254                    apr_pool_t *pool)
255 {
256   struct fs_type_defn **fst;
257   svn_boolean_t known = FALSE;
258
259   /* There are three FS module definitions known at compile time.  We
260      want to check these without any locking overhead even when
261      dynamic third party modules are enabled.  The third party modules
262      cannot be checked until the lock is held.  */
263   for (fst = &fs_modules; *fst; fst = &(*fst)->next)
264     {
265       if (strcmp(fs_type, (*fst)->fs_type) == 0)
266         {
267           known = TRUE;
268           break;
269         }
270       else if (!(*fst)->next)
271         {
272           break;
273         }
274     }
275
276 #if defined(SVN_USE_DSO) && APR_HAS_DSO
277   /* Third party FS modules that are unknown at compile time.
278
279      A third party FS is identified by the file fs-type containing a
280      third party name, say "foo".  The loader will load the DSO with
281      the name "libsvn_fs_foo" and use the entry point with the name
282      "svn_fs_foo__init".
283
284      Note: the BDB and FSFS modules don't follow this naming scheme
285      and this allows them to be used to test the third party loader.
286      Change the content of fs-type to "base" in a BDB filesystem or to
287      "fs" in an FSFS filesystem and they will be loaded as third party
288      modules. */
289   if (!known)
290     {
291       fst = &(*fst)->next;
292       /* Best-effort init, see get_library_vtable_direct. */
293       SVN_ERR(svn_fs_initialize(NULL));
294       SVN_MUTEX__WITH_LOCK(common_pool_lock,
295                            get_or_allocate_third(fst, fs_type));
296       known = TRUE;
297     }
298 #endif
299   if (!known)
300     return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
301                              _("Unknown FS type '%s'"), fs_type);
302   return get_library_vtable_direct(vtable, *fst, pool);
303 }
304
305 svn_error_t *
306 svn_fs_type(const char **fs_type, const char *path, apr_pool_t *pool)
307 {
308   const char *filename;
309   char buf[128];
310   svn_error_t *err;
311   apr_file_t *file;
312   apr_size_t len;
313
314   /* Read the fsap-name file to get the FSAP name, or assume the (old)
315      default.  For old repositories I suppose we could check some
316      other file, DB_CONFIG or strings say, but for now just check the
317      directory exists. */
318   filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool);
319   err = svn_io_file_open(&file, filename, APR_READ|APR_BUFFERED, 0, pool);
320   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
321     {
322       svn_node_kind_t kind;
323       svn_error_t *err2 = svn_io_check_path(path, &kind, pool);
324       if (err2)
325         {
326           svn_error_clear(err2);
327           return err;
328         }
329       if (kind == svn_node_dir)
330         {
331           svn_error_clear(err);
332           *fs_type = SVN_FS_TYPE_BDB;
333           return SVN_NO_ERROR;
334         }
335       return err;
336     }
337   else if (err)
338     return err;
339
340   len = sizeof(buf);
341   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
342   SVN_ERR(svn_io_file_close(file, pool));
343   *fs_type = apr_pstrdup(pool, buf);
344
345   return SVN_NO_ERROR;
346 }
347
348 /* Fetch the library vtable for an existing FS. */
349 static svn_error_t *
350 fs_library_vtable(fs_library_vtable_t **vtable, const char *path,
351                   apr_pool_t *pool)
352 {
353   const char *fs_type;
354
355   SVN_ERR(svn_fs_type(&fs_type, path, pool));
356
357   /* Fetch the library vtable by name, now that we've chosen one. */
358   SVN_ERR(get_library_vtable(vtable, fs_type, pool));
359
360   return SVN_NO_ERROR;
361 }
362
363 static svn_error_t *
364 write_fs_type(const char *path, const char *fs_type, apr_pool_t *pool)
365 {
366   const char *filename;
367   apr_file_t *file;
368
369   filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool);
370   SVN_ERR(svn_io_file_open(&file, filename,
371                            APR_WRITE|APR_CREATE|APR_TRUNCATE|APR_BUFFERED,
372                            APR_OS_DEFAULT, pool));
373   SVN_ERR(svn_io_file_write_full(file, fs_type, strlen(fs_type), NULL,
374                                  pool));
375   SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool));
376   return svn_error_trace(svn_io_file_close(file, pool));
377 }
378
379 \f
380 /* --- Functions for operating on filesystems by pathname --- */
381
382 static apr_status_t uninit(void *data)
383 {
384   common_pool = NULL;
385   common_pool_lock = NULL;
386   common_pool_initialized = 0;
387
388   return APR_SUCCESS;
389 }
390
391 static svn_error_t *
392 synchronized_initialize(void *baton, apr_pool_t *pool)
393 {
394   common_pool = svn_pool_create(pool);
395   base_defn.next = NULL;
396   SVN_ERR(svn_mutex__init(&common_pool_lock, TRUE, common_pool));
397
398   /* ### This won't work if POOL is NULL and libsvn_fs is loaded as a DSO
399      ### (via libsvn_ra_local say) since the global common_pool will live
400      ### longer than the DSO, which gets unloaded when the pool used to
401      ### load it is cleared, and so when the handler runs it will refer to
402      ### a function that no longer exists.  libsvn_ra_local attempts to
403      ### work around this by explicitly calling svn_fs_initialize. */
404   apr_pool_cleanup_register(common_pool, NULL, uninit, apr_pool_cleanup_null);
405   return SVN_NO_ERROR;
406 }
407
408 svn_error_t *
409 svn_fs_initialize(apr_pool_t *pool)
410 {
411 #if defined(SVN_USE_DSO) && APR_HAS_DSO
412   /* Ensure that DSO subsystem is initialized early as possible if
413      we're going to use it. */
414   SVN_ERR(svn_dso_initialize2());
415 #endif
416   /* Protect against multiple calls. */
417   return svn_error_trace(svn_atomic__init_once(&common_pool_initialized,
418                                                synchronized_initialize,
419                                                NULL, pool));
420 }
421
422 /* A default warning handling function.  */
423 static void
424 default_warning_func(void *baton, svn_error_t *err)
425 {
426   /* The one unforgiveable sin is to fail silently.  Dumping to stderr
427      or /dev/tty is not acceptable default behavior for server
428      processes, since those may both be equivalent to /dev/null.
429
430      That said, be a good citizen and print something anyway, in case it goes
431      somewhere, and our caller hasn't overridden the abort() call.
432    */
433   if (svn_error_get_malfunction_handler()
434       == svn_error_abort_on_malfunction)
435     /* ### TODO: extend the malfunction API such that non-abort()ing consumers
436        ### also get the information on ERR. */
437     svn_handle_error2(err, stderr, FALSE /* fatal */, "svn: fs-loader: ");
438   SVN_ERR_MALFUNCTION_NO_RETURN();
439 }
440
441 svn_error_t *
442 svn_fs__path_valid(const char *path, apr_pool_t *pool)
443 {
444   char *c;
445
446   /* UTF-8 encoded string without NULs. */
447   if (! svn_utf__cstring_is_valid(path))
448     {
449       return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
450                                _("Path '%s' is not in UTF-8"), path);
451     }
452
453   /* No "." or ".." elements. */
454   if (svn_path_is_backpath_present(path)
455       || svn_path_is_dotpath_present(path))
456     {
457       return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
458                                _("Path '%s' contains '.' or '..' element"),
459                                path);
460     }
461
462   /* Raise an error if PATH contains a newline because svn:mergeinfo and
463      friends can't handle them.  Issue #4340 describes a similar problem
464      in the FSFS code itself.
465    */
466   c = strchr(path, '\n');
467   if (c)
468     {
469       return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
470                _("Invalid control character '0x%02x' in path '%s'"),
471                (unsigned char)*c, svn_path_illegal_path_escape(path, pool));
472     }
473
474   /* That's good enough. */
475   return SVN_NO_ERROR;
476 }
477
478 /* Allocate svn_fs_t structure. */
479 static svn_fs_t *
480 fs_new(apr_hash_t *fs_config, apr_pool_t *pool)
481 {
482   svn_fs_t *fs = apr_palloc(pool, sizeof(*fs));
483   fs->pool = pool;
484   fs->path = NULL;
485   fs->warning = default_warning_func;
486   fs->warning_baton = NULL;
487   fs->config = fs_config;
488   fs->access_ctx = NULL;
489   fs->vtable = NULL;
490   fs->fsap_data = NULL;
491   fs->uuid = NULL;
492   return fs;
493 }
494
495 svn_fs_t *
496 svn_fs_new(apr_hash_t *fs_config, apr_pool_t *pool)
497 {
498   return fs_new(fs_config, pool);
499 }
500
501 void
502 svn_fs_set_warning_func(svn_fs_t *fs, svn_fs_warning_callback_t warning,
503                         void *warning_baton)
504 {
505   fs->warning = warning;
506   fs->warning_baton = warning_baton;
507 }
508
509 svn_error_t *
510 svn_fs_create(svn_fs_t **fs_p, const char *path, apr_hash_t *fs_config,
511               apr_pool_t *pool)
512 {
513   fs_library_vtable_t *vtable;
514
515   const char *fs_type = svn_hash__get_cstring(fs_config,
516                                               SVN_FS_CONFIG_FS_TYPE,
517                                               DEFAULT_FS_TYPE);
518   SVN_ERR(get_library_vtable(&vtable, fs_type, pool));
519
520   /* Create the FS directory and write out the fsap-name file. */
521   SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, pool));
522   SVN_ERR(write_fs_type(path, fs_type, pool));
523
524   /* Perform the actual creation. */
525   *fs_p = fs_new(fs_config, pool);
526
527   SVN_ERR(vtable->create(*fs_p, path, common_pool_lock, pool, common_pool));
528   SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open2));
529
530   return SVN_NO_ERROR;
531 }
532
533 svn_error_t *
534 svn_fs_open2(svn_fs_t **fs_p, const char *path, apr_hash_t *fs_config,
535              apr_pool_t *result_pool,
536              apr_pool_t *scratch_pool)
537 {
538   fs_library_vtable_t *vtable;
539
540   SVN_ERR(fs_library_vtable(&vtable, path, scratch_pool));
541   *fs_p = fs_new(fs_config, result_pool);
542   SVN_ERR(vtable->open_fs(*fs_p, path, common_pool_lock, scratch_pool,
543                           common_pool));
544   SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open2));
545
546   return SVN_NO_ERROR;
547 }
548
549 svn_error_t *
550 svn_fs_open(svn_fs_t **fs_p,
551             const char *path,
552             apr_hash_t *fs_config,
553             apr_pool_t *pool)
554 {
555   return svn_fs_open2(fs_p, path, fs_config, pool, pool);
556 }
557
558 svn_error_t *
559 svn_fs_upgrade2(const char *path,
560                 svn_fs_upgrade_notify_t notify_func,
561                 void *notify_baton,
562                 svn_cancel_func_t cancel_func,
563                 void *cancel_baton,
564                 apr_pool_t *scratch_pool)
565 {
566   fs_library_vtable_t *vtable;
567   svn_fs_t *fs;
568
569   SVN_ERR(fs_library_vtable(&vtable, path, scratch_pool));
570   fs = fs_new(NULL, scratch_pool);
571
572   SVN_ERR(vtable->upgrade_fs(fs, path,
573                              notify_func, notify_baton,
574                              cancel_func, cancel_baton,
575                              common_pool_lock,
576                              scratch_pool, common_pool));
577   return SVN_NO_ERROR;
578 }
579
580 /* A warning handling function that does not abort on errors,
581    but just lets them be returned normally.  */
582 static void
583 verify_fs_warning_func(void *baton, svn_error_t *err)
584 {
585 }
586
587 svn_error_t *
588 svn_fs_verify(const char *path,
589               apr_hash_t *fs_config,
590               svn_revnum_t start,
591               svn_revnum_t end,
592               svn_fs_progress_notify_func_t notify_func,
593               void *notify_baton,
594               svn_cancel_func_t cancel_func,
595               void *cancel_baton,
596               apr_pool_t *pool)
597 {
598   fs_library_vtable_t *vtable;
599   svn_fs_t *fs;
600
601   SVN_ERR(fs_library_vtable(&vtable, path, pool));
602   fs = fs_new(fs_config, pool);
603   svn_fs_set_warning_func(fs, verify_fs_warning_func, NULL);
604
605   SVN_ERR(vtable->verify_fs(fs, path, start, end,
606                             notify_func, notify_baton,
607                             cancel_func, cancel_baton,
608                             common_pool_lock,
609                             pool, common_pool));
610   return SVN_NO_ERROR;
611 }
612
613 const char *
614 svn_fs_path(svn_fs_t *fs, apr_pool_t *pool)
615 {
616   return apr_pstrdup(pool, fs->path);
617 }
618
619 apr_hash_t *
620 svn_fs_config(svn_fs_t *fs, apr_pool_t *pool)
621 {
622   if (fs->config)
623     return apr_hash_copy(pool, fs->config);
624
625   return NULL;
626 }
627
628 svn_error_t *
629 svn_fs_delete_fs(const char *path, apr_pool_t *pool)
630 {
631   fs_library_vtable_t *vtable;
632
633   SVN_ERR(fs_library_vtable(&vtable, path, pool));
634   return svn_error_trace(vtable->delete_fs(path, pool));
635 }
636
637 svn_error_t *
638 svn_fs_hotcopy3(const char *src_path, const char *dst_path,
639                 svn_boolean_t clean, svn_boolean_t incremental,
640                 svn_fs_hotcopy_notify_t notify_func,
641                 void *notify_baton,
642                 svn_cancel_func_t cancel_func,
643                 void *cancel_baton,
644                 apr_pool_t *scratch_pool)
645 {
646   fs_library_vtable_t *vtable;
647   const char *src_fs_type;
648   svn_fs_t *src_fs;
649   svn_fs_t *dst_fs;
650   const char *dst_fs_type;
651   svn_node_kind_t dst_kind;
652
653   if (strcmp(src_path, dst_path) == 0)
654     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
655                              _("Hotcopy source and destination are equal"));
656
657   SVN_ERR(svn_fs_type(&src_fs_type, src_path, scratch_pool));
658   SVN_ERR(get_library_vtable(&vtable, src_fs_type, scratch_pool));
659   src_fs = fs_new(NULL, scratch_pool);
660   dst_fs = fs_new(NULL, scratch_pool);
661
662   SVN_ERR(svn_io_check_path(dst_path, &dst_kind, scratch_pool));
663   if (dst_kind == svn_node_file)
664     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
665                              _("'%s' already exists and is a file"),
666                              svn_dirent_local_style(dst_path,
667                                                     scratch_pool));
668   if (dst_kind == svn_node_unknown)
669     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
670                              _("'%s' already exists and has an unknown "
671                                "node kind"),
672                              svn_dirent_local_style(dst_path,
673                                                     scratch_pool));
674   if (dst_kind == svn_node_dir)
675     {
676       svn_node_kind_t type_file_kind;
677
678       SVN_ERR(svn_io_check_path(svn_dirent_join(dst_path,
679                                                 FS_TYPE_FILENAME,
680                                                 scratch_pool),
681                                 &type_file_kind, scratch_pool));
682       if (type_file_kind != svn_node_none)
683         {
684           SVN_ERR(svn_fs_type(&dst_fs_type, dst_path, scratch_pool));
685           if (strcmp(src_fs_type, dst_fs_type) != 0)
686             return svn_error_createf(
687                      SVN_ERR_ILLEGAL_TARGET, NULL,
688                      _("The filesystem type of the hotcopy source "
689                        "('%s') does not match the filesystem "
690                        "type of the hotcopy destination ('%s')"),
691                      src_fs_type, dst_fs_type);
692         }
693     }
694
695   SVN_ERR(vtable->hotcopy(src_fs, dst_fs, src_path, dst_path, clean,
696                           incremental, notify_func, notify_baton,
697                           cancel_func, cancel_baton, common_pool_lock,
698                           scratch_pool, common_pool));
699   return svn_error_trace(write_fs_type(dst_path, src_fs_type, scratch_pool));
700 }
701
702 svn_error_t *
703 svn_fs_pack(const char *path,
704             svn_fs_pack_notify_t notify_func,
705             void *notify_baton,
706             svn_cancel_func_t cancel_func,
707             void *cancel_baton,
708             apr_pool_t *pool)
709 {
710   fs_library_vtable_t *vtable;
711   svn_fs_t *fs;
712
713   SVN_ERR(fs_library_vtable(&vtable, path, pool));
714   fs = fs_new(NULL, pool);
715
716   SVN_ERR(vtable->pack_fs(fs, path, notify_func, notify_baton,
717                           cancel_func, cancel_baton, common_pool_lock,
718                           pool, common_pool));
719   return SVN_NO_ERROR;
720 }
721
722 svn_error_t *
723 svn_fs_recover(const char *path,
724                svn_cancel_func_t cancel_func, void *cancel_baton,
725                apr_pool_t *pool)
726 {
727   fs_library_vtable_t *vtable;
728   svn_fs_t *fs;
729
730   SVN_ERR(fs_library_vtable(&vtable, path, pool));
731   fs = fs_new(NULL, pool);
732
733   SVN_ERR(vtable->open_fs_for_recovery(fs, path, common_pool_lock,
734                                        pool, common_pool));
735   return svn_error_trace(vtable->recover(fs, cancel_func, cancel_baton,
736                                          pool));
737 }
738
739 svn_error_t *
740 svn_fs_verify_root(svn_fs_root_t *root,
741                    apr_pool_t *scratch_pool)
742 {
743   svn_fs_t *fs = root->fs;
744   SVN_ERR(fs->vtable->verify_root(root, scratch_pool));
745
746   return SVN_NO_ERROR;
747 }
748
749 svn_error_t *
750 svn_fs_freeze(svn_fs_t *fs,
751               svn_fs_freeze_func_t freeze_func,
752               void *freeze_baton,
753               apr_pool_t *pool)
754 {
755   SVN_ERR(fs->vtable->freeze(fs, freeze_func, freeze_baton, pool));
756
757   return SVN_NO_ERROR;
758 }
759
760 \f
761 /* --- Berkeley-specific functions --- */
762
763 svn_error_t *
764 svn_fs_create_berkeley(svn_fs_t *fs, const char *path)
765 {
766   fs_library_vtable_t *vtable;
767
768   SVN_ERR(get_library_vtable(&vtable, SVN_FS_TYPE_BDB, fs->pool));
769
770   /* Create the FS directory and write out the fsap-name file. */
771   SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, fs->pool));
772   SVN_ERR(write_fs_type(path, SVN_FS_TYPE_BDB, fs->pool));
773
774   /* Perform the actual creation. */
775   SVN_ERR(vtable->create(fs, path, common_pool_lock, fs->pool, common_pool));
776   SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open2));
777
778   return SVN_NO_ERROR;
779 }
780
781 svn_error_t *
782 svn_fs_open_berkeley(svn_fs_t *fs, const char *path)
783 {
784   fs_library_vtable_t *vtable;
785
786   SVN_ERR(fs_library_vtable(&vtable, path, fs->pool));
787   SVN_ERR(vtable->open_fs(fs, path, common_pool_lock, fs->pool, common_pool));
788   SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open2));
789
790   return SVN_NO_ERROR;
791 }
792
793 const char *
794 svn_fs_berkeley_path(svn_fs_t *fs, apr_pool_t *pool)
795 {
796   return svn_fs_path(fs, pool);
797 }
798
799 svn_error_t *
800 svn_fs_delete_berkeley(const char *path, apr_pool_t *pool)
801 {
802   return svn_error_trace(svn_fs_delete_fs(path, pool));
803 }
804
805 svn_error_t *
806 svn_fs_hotcopy_berkeley(const char *src_path, const char *dest_path,
807                         svn_boolean_t clean_logs, apr_pool_t *pool)
808 {
809   return svn_error_trace(svn_fs_hotcopy3(src_path, dest_path, clean_logs,
810                                          FALSE, NULL, NULL, NULL, NULL,
811                                          pool));
812 }
813
814 svn_error_t *
815 svn_fs_berkeley_recover(const char *path, apr_pool_t *pool)
816 {
817   return svn_error_trace(svn_fs_recover(path, NULL, NULL, pool));
818 }
819
820 svn_error_t *
821 svn_fs_set_berkeley_errcall(svn_fs_t *fs,
822                             void (*handler)(const char *errpfx, char *msg))
823 {
824   return svn_error_trace(fs->vtable->bdb_set_errcall(fs, handler));
825 }
826
827 svn_error_t *
828 svn_fs_berkeley_logfiles(apr_array_header_t **logfiles,
829                          const char *path,
830                          svn_boolean_t only_unused,
831                          apr_pool_t *pool)
832 {
833   fs_library_vtable_t *vtable;
834
835   SVN_ERR(fs_library_vtable(&vtable, path, pool));
836   return svn_error_trace(vtable->bdb_logfiles(logfiles, path, only_unused,
837                                               pool));
838 }
839
840 \f
841 /* --- Transaction functions --- */
842
843 svn_error_t *
844 svn_fs_begin_txn2(svn_fs_txn_t **txn_p, svn_fs_t *fs, svn_revnum_t rev,
845                   apr_uint32_t flags, apr_pool_t *pool)
846 {
847   return svn_error_trace(fs->vtable->begin_txn(txn_p, fs, rev, flags, pool));
848 }
849
850
851 svn_error_t *
852 svn_fs_commit_txn(const char **conflict_p, svn_revnum_t *new_rev,
853                    svn_fs_txn_t *txn, apr_pool_t *pool)
854 {
855   svn_error_t *err;
856
857   *new_rev = SVN_INVALID_REVNUM;
858   if (conflict_p)
859     *conflict_p = NULL;
860
861   err = txn->vtable->commit(conflict_p, new_rev, txn, pool);
862
863 #ifdef SVN_DEBUG
864   /* Check postconditions. */
865   if (conflict_p)
866     {
867       SVN_ERR_ASSERT_E(! (SVN_IS_VALID_REVNUM(*new_rev) && *conflict_p != NULL),
868                        err);
869       SVN_ERR_ASSERT_E((*conflict_p != NULL)
870                        == (err && err->apr_err == SVN_ERR_FS_CONFLICT),
871                        err);
872     }
873 #endif
874
875   SVN_ERR(err);
876
877   return SVN_NO_ERROR;
878 }
879
880 svn_error_t *
881 svn_fs_abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool)
882 {
883   return svn_error_trace(txn->vtable->abort(txn, pool));
884 }
885
886 svn_error_t *
887 svn_fs_purge_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
888 {
889   return svn_error_trace(fs->vtable->purge_txn(fs, txn_id, pool));
890 }
891
892 svn_error_t *
893 svn_fs_txn_name(const char **name_p, svn_fs_txn_t *txn, apr_pool_t *pool)
894 {
895   *name_p = apr_pstrdup(pool, txn->id);
896   return SVN_NO_ERROR;
897 }
898
899 svn_revnum_t
900 svn_fs_txn_base_revision(svn_fs_txn_t *txn)
901 {
902   return txn->base_rev;
903 }
904
905 svn_error_t *
906 svn_fs_open_txn(svn_fs_txn_t **txn, svn_fs_t *fs, const char *name,
907                 apr_pool_t *pool)
908 {
909   return svn_error_trace(fs->vtable->open_txn(txn, fs, name, pool));
910 }
911
912 svn_error_t *
913 svn_fs_list_transactions(apr_array_header_t **names_p, svn_fs_t *fs,
914                          apr_pool_t *pool)
915 {
916   return svn_error_trace(fs->vtable->list_transactions(names_p, fs, pool));
917 }
918
919 static svn_boolean_t
920 is_internal_txn_prop(const char *name)
921 {
922   return strcmp(name, SVN_FS__PROP_TXN_CHECK_LOCKS) == 0 ||
923          strcmp(name, SVN_FS__PROP_TXN_CHECK_OOD) == 0 ||
924          strcmp(name, SVN_FS__PROP_TXN_CLIENT_DATE) == 0;
925 }
926
927 svn_error_t *
928 svn_fs_txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
929                 const char *propname, apr_pool_t *pool)
930 {
931   if (is_internal_txn_prop(propname))
932     {
933       *value_p = NULL;
934       return SVN_NO_ERROR;
935     }
936
937   return svn_error_trace(txn->vtable->get_prop(value_p, txn, propname, pool));
938 }
939
940 svn_error_t *
941 svn_fs_txn_proplist(apr_hash_t **table_p, svn_fs_txn_t *txn, apr_pool_t *pool)
942 {
943   SVN_ERR(txn->vtable->get_proplist(table_p, txn, pool));
944
945   /* Don't give away internal transaction properties. */
946   svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
947   svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
948   svn_hash_sets(*table_p, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
949
950   return SVN_NO_ERROR;
951 }
952
953 svn_error_t *
954 svn_fs_change_txn_prop(svn_fs_txn_t *txn, const char *name,
955                        const svn_string_t *value, apr_pool_t *pool)
956 {
957   if (is_internal_txn_prop(name))
958     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
959                              _("Attempt to modify internal transaction "
960                                "property '%s'"), name);
961
962   return svn_error_trace(txn->vtable->change_prop(txn, name, value, pool));
963 }
964
965 svn_error_t *
966 svn_fs_change_txn_props(svn_fs_txn_t *txn, const apr_array_header_t *props,
967                         apr_pool_t *pool)
968 {
969   int i;
970
971   for (i = 0; i < props->nelts; ++i)
972     {
973       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
974
975       if (is_internal_txn_prop(prop->name))
976         return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
977                                  _("Attempt to modify internal transaction "
978                                    "property '%s'"), prop->name);
979     }
980
981   return svn_error_trace(txn->vtable->change_props(txn, props, pool));
982 }
983
984 \f
985 /* --- Root functions --- */
986
987 svn_error_t *
988 svn_fs_revision_root(svn_fs_root_t **root_p, svn_fs_t *fs, svn_revnum_t rev,
989                      apr_pool_t *pool)
990 {
991   /* We create a subpool for each root object to allow us to implement
992      svn_fs_close_root.  */
993   apr_pool_t *subpool = svn_pool_create(pool);
994   return svn_error_trace(fs->vtable->revision_root(root_p, fs, rev, subpool));
995 }
996
997 svn_error_t *
998 svn_fs_txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn, apr_pool_t *pool)
999 {
1000   /* We create a subpool for each root object to allow us to implement
1001      svn_fs_close_root.  */
1002   apr_pool_t *subpool = svn_pool_create(pool);
1003   return svn_error_trace(txn->vtable->root(root_p, txn, subpool));
1004 }
1005
1006 void
1007 svn_fs_close_root(svn_fs_root_t *root)
1008 {
1009   svn_pool_destroy(root->pool);
1010 }
1011
1012 svn_fs_t *
1013 svn_fs_root_fs(svn_fs_root_t *root)
1014 {
1015   return root->fs;
1016 }
1017
1018 svn_boolean_t
1019 svn_fs_is_txn_root(svn_fs_root_t *root)
1020 {
1021   return root->is_txn_root;
1022 }
1023
1024 svn_boolean_t
1025 svn_fs_is_revision_root(svn_fs_root_t *root)
1026 {
1027   return !root->is_txn_root;
1028 }
1029
1030 const char *
1031 svn_fs_txn_root_name(svn_fs_root_t *root, apr_pool_t *pool)
1032 {
1033   return root->is_txn_root ? apr_pstrdup(pool, root->txn) : NULL;
1034 }
1035
1036 svn_revnum_t
1037 svn_fs_txn_root_base_revision(svn_fs_root_t *root)
1038 {
1039   return root->is_txn_root ? root->rev : SVN_INVALID_REVNUM;
1040 }
1041
1042 svn_revnum_t
1043 svn_fs_revision_root_revision(svn_fs_root_t *root)
1044 {
1045   return root->is_txn_root ? SVN_INVALID_REVNUM : root->rev;
1046 }
1047
1048 svn_error_t *
1049 svn_fs_paths_changed2(apr_hash_t **changed_paths_p,
1050                       svn_fs_root_t *root,
1051                       apr_pool_t *pool)
1052 {
1053   return root->vtable->paths_changed(changed_paths_p, root, pool);
1054 }
1055
1056 svn_error_t *
1057 svn_fs_paths_changed(apr_hash_t **changed_paths_p, svn_fs_root_t *root,
1058                      apr_pool_t *pool)
1059 {
1060   apr_hash_t *changed_paths_new_structs;
1061   apr_hash_index_t *hi;
1062
1063   SVN_ERR(svn_fs_paths_changed2(&changed_paths_new_structs, root, pool));
1064   *changed_paths_p = apr_hash_make(pool);
1065   for (hi = apr_hash_first(pool, changed_paths_new_structs);
1066        hi;
1067        hi = apr_hash_next(hi))
1068     {
1069       const void *vkey;
1070       apr_ssize_t klen;
1071       void *vval;
1072       svn_fs_path_change2_t *val;
1073       svn_fs_path_change_t *change;
1074       apr_hash_this(hi, &vkey, &klen, &vval);
1075       val = vval;
1076       change = apr_palloc(pool, sizeof(*change));
1077       change->node_rev_id = val->node_rev_id;
1078       change->change_kind = val->change_kind;
1079       change->text_mod = val->text_mod;
1080       change->prop_mod = val->prop_mod;
1081       apr_hash_set(*changed_paths_p, vkey, klen, change);
1082     }
1083   return SVN_NO_ERROR;
1084 }
1085
1086 svn_error_t *
1087 svn_fs_check_path(svn_node_kind_t *kind_p, svn_fs_root_t *root,
1088                   const char *path, apr_pool_t *pool)
1089 {
1090   return svn_error_trace(root->vtable->check_path(kind_p, root, path, pool));
1091 }
1092
1093 svn_error_t *
1094 svn_fs_node_history2(svn_fs_history_t **history_p, svn_fs_root_t *root,
1095                      const char *path, apr_pool_t *result_pool,
1096                      apr_pool_t *scratch_pool)
1097 {
1098   return svn_error_trace(root->vtable->node_history(history_p, root, path,
1099                                                     result_pool,
1100                                                     scratch_pool));
1101 }
1102
1103 svn_error_t *
1104 svn_fs_node_history(svn_fs_history_t **history_p, svn_fs_root_t *root,
1105                     const char *path, apr_pool_t *pool)
1106 {
1107   return svn_error_trace(root->vtable->node_history(history_p, root, path,
1108                                                     pool, pool));
1109 }
1110
1111 svn_error_t *
1112 svn_fs_is_dir(svn_boolean_t *is_dir, svn_fs_root_t *root, const char *path,
1113               apr_pool_t *pool)
1114 {
1115   svn_node_kind_t kind;
1116
1117   SVN_ERR(root->vtable->check_path(&kind, root, path, pool));
1118   *is_dir = (kind == svn_node_dir);
1119   return SVN_NO_ERROR;
1120 }
1121
1122 svn_error_t *
1123 svn_fs_is_file(svn_boolean_t *is_file, svn_fs_root_t *root, const char *path,
1124                apr_pool_t *pool)
1125 {
1126   svn_node_kind_t kind;
1127
1128   SVN_ERR(root->vtable->check_path(&kind, root, path, pool));
1129   *is_file = (kind == svn_node_file);
1130   return SVN_NO_ERROR;
1131 }
1132
1133 svn_error_t *
1134 svn_fs_node_id(const svn_fs_id_t **id_p, svn_fs_root_t *root,
1135                const char *path, apr_pool_t *pool)
1136 {
1137   return svn_error_trace(root->vtable->node_id(id_p, root, path, pool));
1138 }
1139
1140 svn_error_t *
1141 svn_fs_node_relation(svn_fs_node_relation_t *relation,
1142                      svn_fs_root_t *root_a, const char *path_a,
1143                      svn_fs_root_t *root_b, const char *path_b,
1144                      apr_pool_t *scratch_pool)
1145 {
1146   /* Different repository types? */
1147   if (root_a->fs != root_b->fs)
1148     {
1149       *relation = svn_fs_node_unrelated;
1150       return SVN_NO_ERROR;
1151     }
1152
1153   return svn_error_trace(root_a->vtable->node_relation(relation,
1154                                                        root_a, path_a,
1155                                                        root_b, path_b,
1156                                                        scratch_pool));
1157 }
1158
1159 svn_error_t *
1160 svn_fs_node_created_rev(svn_revnum_t *revision, svn_fs_root_t *root,
1161                         const char *path, apr_pool_t *pool)
1162 {
1163   return svn_error_trace(root->vtable->node_created_rev(revision, root, path,
1164                                                         pool));
1165 }
1166
1167 svn_error_t *
1168 svn_fs_node_origin_rev(svn_revnum_t *revision, svn_fs_root_t *root,
1169                        const char *path, apr_pool_t *pool)
1170 {
1171   return svn_error_trace(root->vtable->node_origin_rev(revision, root, path,
1172                                                        pool));
1173 }
1174
1175 svn_error_t *
1176 svn_fs_node_created_path(const char **created_path, svn_fs_root_t *root,
1177                          const char *path, apr_pool_t *pool)
1178 {
1179   return svn_error_trace(root->vtable->node_created_path(created_path, root,
1180                                                          path, pool));
1181 }
1182
1183 svn_error_t *
1184 svn_fs_node_prop(svn_string_t **value_p, svn_fs_root_t *root,
1185                  const char *path, const char *propname, apr_pool_t *pool)
1186 {
1187   return svn_error_trace(root->vtable->node_prop(value_p, root, path,
1188                                                  propname, pool));
1189 }
1190
1191 svn_error_t *
1192 svn_fs_node_proplist(apr_hash_t **table_p, svn_fs_root_t *root,
1193                      const char *path, apr_pool_t *pool)
1194 {
1195   return svn_error_trace(root->vtable->node_proplist(table_p, root, path,
1196                                                      pool));
1197 }
1198
1199 svn_error_t *
1200 svn_fs_node_has_props(svn_boolean_t *has_props,
1201                       svn_fs_root_t *root,
1202                       const char *path,
1203                       apr_pool_t *scratch_pool)
1204 {
1205   return svn_error_trace(root->vtable->node_has_props(has_props, root, path,
1206                                                       scratch_pool));
1207 }
1208
1209 svn_error_t *
1210 svn_fs_change_node_prop(svn_fs_root_t *root, const char *path,
1211                         const char *name, const svn_string_t *value,
1212                         apr_pool_t *pool)
1213 {
1214   return svn_error_trace(root->vtable->change_node_prop(root, path, name,
1215                                                         value, pool));
1216 }
1217
1218 svn_error_t *
1219 svn_fs_props_different(svn_boolean_t *changed_p, svn_fs_root_t *root1,
1220                        const char *path1, svn_fs_root_t *root2,
1221                        const char *path2, apr_pool_t *scratch_pool)
1222 {
1223   return svn_error_trace(root1->vtable->props_changed(changed_p,
1224                                                       root1, path1,
1225                                                       root2, path2,
1226                                                       TRUE, scratch_pool));
1227 }
1228
1229 svn_error_t *
1230 svn_fs_props_changed(svn_boolean_t *changed_p, svn_fs_root_t *root1,
1231                      const char *path1, svn_fs_root_t *root2,
1232                      const char *path2, apr_pool_t *pool)
1233 {
1234   return svn_error_trace(root1->vtable->props_changed(changed_p,
1235                                                       root1, path1,
1236                                                       root2, path2,
1237                                                       FALSE, pool));
1238 }
1239
1240 svn_error_t *
1241 svn_fs_copied_from(svn_revnum_t *rev_p, const char **path_p,
1242                    svn_fs_root_t *root, const char *path, apr_pool_t *pool)
1243 {
1244   return svn_error_trace(root->vtable->copied_from(rev_p, path_p, root, path,
1245                                                    pool));
1246 }
1247
1248 svn_error_t *
1249 svn_fs_closest_copy(svn_fs_root_t **root_p, const char **path_p,
1250                     svn_fs_root_t *root, const char *path, apr_pool_t *pool)
1251 {
1252   return svn_error_trace(root->vtable->closest_copy(root_p, path_p,
1253                                                     root, path, pool));
1254 }
1255
1256 svn_error_t *
1257 svn_fs_get_mergeinfo2(svn_mergeinfo_catalog_t *catalog,
1258                       svn_fs_root_t *root,
1259                       const apr_array_header_t *paths,
1260                       svn_mergeinfo_inheritance_t inherit,
1261                       svn_boolean_t include_descendants,
1262                       svn_boolean_t adjust_inherited_mergeinfo,
1263                       apr_pool_t *result_pool,
1264                       apr_pool_t *scratch_pool)
1265 {
1266   return svn_error_trace(root->vtable->get_mergeinfo(
1267     catalog, root, paths, inherit, include_descendants,
1268     adjust_inherited_mergeinfo, result_pool, scratch_pool));
1269 }
1270
1271 svn_error_t *
1272 svn_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
1273                      svn_fs_root_t *root,
1274                      const apr_array_header_t *paths,
1275                      svn_mergeinfo_inheritance_t inherit,
1276                      svn_boolean_t include_descendants,
1277                      apr_pool_t *pool)
1278 {
1279   return svn_error_trace(root->vtable->get_mergeinfo(catalog, root, paths,
1280                                                      inherit,
1281                                                      include_descendants,
1282                                                      TRUE, pool, pool));
1283 }
1284
1285 svn_error_t *
1286 svn_fs__get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
1287                                svn_fs_root_t *root,
1288                                const char *path,
1289                                svn_mergeinfo_inheritance_t inherit,
1290                                svn_boolean_t adjust_inherited_mergeinfo,
1291                                apr_pool_t *result_pool,
1292                                apr_pool_t *scratch_pool)
1293 {
1294   apr_array_header_t *paths
1295     = apr_array_make(scratch_pool, 1, sizeof(const char *));
1296   svn_mergeinfo_catalog_t catalog;
1297
1298   APR_ARRAY_PUSH(paths, const char *) = path;
1299
1300   SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, paths,
1301                                 inherit, FALSE /*include_descendants*/,
1302                                 adjust_inherited_mergeinfo,
1303                                 result_pool, scratch_pool));
1304   *mergeinfo = svn_hash_gets(catalog, path);
1305
1306   return SVN_NO_ERROR;
1307 }
1308
1309 svn_error_t *
1310 svn_fs_merge(const char **conflict_p, svn_fs_root_t *source_root,
1311              const char *source_path, svn_fs_root_t *target_root,
1312              const char *target_path, svn_fs_root_t *ancestor_root,
1313              const char *ancestor_path, apr_pool_t *pool)
1314 {
1315   return svn_error_trace(target_root->vtable->merge(conflict_p,
1316                                                     source_root, source_path,
1317                                                     target_root, target_path,
1318                                                     ancestor_root,
1319                                                     ancestor_path, pool));
1320 }
1321
1322 svn_error_t *
1323 svn_fs_dir_entries(apr_hash_t **entries_p, svn_fs_root_t *root,
1324                    const char *path, apr_pool_t *pool)
1325 {
1326   return svn_error_trace(root->vtable->dir_entries(entries_p, root, path,
1327                                                    pool));
1328 }
1329
1330 svn_error_t *
1331 svn_fs_dir_optimal_order(apr_array_header_t **ordered_p,
1332                          svn_fs_root_t *root,
1333                          apr_hash_t *entries,
1334                          apr_pool_t *result_pool,
1335                          apr_pool_t *scratch_pool)
1336 {
1337   return svn_error_trace(root->vtable->dir_optimal_order(ordered_p, root,
1338                                                          entries,
1339                                                          result_pool,
1340                                                          scratch_pool));
1341 }
1342
1343 svn_error_t *
1344 svn_fs_make_dir(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
1345 {
1346   SVN_ERR(svn_fs__path_valid(path, pool));
1347   return svn_error_trace(root->vtable->make_dir(root, path, pool));
1348 }
1349
1350 svn_error_t *
1351 svn_fs_delete(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
1352 {
1353   return svn_error_trace(root->vtable->delete_node(root, path, pool));
1354 }
1355
1356 svn_error_t *
1357 svn_fs_copy(svn_fs_root_t *from_root, const char *from_path,
1358             svn_fs_root_t *to_root, const char *to_path, apr_pool_t *pool)
1359 {
1360   SVN_ERR(svn_fs__path_valid(to_path, pool));
1361   return svn_error_trace(to_root->vtable->copy(from_root, from_path,
1362                                                to_root, to_path, pool));
1363 }
1364
1365 svn_error_t *
1366 svn_fs_revision_link(svn_fs_root_t *from_root, svn_fs_root_t *to_root,
1367                      const char *path, apr_pool_t *pool)
1368 {
1369   return svn_error_trace(to_root->vtable->revision_link(from_root, to_root,
1370                                                         path, pool));
1371 }
1372
1373 svn_error_t *
1374 svn_fs_file_length(svn_filesize_t *length_p, svn_fs_root_t *root,
1375                    const char *path, apr_pool_t *pool)
1376 {
1377   return svn_error_trace(root->vtable->file_length(length_p, root, path,
1378                                                    pool));
1379 }
1380
1381 svn_error_t *
1382 svn_fs_file_checksum(svn_checksum_t **checksum,
1383                      svn_checksum_kind_t kind,
1384                      svn_fs_root_t *root,
1385                      const char *path,
1386                      svn_boolean_t force,
1387                      apr_pool_t *pool)
1388 {
1389   SVN_ERR(root->vtable->file_checksum(checksum, kind, root, path, pool));
1390
1391   if (force && (*checksum == NULL || (*checksum)->kind != kind))
1392     {
1393       svn_stream_t *contents, *checksum_contents;
1394
1395       SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
1396       checksum_contents = svn_stream_checksummed2(contents, checksum, NULL,
1397                                                   kind, TRUE, pool);
1398
1399       /* This will force a read of any remaining data (which is all of it in
1400          this case) and dump the checksum into checksum->digest. */
1401       SVN_ERR(svn_stream_close(checksum_contents));
1402     }
1403
1404   return SVN_NO_ERROR;
1405 }
1406
1407 svn_error_t *
1408 svn_fs_file_md5_checksum(unsigned char digest[],
1409                          svn_fs_root_t *root,
1410                          const char *path,
1411                          apr_pool_t *pool)
1412 {
1413   svn_checksum_t *md5sum;
1414
1415   SVN_ERR(svn_fs_file_checksum(&md5sum, svn_checksum_md5, root, path, TRUE,
1416                                pool));
1417   memcpy(digest, md5sum->digest, APR_MD5_DIGESTSIZE);
1418
1419   return SVN_NO_ERROR;
1420 }
1421
1422 svn_error_t *
1423 svn_fs_file_contents(svn_stream_t **contents, svn_fs_root_t *root,
1424                      const char *path, apr_pool_t *pool)
1425 {
1426   return svn_error_trace(root->vtable->file_contents(contents, root, path,
1427                                                      pool));
1428 }
1429
1430 svn_error_t *
1431 svn_fs_try_process_file_contents(svn_boolean_t *success,
1432                                  svn_fs_root_t *root,
1433                                  const char *path,
1434                                  svn_fs_process_contents_func_t processor,
1435                                  void* baton,
1436                                  apr_pool_t *pool)
1437 {
1438   /* if the FS doesn't implement this function, report a "failed" attempt */
1439   if (root->vtable->try_process_file_contents == NULL)
1440     {
1441       *success = FALSE;
1442       return SVN_NO_ERROR;
1443     }
1444
1445   return svn_error_trace(root->vtable->try_process_file_contents(
1446                          success,
1447                          root, path,
1448                          processor, baton, pool));
1449 }
1450
1451 svn_error_t *
1452 svn_fs_make_file(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
1453 {
1454   SVN_ERR(svn_fs__path_valid(path, pool));
1455   return svn_error_trace(root->vtable->make_file(root, path, pool));
1456 }
1457
1458 svn_error_t *
1459 svn_fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
1460                        void **contents_baton_p, svn_fs_root_t *root,
1461                        const char *path, const char *base_checksum,
1462                        const char *result_checksum, apr_pool_t *pool)
1463 {
1464   svn_checksum_t *base, *result;
1465
1466   /* TODO: If we ever rev this API, we should make the supplied checksums
1467      svn_checksum_t structs. */
1468   SVN_ERR(svn_checksum_parse_hex(&base, svn_checksum_md5, base_checksum,
1469                                  pool));
1470   SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum,
1471                                  pool));
1472
1473   return svn_error_trace(root->vtable->apply_textdelta(contents_p,
1474                                                        contents_baton_p,
1475                                                        root,
1476                                                        path,
1477                                                        base,
1478                                                        result,
1479                                                        pool));
1480 }
1481
1482 svn_error_t *
1483 svn_fs_apply_text(svn_stream_t **contents_p, svn_fs_root_t *root,
1484                   const char *path, const char *result_checksum,
1485                   apr_pool_t *pool)
1486 {
1487   svn_checksum_t *result;
1488
1489   /* TODO: If we ever rev this API, we should make the supplied checksum an
1490      svn_checksum_t struct. */
1491   SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum,
1492                                  pool));
1493
1494   return svn_error_trace(root->vtable->apply_text(contents_p, root, path,
1495                                                   result, pool));
1496 }
1497
1498 svn_error_t *
1499 svn_fs_contents_different(svn_boolean_t *changed_p, svn_fs_root_t *root1,
1500                           const char *path1, svn_fs_root_t *root2,
1501                           const char *path2, apr_pool_t *scratch_pool)
1502 {
1503   return svn_error_trace(root1->vtable->contents_changed(changed_p,
1504                                                          root1, path1,
1505                                                          root2, path2,
1506                                                          TRUE,
1507                                                          scratch_pool));
1508 }
1509
1510 svn_error_t *
1511 svn_fs_contents_changed(svn_boolean_t *changed_p, svn_fs_root_t *root1,
1512                         const char *path1, svn_fs_root_t *root2,
1513                         const char *path2, apr_pool_t *pool)
1514 {
1515   return svn_error_trace(root1->vtable->contents_changed(changed_p,
1516                                                          root1, path1,
1517                                                          root2, path2,
1518                                                          FALSE, pool));
1519 }
1520
1521 svn_error_t *
1522 svn_fs_youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool)
1523 {
1524   return svn_error_trace(fs->vtable->youngest_rev(youngest_p, fs, pool));
1525 }
1526
1527 svn_error_t *
1528 svn_fs_info_format(int *fs_format,
1529                    svn_version_t **supports_version,
1530                    svn_fs_t *fs,
1531                    apr_pool_t *result_pool,
1532                    apr_pool_t *scratch_pool)
1533 {
1534   return svn_error_trace(fs->vtable->info_format(fs_format, supports_version,
1535                                                  fs,
1536                                                  result_pool, scratch_pool));
1537 }
1538
1539 svn_error_t *
1540 svn_fs_info_config_files(apr_array_header_t **files,
1541                          svn_fs_t *fs,
1542                          apr_pool_t *result_pool,
1543                          apr_pool_t *scratch_pool)
1544 {
1545   return svn_error_trace(fs->vtable->info_config_files(files, fs,
1546                                                        result_pool,
1547                                                        scratch_pool));
1548 }
1549
1550 svn_error_t *
1551 svn_fs_deltify_revision(svn_fs_t *fs, svn_revnum_t revision, apr_pool_t *pool)
1552 {
1553   return svn_error_trace(fs->vtable->deltify(fs, revision, pool));
1554 }
1555
1556 svn_error_t *
1557 svn_fs_revision_prop(svn_string_t **value_p, svn_fs_t *fs, svn_revnum_t rev,
1558                      const char *propname, apr_pool_t *pool)
1559 {
1560   return svn_error_trace(fs->vtable->revision_prop(value_p, fs, rev,
1561                                                    propname, pool));
1562 }
1563
1564 svn_error_t *
1565 svn_fs_revision_proplist(apr_hash_t **table_p, svn_fs_t *fs, svn_revnum_t rev,
1566                          apr_pool_t *pool)
1567 {
1568   return svn_error_trace(fs->vtable->revision_proplist(table_p, fs, rev,
1569                                                        pool));
1570 }
1571
1572 svn_error_t *
1573 svn_fs_change_rev_prop2(svn_fs_t *fs, svn_revnum_t rev, const char *name,
1574                         const svn_string_t *const *old_value_p,
1575                         const svn_string_t *value, apr_pool_t *pool)
1576 {
1577   return svn_error_trace(fs->vtable->change_rev_prop(fs, rev, name,
1578                                                      old_value_p,
1579                                                      value, pool));
1580 }
1581
1582 svn_error_t *
1583 svn_fs_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
1584                              svn_fs_root_t *source_root,
1585                              const char *source_path,
1586                              svn_fs_root_t *target_root,
1587                              const char *target_path, apr_pool_t *pool)
1588 {
1589   return svn_error_trace(target_root->vtable->get_file_delta_stream(
1590                            stream_p,
1591                            source_root, source_path,
1592                            target_root, target_path, pool));
1593 }
1594
1595 svn_error_t *
1596 svn_fs_get_uuid(svn_fs_t *fs, const char **uuid, apr_pool_t *pool)
1597 {
1598   /* If you change this, consider changing svn_fs__identifier(). */
1599   *uuid = apr_pstrdup(pool, fs->uuid);
1600   return SVN_NO_ERROR;
1601 }
1602
1603 svn_error_t *
1604 svn_fs_set_uuid(svn_fs_t *fs, const char *uuid, apr_pool_t *pool)
1605 {
1606   if (! uuid)
1607     {
1608       uuid = svn_uuid_generate(pool);
1609     }
1610   else
1611     {
1612       apr_uuid_t parsed_uuid;
1613       apr_status_t apr_err = apr_uuid_parse(&parsed_uuid, uuid);
1614       if (apr_err)
1615         return svn_error_createf(SVN_ERR_BAD_UUID, NULL,
1616                                  _("Malformed UUID '%s'"), uuid);
1617     }
1618   return svn_error_trace(fs->vtable->set_uuid(fs, uuid, pool));
1619 }
1620
1621 svn_error_t *
1622 svn_fs_lock_many(svn_fs_t *fs,
1623                  apr_hash_t *targets,
1624                  const char *comment,
1625                  svn_boolean_t is_dav_comment,
1626                  apr_time_t expiration_date,
1627                  svn_boolean_t steal_lock,
1628                  svn_fs_lock_callback_t lock_callback,
1629                  void *lock_baton,
1630                  apr_pool_t *result_pool,
1631                  apr_pool_t *scratch_pool)
1632 {
1633   apr_hash_index_t *hi;
1634   apr_hash_t *ok_targets = apr_hash_make(scratch_pool);
1635   svn_error_t *err, *cb_err = SVN_NO_ERROR;
1636
1637   /* Enforce that the comment be xml-escapable. */
1638   if (comment)
1639     if (! svn_xml_is_xml_safe(comment, strlen(comment)))
1640       return svn_error_create(SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
1641                               _("Lock comment contains illegal characters"));
1642
1643   if (expiration_date < 0)
1644     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1645               _("Negative expiration date passed to svn_fs_lock"));
1646
1647   /* Enforce that the token be an XML-safe URI. */
1648   for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1649     {
1650       const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1651
1652       err = SVN_NO_ERROR;
1653       if (target->token)
1654         {
1655           const char *c;
1656
1657
1658           if (strncmp(target->token, "opaquelocktoken:", 16))
1659             err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
1660                                     _("Lock token URI '%s' has bad scheme; "
1661                                       "expected '%s'"),
1662                                     target->token, "opaquelocktoken");
1663
1664           if (!err)
1665             for (c = target->token; *c && !err; c++)
1666               if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c))
1667                 err = svn_error_createf(
1668                         SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
1669                         _("Lock token '%s' is not ASCII or is a "
1670                           "control character at byte %u"),
1671                         target->token,
1672                         (unsigned)(c - target->token));
1673
1674           /* strlen(token) == c - token. */
1675           if (!err && !svn_xml_is_xml_safe(target->token, c - target->token))
1676             err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
1677                                     _("Lock token URI '%s' is not XML-safe"),
1678                                     target->token);
1679         }
1680
1681       if (err)
1682         {
1683           if (!cb_err && lock_callback)
1684             cb_err = lock_callback(lock_baton, apr_hash_this_key(hi),
1685                                    NULL, err, scratch_pool);
1686           svn_error_clear(err);
1687         }
1688       else
1689         svn_hash_sets(ok_targets, apr_hash_this_key(hi), target);
1690     }
1691
1692   if (!apr_hash_count(ok_targets))
1693     return svn_error_trace(cb_err);
1694
1695   err = fs->vtable->lock(fs, ok_targets, comment, is_dav_comment,
1696                          expiration_date, steal_lock,
1697                          lock_callback, lock_baton,
1698                          result_pool, scratch_pool);
1699
1700   if (err && cb_err)
1701     svn_error_compose(err, cb_err);
1702   else if (!err)
1703     err = cb_err;
1704
1705   return svn_error_trace(err);
1706 }
1707
1708 struct lock_baton_t {
1709   const svn_lock_t *lock;
1710   svn_error_t *fs_err;
1711 };
1712
1713 /* Implements svn_fs_lock_callback_t. Used by svn_fs_lock and
1714    svn_fs_unlock to record the lock and error from svn_fs_lock_many
1715    and svn_fs_unlock_many. */
1716 static svn_error_t *
1717 lock_cb(void *lock_baton,
1718         const char *path,
1719         const svn_lock_t *lock,
1720         svn_error_t *fs_err,
1721         apr_pool_t *pool)
1722 {
1723   struct lock_baton_t *b = lock_baton;
1724
1725   b->lock = lock;
1726   b->fs_err = svn_error_dup(fs_err);
1727
1728   return SVN_NO_ERROR;
1729 }
1730
1731 svn_error_t *
1732 svn_fs_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
1733             const char *token, const char *comment,
1734             svn_boolean_t is_dav_comment, apr_time_t expiration_date,
1735             svn_revnum_t current_rev, svn_boolean_t steal_lock,
1736             apr_pool_t *pool)
1737 {
1738   apr_hash_t *targets = apr_hash_make(pool);
1739   svn_fs_lock_target_t target;
1740   svn_error_t *err;
1741   struct lock_baton_t baton = {0};
1742
1743   target.token = token;
1744   target.current_rev = current_rev;
1745   svn_hash_sets(targets, path, &target);
1746
1747   err = svn_fs_lock_many(fs, targets, comment, is_dav_comment,
1748                          expiration_date, steal_lock, lock_cb, &baton,
1749                          pool, pool);
1750
1751   if (baton.lock)
1752     *lock = (svn_lock_t*)baton.lock;
1753
1754   if (err && baton.fs_err)
1755     svn_error_compose(err, baton.fs_err);
1756   else if (!err)
1757     err = baton.fs_err;
1758
1759   return svn_error_trace(err);
1760 }
1761
1762 svn_error_t *
1763 svn_fs_generate_lock_token(const char **token, svn_fs_t *fs, apr_pool_t *pool)
1764 {
1765   return svn_error_trace(fs->vtable->generate_lock_token(token, fs, pool));
1766 }
1767
1768 svn_fs_lock_target_t *
1769 svn_fs_lock_target_create(const char *token,
1770                           svn_revnum_t current_rev,
1771                           apr_pool_t *result_pool)
1772 {
1773   svn_fs_lock_target_t *target = apr_palloc(result_pool,
1774                                             sizeof(svn_fs_lock_target_t));
1775
1776   target->token = token;
1777   target->current_rev = current_rev;
1778
1779   return target;
1780 }
1781
1782 void
1783 svn_fs_lock_target_set_token(svn_fs_lock_target_t *target,
1784                              const char *token)
1785 {
1786   target->token = token;
1787 }
1788
1789 svn_error_t *
1790 svn_fs_unlock_many(svn_fs_t *fs,
1791                    apr_hash_t *targets,
1792                    svn_boolean_t break_lock,
1793                    svn_fs_lock_callback_t lock_callback,
1794                    void *lock_baton,
1795                    apr_pool_t *result_pool,
1796                    apr_pool_t *scratch_pool)
1797 {
1798   return svn_error_trace(fs->vtable->unlock(fs, targets, break_lock,
1799                                             lock_callback, lock_baton,
1800                                             result_pool, scratch_pool));
1801 }
1802
1803 svn_error_t *
1804 svn_fs_unlock(svn_fs_t *fs, const char *path, const char *token,
1805               svn_boolean_t break_lock, apr_pool_t *pool)
1806 {
1807   apr_hash_t *targets = apr_hash_make(pool);
1808   svn_error_t *err;
1809   struct lock_baton_t baton = {0};
1810
1811   if (!token)
1812     token = "";
1813   svn_hash_sets(targets, path, token);
1814
1815   err = svn_fs_unlock_many(fs, targets, break_lock, lock_cb, &baton,
1816                            pool, pool);
1817
1818   if (err && baton.fs_err)
1819     svn_error_compose(err, baton.fs_err);
1820   else if (!err)
1821     err = baton.fs_err;
1822
1823   return svn_error_trace(err);
1824 }
1825
1826 svn_error_t *
1827 svn_fs_get_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
1828                 apr_pool_t *pool)
1829 {
1830   return svn_error_trace(fs->vtable->get_lock(lock, fs, path, pool));
1831 }
1832
1833 svn_error_t *
1834 svn_fs_get_locks2(svn_fs_t *fs, const char *path, svn_depth_t depth,
1835                   svn_fs_get_locks_callback_t get_locks_func,
1836                   void *get_locks_baton, apr_pool_t *pool)
1837 {
1838   SVN_ERR_ASSERT((depth == svn_depth_empty) ||
1839                  (depth == svn_depth_files) ||
1840                  (depth == svn_depth_immediates) ||
1841                  (depth == svn_depth_infinity));
1842   return svn_error_trace(fs->vtable->get_locks(fs, path, depth,
1843                                                get_locks_func,
1844                                                get_locks_baton, pool));
1845 }
1846
1847 \f
1848 /* --- History functions --- */
1849
1850 svn_error_t *
1851 svn_fs_history_prev2(svn_fs_history_t **prev_history_p,
1852                      svn_fs_history_t *history, svn_boolean_t cross_copies,
1853                      apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1854 {
1855   return svn_error_trace(history->vtable->prev(prev_history_p, history,
1856                                                cross_copies, result_pool,
1857                                                scratch_pool));
1858 }
1859
1860 svn_error_t *
1861 svn_fs_history_prev(svn_fs_history_t **prev_history_p,
1862                     svn_fs_history_t *history, svn_boolean_t cross_copies,
1863                     apr_pool_t *pool)
1864 {
1865   return svn_error_trace(history->vtable->prev(prev_history_p, history,
1866                                                cross_copies, pool, pool));
1867 }
1868
1869 svn_error_t *
1870 svn_fs_history_location(const char **path, svn_revnum_t *revision,
1871                         svn_fs_history_t *history, apr_pool_t *pool)
1872 {
1873   return svn_error_trace(history->vtable->location(path, revision, history,
1874                                                    pool));
1875 }
1876
1877 \f
1878 /* --- Node-ID functions --- */
1879
1880 svn_fs_id_t *
1881 svn_fs_parse_id(const char *data, apr_size_t len, apr_pool_t *pool)
1882 {
1883   fs_library_vtable_t *vtable;
1884   svn_error_t *err;
1885
1886   err = get_library_vtable(&vtable, SVN_FS_TYPE_BDB, pool);
1887   if (err)
1888     {
1889       svn_error_clear(err);
1890       return NULL;
1891     }
1892   return vtable->parse_id(data, len, pool);
1893 }
1894
1895 svn_string_t *
1896 svn_fs_unparse_id(const svn_fs_id_t *id, apr_pool_t *pool)
1897 {
1898   return id->vtable->unparse(id, pool);
1899 }
1900
1901 svn_boolean_t
1902 svn_fs_check_related(const svn_fs_id_t *a, const svn_fs_id_t *b)
1903 {
1904   return (a->vtable->compare(a, b) != svn_fs_node_unrelated);
1905 }
1906
1907 int
1908 svn_fs_compare_ids(const svn_fs_id_t *a, const svn_fs_id_t *b)
1909 {
1910   switch (a->vtable->compare(a, b))
1911     {
1912     case svn_fs_node_unchanged:
1913       return 0;
1914     case svn_fs_node_common_ancestor:
1915       return 1;
1916     default:
1917       return -1;
1918     }
1919 }
1920
1921 svn_error_t *
1922 svn_fs_print_modules(svn_stringbuf_t *output,
1923                      apr_pool_t *pool)
1924 {
1925   struct fs_type_defn *defn = fs_modules;
1926   fs_library_vtable_t *vtable;
1927   apr_pool_t *iterpool = svn_pool_create(pool);
1928
1929   while (defn)
1930     {
1931       char *line;
1932       svn_error_t *err;
1933
1934       svn_pool_clear(iterpool);
1935
1936       err = get_library_vtable_direct(&vtable, defn, iterpool);
1937       if (err)
1938         {
1939           if (err->apr_err == SVN_ERR_FS_UNKNOWN_FS_TYPE)
1940             {
1941               svn_error_clear(err);
1942               defn = defn->next;
1943               continue;
1944             }
1945           else
1946             return err;
1947         }
1948
1949       line = apr_psprintf(iterpool, "* fs_%s : %s\n",
1950                           defn->fsap_name, vtable->get_description());
1951       svn_stringbuf_appendcstr(output, line);
1952       defn = defn->next;
1953     }
1954
1955   svn_pool_destroy(iterpool);
1956
1957   return SVN_NO_ERROR;
1958 }
1959
1960 svn_fs_path_change2_t *
1961 svn_fs_path_change2_create(const svn_fs_id_t *node_rev_id,
1962                            svn_fs_path_change_kind_t change_kind,
1963                            apr_pool_t *pool)
1964 {
1965   return svn_fs__path_change_create_internal(node_rev_id, change_kind, pool);
1966 }
1967
1968 /* Return the library version number. */
1969 const svn_version_t *
1970 svn_fs_version(void)
1971 {
1972   SVN_VERSION_BODY;
1973 }
1974
1975 \f
1976 /** info **/
1977 svn_error_t *
1978 svn_fs_info(const svn_fs_info_placeholder_t **info_p,
1979             svn_fs_t *fs,
1980             apr_pool_t *result_pool,
1981             apr_pool_t *scratch_pool)
1982 {
1983   if (fs->vtable->info_fsap)
1984     {
1985       SVN_ERR(fs->vtable->info_fsap((const void **)info_p, fs,
1986                                     result_pool, scratch_pool));
1987     }
1988   else
1989     {
1990       svn_fs_info_placeholder_t *info = apr_palloc(result_pool, sizeof(*info));
1991       /* ### Ask the disk(!), since svn_fs_t doesn't cache the answer. */
1992       SVN_ERR(svn_fs_type(&info->fs_type, fs->path, result_pool));
1993       *info_p = info;
1994     }
1995   return SVN_NO_ERROR;
1996 }
1997
1998 void *
1999 svn_fs_info_dup(const void *info_void,
2000                 apr_pool_t *result_pool,
2001                 apr_pool_t *scratch_pool)
2002 {
2003   const svn_fs_info_placeholder_t *info = info_void;
2004   fs_library_vtable_t *vtable;
2005
2006   SVN_ERR(get_library_vtable(&vtable, info->fs_type, scratch_pool));
2007
2008   if (vtable->info_fsap_dup)
2009     return vtable->info_fsap_dup(info_void, result_pool);
2010   else
2011     return apr_pmemdup(result_pool, info, sizeof(*info));
2012 }
2013