1
/* hotcopys.c --- FS hotcopy functionality for FSX
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
12
* http://www.apache.org/licenses/LICENSE-2.0
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
20
* ====================================================================
22
#include "svn_pools.h"
24
#include "svn_dirent_uri.h"
30
#include "rep-cache.h"
31
#include "transaction.h"
34
#include "../libsvn_fs/fs-loader.h"
36
#include "svn_private_config.h"
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
44
hotcopy_io_dir_file_copy(svn_boolean_t *skipped_p,
48
apr_pool_t *scratch_pool)
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;
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)
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)
76
return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
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.
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.
92
* If there is any other error, just return that error directly.
94
* If there is any error, the effect on *NAME_P is undefined.
96
* *NAME_P and NAME may refer to the same storage.
99
entry_name_to_utf8(const char **name_p,
102
apr_pool_t *result_pool)
104
svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, result_pool);
105
if (err && err->apr_err == APR_EINVAL)
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));
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. */
121
hotcopy_io_copy_dir_recursively(svn_boolean_t *skipped_p,
123
const char *dst_parent,
124
const char *dst_basename,
125
svn_boolean_t copy_perms,
126
svn_cancel_func_t cancel_func,
128
apr_pool_t *scratch_pool)
130
svn_node_kind_t kind;
132
const char *dst_path;
134
apr_finfo_t this_entry;
135
apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
137
/* Make a subpool for recursion */
138
apr_pool_t *subpool = svn_pool_create(scratch_pool);
140
/* The 'dst_path' is simply dst_parent/dst_basename */
141
dst_path = svn_dirent_join(dst_parent, dst_basename, scratch_pool);
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));
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,
158
SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
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));
164
/* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
165
SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
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))
171
if ((this_entry.name[0] == '.')
172
&& ((this_entry.name[1] == '\0')
173
|| ((this_entry.name[1] == '.')
174
&& (this_entry.name[2] == '\0'))))
180
const char *entryname_utf8;
183
SVN_ERR(cancel_func(cancel_baton));
185
SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
187
if (this_entry.filetype == APR_REG) /* regular file */
189
SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, src, dst_path,
190
entryname_utf8, subpool));
192
else if (this_entry.filetype == APR_LNK) /* symlink */
194
const char *src_target = svn_dirent_join(src, entryname_utf8,
196
const char *dst_target = svn_dirent_join(dst_path,
199
SVN_ERR(svn_io_copy_link(src_target, dst_target,
202
else if (this_entry.filetype == APR_DIR) /* recurse */
204
const char *src_target;
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)
212
src_target = svn_dirent_join(src, entryname_utf8, subpool);
213
SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
222
/* ### support other APR node types someday?? */
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));
231
status = apr_dir_close(this_dir);
233
return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
234
svn_dirent_local_style(src, scratch_pool));
236
/* Free any memory used by recursion */
237
svn_pool_destroy(subpool);
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. */
248
hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
249
const char *src_subdir,
250
const char *dst_subdir,
252
int max_files_per_dir,
253
apr_pool_t *scratch_pool)
255
const char *src_subdir_shard = src_subdir,
256
*dst_subdir_shard = dst_subdir;
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);
263
if (rev % max_files_per_dir == 0)
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,
270
SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
271
src_subdir_shard, dst_subdir_shard,
272
apr_psprintf(scratch_pool, "%ld", rev),
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. */
287
hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
288
svn_revnum_t *dst_min_unpacked_rev,
292
int max_files_per_dir,
293
apr_pool_t *scratch_pool)
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;
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,
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,
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);
320
if (src_ffd->min_unpacked_rev < rev + max_files_per_dir)
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;
328
svn_pool_clear(iterpool);
330
SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
331
revprop_rev, max_files_per_dir,
334
svn_pool_destroy(iterpool);
338
/* revprop for revision 0 will never be packed */
340
SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
341
0, max_files_per_dir,
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,
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,
357
/* If necessary, update the min-unpacked rev file in the hotcopy. */
358
if (*dst_min_unpacked_rev < rev + max_files_per_dir)
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,
369
/* Remove file PATH, if it exists - even if it is read-only.
370
* Use SCRATCH_POOL for temporary allocations. */
372
hotcopy_remove_file(const char *path,
373
apr_pool_t *scratch_pool)
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));
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. */
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)
396
const char *dst_subdir_shard;
398
apr_pool_t *iterpool;
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);
404
iterpool = svn_pool_create(scratch_pool);
405
for (rev = start_rev; rev < end_rev; rev++)
407
svn_pool_clear(iterpool);
409
/* If necessary, update paths for shard. */
410
if (rev != start_rev && rev % max_files_per_dir == 0)
412
shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
413
dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
416
/* remove files for REV */
417
SVN_ERR(hotcopy_remove_file(svn_dirent_join(dst_subdir_shard,
418
apr_psprintf(iterpool,
424
svn_pool_destroy(iterpool);
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. */
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)
439
SVN_ERR_ASSERT(start_rev <= end_rev);
440
SVN_ERR(hotcopy_remove_files(dst_fs,
441
svn_dirent_join(dst_fs->path,
445
max_files_per_dir, scratch_pool));
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
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)
461
SVN_ERR_ASSERT(start_rev <= end_rev);
463
/* don't delete rev 0 props */
464
SVN_ERR(hotcopy_remove_files(dst_fs,
465
svn_dirent_join(dst_fs->path,
468
start_rev ? start_rev : 1, end_rev,
469
max_files_per_dir, scratch_pool));
474
/* Verify that DST_FS is a suitable destination for an incremental
475
* hotcopy from SRC_FS. */
477
hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
480
svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
481
svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data;
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);
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 "
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"));
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.
514
remove_folder(const char *path,
515
svn_cancel_func_t cancel_func,
517
apr_pool_t *scratch_pool)
519
svn_error_t *err = svn_io_remove_dir2(path, TRUE,
520
cancel_func, cancel_baton,
523
if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
525
svn_error_clear(err);
529
return svn_error_trace(err);
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.
541
hotcopy_revisions(svn_fs_t *src_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,
552
svn_cancel_func_t cancel_func,
554
apr_pool_t *scratch_pool)
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;
561
apr_pool_t *iterpool;
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,
566
SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs,
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);
581
SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
582
PATH_MIN_UNPACKED_REV, scratch_pool));
585
SVN_ERR(cancel_func(cancel_baton));
588
* Copy the necessary rev files.
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)
595
svn_boolean_t skipped = TRUE;
596
svn_revnum_t pack_end_rev;
598
svn_pool_clear(iterpool);
601
SVN_ERR(cancel_func(cancel_baton));
603
/* Copy the packed shard. */
604
SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
606
rev, max_files_per_dir,
609
pack_end_rev = rev + max_files_per_dir - 1;
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)
616
SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool));
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);
627
/* Remove revision files which are now packed. */
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,
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));
644
SVN_ERR(remove_folder(svn_fs_x__path_revprops_shard(dst_fs, rev,
646
cancel_func, cancel_baton, iterpool));
650
SVN_ERR(cancel_func(cancel_baton));
652
SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
653
SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
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++)
659
svn_boolean_t skipped = TRUE;
661
svn_pool_clear(iterpool);
664
SVN_ERR(cancel_func(cancel_baton));
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.
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). */
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,
683
/* Copy the revprop file. */
684
SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revprops_dir,
686
rev, max_files_per_dir,
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)
694
if (max_files_per_dir && (rev % max_files_per_dir == 0))
696
SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool));
700
if (notify_func && !skipped)
701
notify_func(notify_baton, rev, rev, iterpool);
703
svn_pool_destroy(iterpool);
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);
712
/* Baton for hotcopy_body(). */
713
typedef struct hotcopy_body_baton_t {
716
svn_boolean_t incremental;
717
svn_fs_hotcopy_notify_t notify_func;
719
svn_cancel_func_t cancel_func;
721
} hotcopy_body_baton_t;
723
/* Perform a hotcopy, either normal or incremental.
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.
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.
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.
742
hotcopy_body(void *baton,
743
apr_pool_t *scratch_pool)
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;
763
/* Try to copy the config.
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,
773
SVN_ERR(cancel_func(cancel_baton));
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));
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);
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,
798
dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR,
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));
807
SVN_ERR(cancel_func(cancel_baton));
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));
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,
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,
835
/* Now copy the node-origins cache tree. */
836
src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR,
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,
846
* NB: Data copied below is only read by writers, not readers.
847
* Writers are still locked out at this point.
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)
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,
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));
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));
877
/* Wrapper around hotcopy_body taking out all necessary source repository
881
hotcopy_locking_src_body(void *baton,
882
apr_pool_t *scratch_pool)
884
hotcopy_body_baton_t *hbb = baton;
886
return svn_error_trace(svn_fs_x__with_pack_lock(hbb->src_fs, hotcopy_body,
887
baton, scratch_pool));
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. */
894
hotcopy_create_empty_dest(svn_fs_t *src_fs,
896
const char *dst_path,
897
apr_pool_t *scratch_pool)
899
svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
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,
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));
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),
914
SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0,
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));
926
svn_fs_x__hotcopy_prepare_target(svn_fs_t *src_fs,
928
const char *dst_path,
929
svn_boolean_t incremental,
930
apr_pool_t *scratch_pool)
934
const char *dst_format_abspath;
935
svn_node_kind_t dst_format_kind;
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,
941
SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind,
943
if (dst_format_kind == svn_node_none)
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,
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));
959
/* Start out with an empty destination using the same configuration
961
SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path,
969
svn_fs_x__hotcopy(svn_fs_t *src_fs,
971
svn_boolean_t incremental,
972
svn_fs_hotcopy_notify_t notify_func,
974
svn_cancel_func_t cancel_func,
976
apr_pool_t *scratch_pool)
978
hotcopy_body_baton_t hbb;
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,