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
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(). */
66static apr_pool_t *common_pool = NULL;
67static svn_mutex__t *common_pool_lock = NULL;
68static svn_atomic_t common_pool_initialized = FALSE;
69
70
71/* --- Utility functions for the loader --- */
72
73struct 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
81static 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
94static 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
106static 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
118static struct fs_type_defn *fs_modules = &fsfs_defn;
119
120
121static svn_error_t *
122load_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. */
168static svn_error_t *
169get_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 */
228static svn_error_t *
229get_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. */
252static svn_error_t *
253get_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
305svn_error_t *
306svn_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. */
349static svn_error_t *
350fs_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
363static svn_error_t *
364write_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
380/* --- Functions for operating on filesystems by pathname --- */
381
382static 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
391static svn_error_t *
392synchronized_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
408svn_error_t *
409svn_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.  */
423static void
424default_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
441svn_error_t *
442svn_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. */
479static svn_fs_t *
480fs_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
495svn_fs_t *
496svn_fs_new(apr_hash_t *fs_config, apr_pool_t *pool)
497{
498  return fs_new(fs_config, pool);
499}
500
501void
502svn_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
509svn_error_t *
510svn_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
533svn_error_t *
534svn_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
549svn_error_t *
550svn_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
558svn_error_t *
559svn_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.  */
582static void
583verify_fs_warning_func(void *baton, svn_error_t *err)
584{
585}
586
587svn_error_t *
588svn_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
613const char *
614svn_fs_path(svn_fs_t *fs, apr_pool_t *pool)
615{
616  return apr_pstrdup(pool, fs->path);
617}
618
619apr_hash_t *
620svn_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
628svn_error_t *
629svn_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
637svn_error_t *
638svn_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
702svn_error_t *
703svn_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
722svn_error_t *
723svn_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
739svn_error_t *
740svn_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
749svn_error_t *
750svn_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
761/* --- Berkeley-specific functions --- */
762
763svn_error_t *
764svn_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
781svn_error_t *
782svn_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
793const char *
794svn_fs_berkeley_path(svn_fs_t *fs, apr_pool_t *pool)
795{
796  return svn_fs_path(fs, pool);
797}
798
799svn_error_t *
800svn_fs_delete_berkeley(const char *path, apr_pool_t *pool)
801{
802  return svn_error_trace(svn_fs_delete_fs(path, pool));
803}
804
805svn_error_t *
806svn_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
814svn_error_t *
815svn_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
820svn_error_t *
821svn_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
827svn_error_t *
828svn_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
841/* --- Transaction functions --- */
842
843svn_error_t *
844svn_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
851svn_error_t *
852svn_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
880svn_error_t *
881svn_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
886svn_error_t *
887svn_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
892svn_error_t *
893svn_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
899svn_revnum_t
900svn_fs_txn_base_revision(svn_fs_txn_t *txn)
901{
902  return txn->base_rev;
903}
904
905svn_error_t *
906svn_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
912svn_error_t *
913svn_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
919static svn_boolean_t
920is_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
927svn_error_t *
928svn_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
940svn_error_t *
941svn_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
953svn_error_t *
954svn_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
965svn_error_t *
966svn_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
985/* --- Root functions --- */
986
987svn_error_t *
988svn_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
997svn_error_t *
998svn_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
1006void
1007svn_fs_close_root(svn_fs_root_t *root)
1008{
1009  svn_pool_destroy(root->pool);
1010}
1011
1012svn_fs_t *
1013svn_fs_root_fs(svn_fs_root_t *root)
1014{
1015  return root->fs;
1016}
1017
1018svn_boolean_t
1019svn_fs_is_txn_root(svn_fs_root_t *root)
1020{
1021  return root->is_txn_root;
1022}
1023
1024svn_boolean_t
1025svn_fs_is_revision_root(svn_fs_root_t *root)
1026{
1027  return !root->is_txn_root;
1028}
1029
1030const char *
1031svn_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
1036svn_revnum_t
1037svn_fs_txn_root_base_revision(svn_fs_root_t *root)
1038{
1039  return root->is_txn_root ? root->rev : SVN_INVALID_REVNUM;
1040}
1041
1042svn_revnum_t
1043svn_fs_revision_root_revision(svn_fs_root_t *root)
1044{
1045  return root->is_txn_root ? SVN_INVALID_REVNUM : root->rev;
1046}
1047
1048svn_error_t *
1049svn_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
1056svn_error_t *
1057svn_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
1086svn_error_t *
1087svn_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
1093svn_error_t *
1094svn_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
1103svn_error_t *
1104svn_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
1111svn_error_t *
1112svn_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
1122svn_error_t *
1123svn_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
1133svn_error_t *
1134svn_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
1140svn_error_t *
1141svn_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
1159svn_error_t *
1160svn_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
1167svn_error_t *
1168svn_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
1175svn_error_t *
1176svn_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
1183svn_error_t *
1184svn_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
1191svn_error_t *
1192svn_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
1199svn_error_t *
1200svn_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
1209svn_error_t *
1210svn_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
1218svn_error_t *
1219svn_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
1229svn_error_t *
1230svn_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
1240svn_error_t *
1241svn_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
1248svn_error_t *
1249svn_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
1256svn_error_t *
1257svn_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
1271svn_error_t *
1272svn_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
1285svn_error_t *
1286svn_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
1309svn_error_t *
1310svn_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
1322svn_error_t *
1323svn_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
1330svn_error_t *
1331svn_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
1343svn_error_t *
1344svn_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
1350svn_error_t *
1351svn_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
1356svn_error_t *
1357svn_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
1365svn_error_t *
1366svn_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
1373svn_error_t *
1374svn_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
1381svn_error_t *
1382svn_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
1407svn_error_t *
1408svn_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
1422svn_error_t *
1423svn_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
1430svn_error_t *
1431svn_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
1451svn_error_t *
1452svn_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
1458svn_error_t *
1459svn_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
1482svn_error_t *
1483svn_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
1498svn_error_t *
1499svn_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
1510svn_error_t *
1511svn_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
1521svn_error_t *
1522svn_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
1527svn_error_t *
1528svn_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
1539svn_error_t *
1540svn_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
1550svn_error_t *
1551svn_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
1556svn_error_t *
1557svn_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
1564svn_error_t *
1565svn_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
1572svn_error_t *
1573svn_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
1582svn_error_t *
1583svn_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
1595svn_error_t *
1596svn_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
1603svn_error_t *
1604svn_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
1621svn_error_t *
1622svn_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
1708struct 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. */
1716static svn_error_t *
1717lock_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
1731svn_error_t *
1732svn_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
1762svn_error_t *
1763svn_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
1768svn_fs_lock_target_t *
1769svn_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
1782void
1783svn_fs_lock_target_set_token(svn_fs_lock_target_t *target,
1784                             const char *token)
1785{
1786  target->token = token;
1787}
1788
1789svn_error_t *
1790svn_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
1803svn_error_t *
1804svn_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
1826svn_error_t *
1827svn_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
1833svn_error_t *
1834svn_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
1848/* --- History functions --- */
1849
1850svn_error_t *
1851svn_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
1860svn_error_t *
1861svn_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
1869svn_error_t *
1870svn_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
1878/* --- Node-ID functions --- */
1879
1880svn_fs_id_t *
1881svn_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
1895svn_string_t *
1896svn_fs_unparse_id(const svn_fs_id_t *id, apr_pool_t *pool)
1897{
1898  return id->vtable->unparse(id, pool);
1899}
1900
1901svn_boolean_t
1902svn_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
1907int
1908svn_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
1921svn_error_t *
1922svn_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
1960svn_fs_path_change2_t *
1961svn_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. */
1969const svn_version_t *
1970svn_fs_version(void)
1971{
1972  SVN_VERSION_BODY;
1973}
1974
1975
1976/** info **/
1977svn_error_t *
1978svn_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
1998void *
1999svn_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
2014