~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_x/hotcopy.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* hotcopys.c --- FS hotcopy functionality for FSX
 
2
 *
 
3
 * ====================================================================
 
4
 *    Licensed to the Apache Software Foundation (ASF) under one
 
5
 *    or more contributor license agreements.  See the NOTICE file
 
6
 *    distributed with this work for additional information
 
7
 *    regarding copyright ownership.  The ASF licenses this file
 
8
 *    to you under the Apache License, Version 2.0 (the
 
9
 *    "License"); you may not use this file except in compliance
 
10
 *    with the License.  You may obtain a copy of the License at
 
11
 *
 
12
 *      http://www.apache.org/licenses/LICENSE-2.0
 
13
 *
 
14
 *    Unless required by applicable law or agreed to in writing,
 
15
 *    software distributed under the License is distributed on an
 
16
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
17
 *    KIND, either express or implied.  See the License for the
 
18
 *    specific language governing permissions and limitations
 
19
 *    under the License.
 
20
 * ====================================================================
 
21
 */
 
22
#include "svn_pools.h"
 
23
#include "svn_path.h"
 
24
#include "svn_dirent_uri.h"
 
25
 
 
26
#include "fs_x.h"
 
27
#include "hotcopy.h"
 
28
#include "util.h"
 
29
#include "revprops.h"
 
30
#include "rep-cache.h"
 
31
#include "transaction.h"
 
32
#include "recovery.h"
 
33
 
 
34
#include "../libsvn_fs/fs-loader.h"
 
35
 
 
36
#include "svn_private_config.h"
 
37
 
 
38
/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
 
39
 * the destination and do not differ in terms of kind, size, and mtime.
 
40
 * Set *SKIPPED_P to FALSE only if the file was copied, do not change
 
41
 * the value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not
 
42
 * required. */
 
43
static svn_error_t *
 
44
hotcopy_io_dir_file_copy(svn_boolean_t *skipped_p,
 
45
                         const char *src_path,
 
46
                         const char *dst_path,
 
47
                         const char *file,
 
48
                         apr_pool_t *scratch_pool)
 
49
{
 
50
  const svn_io_dirent2_t *src_dirent;
 
51
  const svn_io_dirent2_t *dst_dirent;
 
52
  const char *src_target;
 
53
  const char *dst_target;
 
54
 
 
55
  /* Does the destination already exist? If not, we must copy it. */
 
56
  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
 
57
  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
 
58
                              scratch_pool, scratch_pool));
 
59
  if (dst_dirent->kind != svn_node_none)
 
60
    {
 
61
      /* If the destination's stat information indicates that the file
 
62
       * is equal to the source, don't bother copying the file again. */
 
63
      src_target = svn_dirent_join(src_path, file, scratch_pool);
 
64
      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
 
65
                                  scratch_pool, scratch_pool));
 
66
      if (src_dirent->kind == dst_dirent->kind &&
 
67
          src_dirent->special == dst_dirent->special &&
 
68
          src_dirent->filesize == dst_dirent->filesize &&
 
69
          src_dirent->mtime <= dst_dirent->mtime)
 
70
        return SVN_NO_ERROR;
 
71
    }
 
72
 
 
73
  if (skipped_p)
 
74
    *skipped_p = FALSE;
 
75
 
 
76
  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
 
77
                                              scratch_pool));
 
78
}
 
79
 
 
80
/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
 
81
 * NAME is in the internal encoding used by APR; PARENT is in
 
82
 * UTF-8 and in internal (not local) style.
 
83
 *
 
84
 * Use PARENT only for generating an error string if the conversion
 
85
 * fails because NAME could not be represented in UTF-8.  In that
 
86
 * case, return a two-level error in which the outer error's message
 
87
 * mentions PARENT, but the inner error's message does not mention
 
88
 * NAME (except possibly in hex) since NAME may not be printable.
 
89
 * Such a compound error at least allows the user to go looking in the
 
90
 * right directory for the problem.
 
91
 *
 
92
 * If there is any other error, just return that error directly.
 
93
 *
 
94
 * If there is any error, the effect on *NAME_P is undefined.
 
95
 *
 
96
 * *NAME_P and NAME may refer to the same storage.
 
97
 */
 
98
static svn_error_t *
 
99
entry_name_to_utf8(const char **name_p,
 
100
                   const char *name,
 
101
                   const char *parent,
 
102
                   apr_pool_t *result_pool)
 
103
{
 
104
  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, result_pool);
 
105
  if (err && err->apr_err == APR_EINVAL)
 
106
    {
 
107
      return svn_error_createf(err->apr_err, err,
 
108
                               _("Error converting entry "
 
109
                                 "in directory '%s' to UTF-8"),
 
110
                               svn_dirent_local_style(parent, result_pool));
 
111
    }
 
112
  return err;
 
113
}
 
114
 
 
115
/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
 
116
 * exist in the destination and do not differ from the source in terms of
 
117
 * kind, size, and mtime. Set *SKIPPED_P to FALSE only if at least one
 
118
 * file was copied, do not change the value in *SKIPPED_P otherwise.
 
119
 * SKIPPED_P may be NULL if not required. */
 
120
static svn_error_t *
 
121
hotcopy_io_copy_dir_recursively(svn_boolean_t *skipped_p,
 
122
                                const char *src,
 
123
                                const char *dst_parent,
 
124
                                const char *dst_basename,
 
125
                                svn_boolean_t copy_perms,
 
126
                                svn_cancel_func_t cancel_func,
 
127
                                void *cancel_baton,
 
128
                                apr_pool_t *scratch_pool)
 
129
{
 
130
  svn_node_kind_t kind;
 
131
  apr_status_t status;
 
132
  const char *dst_path;
 
133
  apr_dir_t *this_dir;
 
134
  apr_finfo_t this_entry;
 
135
  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
 
136
 
 
137
  /* Make a subpool for recursion */
 
138
  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
139
 
 
140
  /* The 'dst_path' is simply dst_parent/dst_basename */
 
141
  dst_path = svn_dirent_join(dst_parent, dst_basename, scratch_pool);
 
142
 
 
143
  /* Sanity checks:  SRC and DST_PARENT are directories, and
 
144
     DST_BASENAME doesn't already exist in DST_PARENT. */
 
145
  SVN_ERR(svn_io_check_path(src, &kind, subpool));
 
146
  if (kind != svn_node_dir)
 
147
    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
 
148
                             _("Source '%s' is not a directory"),
 
149
                             svn_dirent_local_style(src, scratch_pool));
 
150
 
 
151
  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
 
152
  if (kind != svn_node_dir)
 
153
    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
 
154
                             _("Destination '%s' is not a directory"),
 
155
                             svn_dirent_local_style(dst_parent,
 
156
                                                    scratch_pool));
 
157
 
 
158
  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
 
159
 
 
160
  /* Create the new directory. */
 
161
  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
 
162
  SVN_ERR(svn_io_make_dir_recursively(dst_path, scratch_pool));
 
163
 
 
164
  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
 
165
  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
 
166
 
 
167
  for (status = apr_dir_read(&this_entry, flags, this_dir);
 
168
       status == APR_SUCCESS;
 
169
       status = apr_dir_read(&this_entry, flags, this_dir))
 
170
    {
 
171
      if ((this_entry.name[0] == '.')
 
172
          && ((this_entry.name[1] == '\0')
 
173
              || ((this_entry.name[1] == '.')
 
174
                  && (this_entry.name[2] == '\0'))))
 
175
        {
 
176
          continue;
 
177
        }
 
178
      else
 
179
        {
 
180
          const char *entryname_utf8;
 
181
 
 
182
          if (cancel_func)
 
183
            SVN_ERR(cancel_func(cancel_baton));
 
184
 
 
185
          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
 
186
                                     src, subpool));
 
187
          if (this_entry.filetype == APR_REG) /* regular file */
 
188
            {
 
189
              SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, src, dst_path,
 
190
                                               entryname_utf8, subpool));
 
191
            }
 
192
          else if (this_entry.filetype == APR_LNK) /* symlink */
 
193
            {
 
194
              const char *src_target = svn_dirent_join(src, entryname_utf8,
 
195
                                                       subpool);
 
196
              const char *dst_target = svn_dirent_join(dst_path,
 
197
                                                       entryname_utf8,
 
198
                                                       subpool);
 
199
              SVN_ERR(svn_io_copy_link(src_target, dst_target,
 
200
                                       subpool));
 
201
            }
 
202
          else if (this_entry.filetype == APR_DIR) /* recurse */
 
203
            {
 
204
              const char *src_target;
 
205
 
 
206
              /* Prevent infinite recursion by filtering off our
 
207
                 newly created destination path. */
 
208
              if (strcmp(src, dst_parent) == 0
 
209
                  && strcmp(entryname_utf8, dst_basename) == 0)
 
210
                continue;
 
211
 
 
212
              src_target = svn_dirent_join(src, entryname_utf8, subpool);
 
213
              SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
 
214
                                                      src_target,
 
215
                                                      dst_path,
 
216
                                                      entryname_utf8,
 
217
                                                      copy_perms,
 
218
                                                      cancel_func,
 
219
                                                      cancel_baton,
 
220
                                                      subpool));
 
221
            }
 
222
          /* ### support other APR node types someday?? */
 
223
 
 
224
        }
 
225
    }
 
226
 
 
227
  if (! (APR_STATUS_IS_ENOENT(status)))
 
228
    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
 
229
                              svn_dirent_local_style(src, scratch_pool));
 
230
 
 
231
  status = apr_dir_close(this_dir);
 
232
  if (status)
 
233
    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
 
234
                              svn_dirent_local_style(src, scratch_pool));
 
235
 
 
236
  /* Free any memory used by recursion */
 
237
  svn_pool_destroy(subpool);
 
238
 
 
239
  return SVN_NO_ERROR;
 
240
}
 
241
 
 
242
/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
 
243
 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
 
244
 * Set *SKIPPED_P to FALSE only if the file was copied, do not change the
 
245
 * value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not required.
 
246
 * Use SCRATCH_POOL for temporary allocations. */
 
247
static svn_error_t *
 
248
hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
 
249
                        const char *src_subdir,
 
250
                        const char *dst_subdir,
 
251
                        svn_revnum_t rev,
 
252
                        int max_files_per_dir,
 
253
                        apr_pool_t *scratch_pool)
 
254
{
 
255
  const char *src_subdir_shard = src_subdir,
 
256
             *dst_subdir_shard = dst_subdir;
 
257
 
 
258
  const char *shard = apr_psprintf(scratch_pool, "%ld",
 
259
                                   rev / max_files_per_dir);
 
260
  src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
 
261
  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
 
262
 
 
263
  if (rev % max_files_per_dir == 0)
 
264
    {
 
265
      SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
 
266
      SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
 
267
                                scratch_pool));
 
268
    }
 
269
 
 
270
  SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
 
271
                                   src_subdir_shard, dst_subdir_shard,
 
272
                                   apr_psprintf(scratch_pool, "%ld", rev),
 
273
                                   scratch_pool));
 
274
  return SVN_NO_ERROR;
 
275
}
 
276
 
 
277
 
 
278
/* Copy a packed shard containing revision REV, and which contains
 
279
 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
 
280
 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
 
281
 * Do not re-copy data which already exists in DST_FS.
 
282
 * Set *SKIPPED_P to FALSE only if at least one part of the shard
 
283
 * was copied, do not change the value in *SKIPPED_P otherwise.
 
284
 * SKIPPED_P may be NULL if not required.
 
285
 * Use SCRATCH_POOL for temporary allocations. */
 
286
static svn_error_t *
 
287
hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
 
288
                          svn_revnum_t *dst_min_unpacked_rev,
 
289
                          svn_fs_t *src_fs,
 
290
                          svn_fs_t *dst_fs,
 
291
                          svn_revnum_t rev,
 
292
                          int max_files_per_dir,
 
293
                          apr_pool_t *scratch_pool)
 
294
{
 
295
  const char *src_subdir;
 
296
  const char *dst_subdir;
 
297
  const char *packed_shard;
 
298
  const char *src_subdir_packed_shard;
 
299
  svn_revnum_t revprop_rev;
 
300
  apr_pool_t *iterpool;
 
301
  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
 
302
 
 
303
  /* Copy the packed shard. */
 
304
  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
 
305
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
 
306
  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
 
307
                              rev / max_files_per_dir);
 
308
  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
 
309
                                            scratch_pool);
 
310
  SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
 
311
                                          dst_subdir, packed_shard,
 
312
                                          TRUE /* copy_perms */,
 
313
                                          NULL /* cancel_func */, NULL,
 
314
                                          scratch_pool));
 
315
 
 
316
  /* Copy revprops belonging to revisions in this pack. */
 
317
  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
 
318
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
 
319
 
 
320
  if (src_ffd->min_unpacked_rev < rev + max_files_per_dir)
 
321
    {
 
322
      /* copy unpacked revprops rev by rev */
 
323
      iterpool = svn_pool_create(scratch_pool);
 
324
      for (revprop_rev = rev;
 
325
           revprop_rev < rev + max_files_per_dir;
 
326
           revprop_rev++)
 
327
        {
 
328
          svn_pool_clear(iterpool);
 
329
 
 
330
          SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
 
331
                                          revprop_rev, max_files_per_dir,
 
332
                                          iterpool));
 
333
        }
 
334
      svn_pool_destroy(iterpool);
 
335
    }
 
336
  else
 
337
    {
 
338
      /* revprop for revision 0 will never be packed */
 
339
      if (rev == 0)
 
340
        SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
 
341
                                        0, max_files_per_dir,
 
342
                                        scratch_pool));
 
343
 
 
344
      /* packed revprops folder */
 
345
      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
 
346
                                  rev / max_files_per_dir);
 
347
      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
 
348
                                                scratch_pool);
 
349
      SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
 
350
                                              src_subdir_packed_shard,
 
351
                                              dst_subdir, packed_shard,
 
352
                                              TRUE /* copy_perms */,
 
353
                                              NULL /* cancel_func */, NULL,
 
354
                                              scratch_pool));
 
355
    }
 
356
 
 
357
  /* If necessary, update the min-unpacked rev file in the hotcopy. */
 
358
  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
 
359
    {
 
360
      *dst_min_unpacked_rev = rev + max_files_per_dir;
 
361
      SVN_ERR(svn_fs_x__write_min_unpacked_rev(dst_fs,
 
362
                                               *dst_min_unpacked_rev,
 
363
                                               scratch_pool));
 
364
    }
 
365
 
 
366
  return SVN_NO_ERROR;
 
367
}
 
368
 
 
369
/* Remove file PATH, if it exists - even if it is read-only.
 
370
 * Use SCRATCH_POOL for temporary allocations. */
 
371
static svn_error_t *
 
372
hotcopy_remove_file(const char *path,
 
373
                    apr_pool_t *scratch_pool)
 
374
{
 
375
  /* Make the rev file writable and remove it. */
 
376
  SVN_ERR(svn_io_set_file_read_write(path, TRUE, scratch_pool));
 
377
  SVN_ERR(svn_io_remove_file2(path, TRUE, scratch_pool));
 
378
 
 
379
  return SVN_NO_ERROR;
 
380
}
 
381
 
 
382
 
 
383
/* Remove revision or revprop files between START_REV (inclusive) and
 
384
 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
 
385
 * sharding as per MAX_FILES_PER_DIR.
 
386
 * Use SCRATCH_POOL for temporary allocations. */
 
387
static svn_error_t *
 
388
hotcopy_remove_files(svn_fs_t *dst_fs,
 
389
                     const char *dst_subdir,
 
390
                     svn_revnum_t start_rev,
 
391
                     svn_revnum_t end_rev,
 
392
                     int max_files_per_dir,
 
393
                     apr_pool_t *scratch_pool)
 
394
{
 
395
  const char *shard;
 
396
  const char *dst_subdir_shard;
 
397
  svn_revnum_t rev;
 
398
  apr_pool_t *iterpool;
 
399
 
 
400
  /* Pre-compute paths for initial shard. */
 
401
  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
 
402
  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
 
403
 
 
404
  iterpool = svn_pool_create(scratch_pool);
 
405
  for (rev = start_rev; rev < end_rev; rev++)
 
406
    {
 
407
      svn_pool_clear(iterpool);
 
408
 
 
409
      /* If necessary, update paths for shard. */
 
410
      if (rev != start_rev && rev % max_files_per_dir == 0)
 
411
        {
 
412
          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
 
413
          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
 
414
        }
 
415
 
 
416
      /* remove files for REV */
 
417
      SVN_ERR(hotcopy_remove_file(svn_dirent_join(dst_subdir_shard,
 
418
                                                  apr_psprintf(iterpool,
 
419
                                                               "%ld", rev),
 
420
                                                  iterpool),
 
421
                                  iterpool));
 
422
    }
 
423
 
 
424
  svn_pool_destroy(iterpool);
 
425
 
 
426
  return SVN_NO_ERROR;
 
427
}
 
428
 
 
429
/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
 
430
 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
 
431
 * Use SCRATCH_POOL for temporary allocations. */
 
432
static svn_error_t *
 
433
hotcopy_remove_rev_files(svn_fs_t *dst_fs,
 
434
                         svn_revnum_t start_rev,
 
435
                         svn_revnum_t end_rev,
 
436
                         int max_files_per_dir,
 
437
                         apr_pool_t *scratch_pool)
 
438
{
 
439
  SVN_ERR_ASSERT(start_rev <= end_rev);
 
440
  SVN_ERR(hotcopy_remove_files(dst_fs,
 
441
                               svn_dirent_join(dst_fs->path,
 
442
                                               PATH_REVS_DIR,
 
443
                                               scratch_pool),
 
444
                               start_rev, end_rev,
 
445
                               max_files_per_dir, scratch_pool));
 
446
 
 
447
  return SVN_NO_ERROR;
 
448
}
 
449
 
 
450
/* Remove revision properties between START_REV (inclusive) and END_REV
 
451
 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
 
452
 * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
 
453
 * not be deleted. */
 
454
static svn_error_t *
 
455
hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
 
456
                             svn_revnum_t start_rev,
 
457
                             svn_revnum_t end_rev,
 
458
                             int max_files_per_dir,
 
459
                             apr_pool_t *scratch_pool)
 
460
{
 
461
  SVN_ERR_ASSERT(start_rev <= end_rev);
 
462
 
 
463
  /* don't delete rev 0 props */
 
464
  SVN_ERR(hotcopy_remove_files(dst_fs,
 
465
                               svn_dirent_join(dst_fs->path,
 
466
                                               PATH_REVPROPS_DIR,
 
467
                                               scratch_pool),
 
468
                               start_rev ? start_rev : 1, end_rev,
 
469
                               max_files_per_dir, scratch_pool));
 
470
 
 
471
  return SVN_NO_ERROR;
 
472
}
 
473
 
 
474
/* Verify that DST_FS is a suitable destination for an incremental
 
475
 * hotcopy from SRC_FS. */
 
476
static svn_error_t *
 
477
hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
 
478
                                        svn_fs_t *dst_fs)
 
479
{
 
480
  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
 
481
  svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data;
 
482
 
 
483
  /* We only support incremental hotcopy between the same format. */
 
484
  if (src_ffd->format != dst_ffd->format)
 
485
    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
 
486
      _("The FSX format (%d) of the hotcopy source does not match the "
 
487
        "FSX format (%d) of the hotcopy destination; please upgrade "
 
488
        "both repositories to the same format"),
 
489
      src_ffd->format, dst_ffd->format);
 
490
 
 
491
  /* Make sure the UUID of source and destination match up.
 
492
   * We don't want to copy over a different repository. */
 
493
  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
 
494
    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
 
495
                            _("The UUID of the hotcopy source does "
 
496
                              "not match the UUID of the hotcopy "
 
497
                              "destination"));
 
498
 
 
499
  /* Also require same shard size. */
 
500
  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
 
501
    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
 
502
                            _("The sharding layout configuration "
 
503
                              "of the hotcopy source does not match "
 
504
                              "the sharding layout configuration of "
 
505
                              "the hotcopy destination"));
 
506
  return SVN_NO_ERROR;
 
507
}
 
508
 
 
509
/* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
 
510
 * CANCEL_FUNC and CANCEL_BATON do the usual thing.
 
511
 * Use SCRATCH_POOL for temporary allocations.
 
512
 */
 
513
static svn_error_t *
 
514
remove_folder(const char *path,
 
515
              svn_cancel_func_t cancel_func,
 
516
              void *cancel_baton,
 
517
              apr_pool_t *scratch_pool)
 
518
{
 
519
  svn_error_t *err = svn_io_remove_dir2(path, TRUE,
 
520
                                        cancel_func, cancel_baton,
 
521
                                        scratch_pool);
 
522
 
 
523
  if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
 
524
    {
 
525
      svn_error_clear(err);
 
526
      err = SVN_NO_ERROR;
 
527
    }
 
528
 
 
529
  return svn_error_trace(err);
 
530
}
 
531
 
 
532
/* Copy the revision and revprop files (possibly sharded / packed) from
 
533
 * SRC_FS to DST_FS.  Do not re-copy data which already exists in DST_FS.
 
534
 * When copying packed or unpacked shards, checkpoint the result in DST_FS
 
535
 * for every shard by updating the 'current' file if necessary.  Assume
 
536
 * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
 
537
 * global next-ID counters.  Indicate progress via the optional NOTIFY_FUNC
 
538
 * callback using NOTIFY_BATON.  Use SCRATCH_POOL for temporary allocations.
 
539
 */
 
540
static svn_error_t *
 
541
hotcopy_revisions(svn_fs_t *src_fs,
 
542
                  svn_fs_t *dst_fs,
 
543
                  svn_revnum_t src_youngest,
 
544
                  svn_revnum_t dst_youngest,
 
545
                  svn_boolean_t incremental,
 
546
                  const char *src_revs_dir,
 
547
                  const char *dst_revs_dir,
 
548
                  const char *src_revprops_dir,
 
549
                  const char *dst_revprops_dir,
 
550
                  svn_fs_hotcopy_notify_t notify_func,
 
551
                  void* notify_baton,
 
552
                  svn_cancel_func_t cancel_func,
 
553
                  void* cancel_baton,
 
554
                  apr_pool_t *scratch_pool)
 
555
{
 
556
  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
 
557
  int max_files_per_dir = src_ffd->max_files_per_dir;
 
558
  svn_revnum_t src_min_unpacked_rev;
 
559
  svn_revnum_t dst_min_unpacked_rev;
 
560
  svn_revnum_t rev;
 
561
  apr_pool_t *iterpool;
 
562
 
 
563
  /* Copy the min unpacked rev, and read its value. */
 
564
  SVN_ERR(svn_fs_x__read_min_unpacked_rev(&src_min_unpacked_rev, src_fs,
 
565
                                          scratch_pool));
 
566
  SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs,
 
567
                                          scratch_pool));
 
568
 
 
569
  /* We only support packs coming from the hotcopy source.
 
570
    * The destination should not be packed independently from
 
571
    * the source. This also catches the case where users accidentally
 
572
    * swap the source and destination arguments. */
 
573
  if (src_min_unpacked_rev < dst_min_unpacked_rev)
 
574
    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
 
575
                             _("The hotcopy destination already contains "
 
576
                               "more packed revisions (%lu) than the "
 
577
                               "hotcopy source contains (%lu)"),
 
578
                             dst_min_unpacked_rev - 1,
 
579
                             src_min_unpacked_rev - 1);
 
580
 
 
581
  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
 
582
                               PATH_MIN_UNPACKED_REV, scratch_pool));
 
583
 
 
584
  if (cancel_func)
 
585
    SVN_ERR(cancel_func(cancel_baton));
 
586
 
 
587
  /*
 
588
   * Copy the necessary rev files.
 
589
   */
 
590
 
 
591
  iterpool = svn_pool_create(scratch_pool);
 
592
  /* First, copy packed shards. */
 
593
  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
 
594
    {
 
595
      svn_boolean_t skipped = TRUE;
 
596
      svn_revnum_t pack_end_rev;
 
597
 
 
598
      svn_pool_clear(iterpool);
 
599
 
 
600
      if (cancel_func)
 
601
        SVN_ERR(cancel_func(cancel_baton));
 
602
 
 
603
      /* Copy the packed shard. */
 
604
      SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
 
605
                                        src_fs, dst_fs,
 
606
                                        rev, max_files_per_dir,
 
607
                                        iterpool));
 
608
 
 
609
      pack_end_rev = rev + max_files_per_dir - 1;
 
610
 
 
611
      /* Whenever this pack did not previously exist in the destination,
 
612
       * update 'current' to the most recent packed rev (so readers can see
 
613
       * new revisions which arrived in this pack). */
 
614
      if (pack_end_rev > dst_youngest)
 
615
        {
 
616
          SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool));
 
617
        }
 
618
 
 
619
      /* When notifying about packed shards, make things simpler by either
 
620
       * reporting a full revision range, i.e [pack start, pack end] or
 
621
       * reporting nothing. There is one case when this approach might not
 
622
       * be exact (incremental hotcopy with a pack replacing last unpacked
 
623
       * revisions), but generally this is good enough. */
 
624
      if (notify_func && !skipped)
 
625
        notify_func(notify_baton, rev, pack_end_rev, iterpool);
 
626
 
 
627
      /* Remove revision files which are now packed. */
 
628
      if (incremental)
 
629
        {
 
630
          SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
 
631
                                           rev + max_files_per_dir,
 
632
                                           max_files_per_dir, iterpool));
 
633
          SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
 
634
                                               rev + max_files_per_dir,
 
635
                                               max_files_per_dir,
 
636
                                               iterpool));
 
637
        }
 
638
 
 
639
      /* Now that all revisions have moved into the pack, the original
 
640
       * rev dir can be removed. */
 
641
      SVN_ERR(remove_folder(svn_fs_x__path_rev_shard(dst_fs, rev, iterpool),
 
642
                            cancel_func, cancel_baton, iterpool));
 
643
      if (rev > 0)
 
644
        SVN_ERR(remove_folder(svn_fs_x__path_revprops_shard(dst_fs, rev,
 
645
                                                            iterpool),
 
646
                              cancel_func, cancel_baton, iterpool));
 
647
    }
 
648
 
 
649
  if (cancel_func)
 
650
    SVN_ERR(cancel_func(cancel_baton));
 
651
 
 
652
  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
 
653
  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
 
654
 
 
655
  /* Now, copy pairs of non-packed revisions and revprop files.
 
656
   * If necessary, update 'current' after copying all files from a shard. */
 
657
  for (; rev <= src_youngest; rev++)
 
658
    {
 
659
      svn_boolean_t skipped = TRUE;
 
660
 
 
661
      svn_pool_clear(iterpool);
 
662
 
 
663
      if (cancel_func)
 
664
        SVN_ERR(cancel_func(cancel_baton));
 
665
 
 
666
      /* Copying non-packed revisions is racy in case the source repository is
 
667
       * being packed concurrently with this hotcopy operation.  With the pack
 
668
       * lock, however, the race is impossible, because hotcopy and pack
 
669
       * operations block each other.
 
670
       *
 
671
       * We assume that all revisions coming after 'min-unpacked-rev' really
 
672
       * are unpacked and that's not necessarily true with concurrent packing.
 
673
       * Don't try to be smart in this edge case, because handling it properly
 
674
       * might require copying *everything* from the start. Just abort the
 
675
       * hotcopy with an ENOENT (revision file moved to a pack, so it is no
 
676
       * longer where we expect it to be). */
 
677
 
 
678
      /* Copy the rev file. */
 
679
      SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
 
680
                                      rev, max_files_per_dir,
 
681
                                      iterpool));
 
682
 
 
683
      /* Copy the revprop file. */
 
684
      SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revprops_dir,
 
685
                                      dst_revprops_dir,
 
686
                                      rev, max_files_per_dir,
 
687
                                      iterpool));
 
688
 
 
689
      /* Whenever this revision did not previously exist in the destination,
 
690
       * checkpoint the progress via 'current' (do that once per full shard
 
691
       * in order not to slow things down). */
 
692
      if (rev > dst_youngest)
 
693
        {
 
694
          if (max_files_per_dir && (rev % max_files_per_dir == 0))
 
695
            {
 
696
              SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool));
 
697
            }
 
698
        }
 
699
 
 
700
      if (notify_func && !skipped)
 
701
        notify_func(notify_baton, rev, rev, iterpool);
 
702
    }
 
703
  svn_pool_destroy(iterpool);
 
704
 
 
705
  /* We assume that all revisions were copied now, i.e. we didn't exit the
 
706
   * above loop early. 'rev' was last incremented during exit of the loop. */
 
707
  SVN_ERR_ASSERT(rev == src_youngest + 1);
 
708
 
 
709
  return SVN_NO_ERROR;
 
710
}
 
711
 
 
712
/* Baton for hotcopy_body(). */
 
713
typedef struct hotcopy_body_baton_t {
 
714
  svn_fs_t *src_fs;
 
715
  svn_fs_t *dst_fs;
 
716
  svn_boolean_t incremental;
 
717
  svn_fs_hotcopy_notify_t notify_func;
 
718
  void *notify_baton;
 
719
  svn_cancel_func_t cancel_func;
 
720
  void *cancel_baton;
 
721
} hotcopy_body_baton_t;
 
722
 
 
723
/* Perform a hotcopy, either normal or incremental.
 
724
 *
 
725
 * Normal hotcopy assumes that the destination exists as an empty
 
726
 * directory. It behaves like an incremental hotcopy except that
 
727
 * none of the copied files already exist in the destination.
 
728
 *
 
729
 * An incremental hotcopy copies only changed or new files to the destination,
 
730
 * and removes files from the destination no longer present in the source.
 
731
 * While the incremental hotcopy is running, readers should still be able
 
732
 * to access the destintation repository without error and should not see
 
733
 * revisions currently in progress of being copied. Readers are able to see
 
734
 * new fully copied revisions even if the entire incremental hotcopy procedure
 
735
 * has not yet completed.
 
736
 *
 
737
 * Writers are blocked out completely during the entire incremental hotcopy
 
738
 * process to ensure consistency. This function assumes that the repository
 
739
 * write-lock is held.
 
740
 */
 
741
static svn_error_t *
 
742
hotcopy_body(void *baton,
 
743
             apr_pool_t *scratch_pool)
 
744
{
 
745
  hotcopy_body_baton_t *hbb = baton;
 
746
  svn_fs_t *src_fs = hbb->src_fs;
 
747
  svn_fs_t *dst_fs = hbb->dst_fs;
 
748
  svn_boolean_t incremental = hbb->incremental;
 
749
  svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
 
750
  void* notify_baton = hbb->notify_baton;
 
751
  svn_cancel_func_t cancel_func = hbb->cancel_func;
 
752
  void* cancel_baton = hbb->cancel_baton;
 
753
  svn_revnum_t src_youngest;
 
754
  svn_revnum_t dst_youngest;
 
755
  const char *src_revprops_dir;
 
756
  const char *dst_revprops_dir;
 
757
  const char *src_revs_dir;
 
758
  const char *dst_revs_dir;
 
759
  const char *src_subdir;
 
760
  const char *dst_subdir;
 
761
  svn_node_kind_t kind;
 
762
 
 
763
  /* Try to copy the config.
 
764
   *
 
765
   * ### We try copying the config file before doing anything else,
 
766
   * ### because higher layers will abort the hotcopy if we throw
 
767
   * ### an error from this function, and that renders the hotcopy
 
768
   * ### unusable anyway. */
 
769
  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
 
770
                               scratch_pool));
 
771
 
 
772
  if (cancel_func)
 
773
    SVN_ERR(cancel_func(cancel_baton));
 
774
 
 
775
  /* Find the youngest revision in the source and destination.
 
776
   * We only support hotcopies from sources with an equal or greater amount
 
777
   * of revisions than the destination.
 
778
   * This also catches the case where users accidentally swap the
 
779
   * source and destination arguments. */
 
780
  SVN_ERR(svn_fs_x__read_current(&src_youngest, src_fs, scratch_pool));
 
781
  if (incremental)
 
782
    {
 
783
      SVN_ERR(svn_fs_x__youngest_rev(&dst_youngest, dst_fs, scratch_pool));
 
784
      if (src_youngest < dst_youngest)
 
785
        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
 
786
                 _("The hotcopy destination already contains more revisions "
 
787
                   "(%lu) than the hotcopy source contains (%lu); are source "
 
788
                   "and destination swapped?"),
 
789
                   dst_youngest, src_youngest);
 
790
    }
 
791
  else
 
792
    dst_youngest = 0;
 
793
 
 
794
  src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
 
795
  dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
 
796
  src_revprops_dir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR,
 
797
                                     scratch_pool);
 
798
  dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR,
 
799
                                     scratch_pool);
 
800
 
 
801
  /* Ensure that the required folders exist in the destination
 
802
   * before actually copying the revisions and revprops. */
 
803
  SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, scratch_pool));
 
804
  SVN_ERR(svn_io_make_dir_recursively(dst_revprops_dir, scratch_pool));
 
805
 
 
806
  if (cancel_func)
 
807
    SVN_ERR(cancel_func(cancel_baton));
 
808
 
 
809
  /* Split the logic for new and old FS formats. The latter is much simpler
 
810
   * due to the absense of sharding and packing. However, it requires special
 
811
   * care when updating the 'current' file (which contains not just the
 
812
   * revision number, but also the next-ID counters). */
 
813
  SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
 
814
                            incremental, src_revs_dir, dst_revs_dir,
 
815
                            src_revprops_dir, dst_revprops_dir,
 
816
                            notify_func, notify_baton,
 
817
                            cancel_func, cancel_baton, scratch_pool));
 
818
  SVN_ERR(svn_fs_x__write_current(dst_fs, src_youngest, scratch_pool));
 
819
 
 
820
  /* Replace the locks tree.
 
821
   * This is racy in case readers are currently trying to list locks in
 
822
   * the destination. However, we need to get rid of stale locks.
 
823
   * This is the simplest way of doing this, so we accept this small race. */
 
824
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, scratch_pool);
 
825
  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
 
826
                             scratch_pool));
 
827
  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, scratch_pool);
 
828
  SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
 
829
  if (kind == svn_node_dir)
 
830
    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
 
831
                                        PATH_LOCKS_DIR, TRUE,
 
832
                                        cancel_func, cancel_baton,
 
833
                                        scratch_pool));
 
834
 
 
835
  /* Now copy the node-origins cache tree. */
 
836
  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR,
 
837
                               scratch_pool);
 
838
  SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
 
839
  if (kind == svn_node_dir)
 
840
    SVN_ERR(hotcopy_io_copy_dir_recursively(NULL, src_subdir, dst_fs->path,
 
841
                                            PATH_NODE_ORIGINS_DIR, TRUE,
 
842
                                            cancel_func, cancel_baton,
 
843
                                            scratch_pool));
 
844
 
 
845
  /*
 
846
   * NB: Data copied below is only read by writers, not readers.
 
847
   *     Writers are still locked out at this point.
 
848
   */
 
849
 
 
850
  /* Copy the rep cache and then remove entries for revisions
 
851
   * younger than the destination's youngest revision. */
 
852
  src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, scratch_pool);
 
853
  dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, scratch_pool);
 
854
  SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
 
855
  if (kind == svn_node_file)
 
856
    {
 
857
      /* Copy the rep cache and then remove entries for revisions
 
858
       * that did not make it into the destination. */
 
859
      SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, scratch_pool));
 
860
      SVN_ERR(svn_fs_x__del_rep_reference(dst_fs, src_youngest,
 
861
                                          scratch_pool));
 
862
    }
 
863
 
 
864
  /* Copy the txn-current file. */
 
865
  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
 
866
                                PATH_TXN_CURRENT, scratch_pool));
 
867
 
 
868
  /* If a revprop generation file exists in the source filesystem,
 
869
   * reset it to zero (since this is on a different path, it will not
 
870
   * overlap with data already in cache).  Also, clean up stale files
 
871
   * used for the named atomics implementation. */
 
872
  SVN_ERR(svn_fs_x__reset_revprop_generation_file(dst_fs, scratch_pool));
 
873
 
 
874
  return SVN_NO_ERROR;
 
875
}
 
876
 
 
877
/* Wrapper around hotcopy_body taking out all necessary source repository
 
878
 * locks.
 
879
 */
 
880
static svn_error_t *
 
881
hotcopy_locking_src_body(void *baton,
 
882
                         apr_pool_t *scratch_pool)
 
883
{
 
884
  hotcopy_body_baton_t *hbb = baton;
 
885
 
 
886
  return svn_error_trace(svn_fs_x__with_pack_lock(hbb->src_fs, hotcopy_body,
 
887
                                                  baton, scratch_pool));
 
888
}
 
889
 
 
890
/* Create an empty filesystem at DST_FS at DST_PATH with the same
 
891
 * configuration as SRC_FS (uuid, format, and other parameters).
 
892
 * After creation DST_FS has no revisions, not even revision zero. */
 
893
static svn_error_t *
 
894
hotcopy_create_empty_dest(svn_fs_t *src_fs,
 
895
                          svn_fs_t *dst_fs,
 
896
                          const char *dst_path,
 
897
                          apr_pool_t *scratch_pool)
 
898
{
 
899
  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
 
900
 
 
901
  /* Create the DST_FS repository with the same layout as SRC_FS. */
 
902
  SVN_ERR(svn_fs_x__create_file_tree(dst_fs, dst_path, src_ffd->format,
 
903
                                     src_ffd->max_files_per_dir,
 
904
                                     scratch_pool));
 
905
 
 
906
  /* Copy the UUID.  Hotcopy destination receives a new instance ID, but
 
907
   * has the same filesystem UUID as the source. */
 
908
  SVN_ERR(svn_fs_x__set_uuid(dst_fs, src_fs->uuid, NULL, scratch_pool));
 
909
 
 
910
  /* Remove revision 0 contents.  Otherwise, it may not get overwritten
 
911
   * due to having a newer timestamp. */
 
912
  SVN_ERR(hotcopy_remove_file(svn_fs_x__path_rev(dst_fs, 0, scratch_pool),
 
913
                              scratch_pool));
 
914
  SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0,
 
915
                                                      scratch_pool),
 
916
                              scratch_pool));
 
917
 
 
918
  /* This filesystem is ready.  Stamp it with a format number.  Fail if
 
919
   * the 'format' file should already exist. */
 
920
  SVN_ERR(svn_fs_x__write_format(dst_fs, FALSE, scratch_pool));
 
921
 
 
922
  return SVN_NO_ERROR;
 
923
}
 
924
 
 
925
svn_error_t *
 
926
svn_fs_x__hotcopy_prepare_target(svn_fs_t *src_fs,
 
927
                                 svn_fs_t *dst_fs,
 
928
                                 const char *dst_path,
 
929
                                 svn_boolean_t incremental,
 
930
                                 apr_pool_t *scratch_pool)
 
931
{
 
932
  if (incremental)
 
933
    {
 
934
      const char *dst_format_abspath;
 
935
      svn_node_kind_t dst_format_kind;
 
936
 
 
937
      /* Check destination format to be sure we know how to incrementally
 
938
       * hotcopy to the destination FS. */
 
939
      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT,
 
940
                                           scratch_pool);
 
941
      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind,
 
942
                                scratch_pool));
 
943
      if (dst_format_kind == svn_node_none)
 
944
        {
 
945
          /* Destination doesn't exist yet. Perform a normal hotcopy to a
 
946
           * empty destination using the same configuration as the source. */
 
947
          SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path,
 
948
                                            scratch_pool));
 
949
        }
 
950
      else
 
951
        {
 
952
          /* Check the existing repository. */
 
953
          SVN_ERR(svn_fs_x__open(dst_fs, dst_path, scratch_pool));
 
954
          SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs));
 
955
        }
 
956
    }
 
957
  else
 
958
    {
 
959
      /* Start out with an empty destination using the same configuration
 
960
       * as the source. */
 
961
      SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path,
 
962
                                        scratch_pool));
 
963
    }
 
964
 
 
965
  return SVN_NO_ERROR;
 
966
}
 
967
 
 
968
svn_error_t *
 
969
svn_fs_x__hotcopy(svn_fs_t *src_fs,
 
970
                  svn_fs_t *dst_fs,
 
971
                  svn_boolean_t incremental,
 
972
                  svn_fs_hotcopy_notify_t notify_func,
 
973
                  void *notify_baton,
 
974
                  svn_cancel_func_t cancel_func,
 
975
                  void *cancel_baton,
 
976
                  apr_pool_t *scratch_pool)
 
977
{
 
978
  hotcopy_body_baton_t hbb;
 
979
 
 
980
  hbb.src_fs = src_fs;
 
981
  hbb.dst_fs = dst_fs;
 
982
  hbb.incremental = incremental;
 
983
  hbb.notify_func = notify_func;
 
984
  hbb.notify_baton = notify_baton;
 
985
  hbb.cancel_func = cancel_func;
 
986
  hbb.cancel_baton = cancel_baton;
 
987
  SVN_ERR(svn_fs_x__with_all_locks(dst_fs, hotcopy_locking_src_body, &hbb,
 
988
                                   scratch_pool));
 
989
 
 
990
  return SVN_NO_ERROR;
 
991
}