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